Почему базовые POCO Entity Framework, возвращаемые из DbQuery, должны быть открытыми? - PullRequest
0 голосов
/ 30 октября 2018

Я использую Microsoft.EntityFrameworkCore.DbQuery для возврата POCO из результата табличной функции в моей базе данных. Я заметил, что ни в одном из моих POCO не установлены какие-либо свойства - все они являются значениями по умолчанию - при условии, что их модификаторы доступа отличаются от public. В моем случае все POCO явно реализуют интерфейсы, поэтому потребитель ничего не знает (или не должен знать) об определениях POCO, поэтому я хочу установить для них значение internal.

.

Почему POCO должны быть публичными? Или есть способ сделать так, чтобы они не были публичными? Вот пример кода, демонстрирующего это:

CoreLibrary.dll (.NET Standard 2.0)

using System.Collections.Generic;

namespace CoreLibrary
{
    public interface IWidgetRepository
    {
        IReadOnlyCollection<IWidget> FindAllWidgets();
        IReadOnlyCollection<IWidget> FindWidgets(string withText);
    }

    public interface IWidget
    {
        int ID { get; }
        string Name { get; }
        string Value { get; }
    }
}

DataAccess.dll (.NET Standard 2.0)

using CoreLibrary;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;

namespace DataAccess
{
    public sealed class WidgetRepositoryApi : IWidgetRepository
    {
        private DatabaseContext _dbContext;

        public WidgetRepositoryApi(string databaseServerName, string databaseName)
            => _dbContext = new DatabaseContext(databaseServerName, databaseName);

        public IReadOnlyCollection<IWidget> FindAllWidgets()
            => _dbContext.Widget.ToList();

        public IReadOnlyCollection<IWidget> FindWidgets(string withText)
        {
            string sqlQuery = "SELECT * FROM dbo.fn_SearchWidgetText(@Text)";

            var ret = _dbContext.GetWidgetFullTextSearchMatches
                .FromSql(sqlQuery, new SqlParameter("@Text", 
                System.Data.SqlDbType.NVarChar) { Value = withText });

            return ret.ToList().AsReadOnly();
        }
    }

    internal partial class DatabaseContext : DbContext
    {
        private string _dbConnectionString;

        internal DatabaseContext(string databaseServerName, string databaseName) 
            => _dbConnectionString = 
            $"Server={databaseServerName};Database={databaseName};Trusted_Connection=True;";

        internal virtual DbSet<DBSetWidget> Widget { get; set; }

        internal virtual DbQuery<DBQueryWidget> GetWidgetFullTextSearchMatches { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseSqlServer(_dbConnectionString);
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<DBSetWidget>(entity =>
            {
                entity.Property(e => e.Id).HasColumnName("ID");

                entity.Property(e => e.Name)
                    .IsRequired()
                    .HasMaxLength(50);

                entity.Property(e => e.Value).IsRequired();
            });
        }
    }

    /********************************************************************
     * NOTE: there need to be two widget implementations (POCOs)
     * because otherwise the code throws
     * System.InvalidCastException: 'Unable to cast object of type 
     * 'Microsoft.EntityFrameworkCore.Internal.
     * InternalDbQuery`1[DataAccess.DBSetWidget]' to type 
     * 'Microsoft.EntityFrameworkCore.DbSet`1[DataAccess.DBSetWidget]'.'
     *******************************************************************/

    internal partial class DBQueryWidget : IWidget
    {
        internal int Id { get; set; }
        internal string Name { get; set; }
        internal string Value { get; set; }
        int IWidget.ID => Id;
        string IWidget.Name => Name;
        string IWidget.Value => Value;
    }

    internal partial class DBSetWidget : IWidget
    {
        internal int Id { get; set; }
        internal string Name { get; set; }
        internal string Value { get; set; }
        int IWidget.ID => Id;
        string IWidget.Name => Name;
        string IWidget.Value => Value;
    }
}

ConsumerConsoleApp.exe (.NET Core 2.1)

using DataAccess;

namespace ConsumerConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var repo = new WidgetRepositoryApi(args[0], args[1]);
            var allWidgets = repo.FindAllWidgets();
            var someWidgets = repo.FindWidgets("facebook");
        }
    }
}

Таблица виджетов в базе данных SQL

CREATE TABLE [dbo].[Widget](
[ID] [INT] IDENTITY(1,1) NOT NULL,
[Name] [NVARCHAR](50) NOT NULL,
[Value] [NVARCHAR](MAX) NOT NULL,
CONSTRAINT [PK_Widget] PRIMARY KEY CLUSTERED 
(
[ID] ASC
)WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, 
IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

fn_SearchWidgetText в базе данных

CREATE FUNCTION [dbo].[fn_SearchWidgetText]
(   
    @Text NVARCHAR(1000)
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT * 
    FROM dbo.Widget
    WHERE CONTAINS(Value, @Text)
)

enter image description here

enter image description here

Если я изменю все мои модификаторы доступа в моем пространстве имен DataAccess на public, теперь POCO заполнятся правильно:

enter image description here

Ответы [ 2 ]

0 голосов
/ 02 ноября 2018

Ядро Entity Framework не отображает внутренние свойства в соответствии с соглашениями по умолчанию. Единственная разница между DBQueryWidget и DBSetWidget заключается в том, что для последнего у вас есть явная инструкция отображения:

modelBuilder.Entity<DBSetWidget>(entity =>
{
    entity.Property(e => e.Id).HasColumnName("ID");

    entity.Property(e => e.Name)
        .IsRequired()
        .HasMaxLength(50);

    entity.Property(e => e.Value).IsRequired();
});

Для DBQueryWidget такого отображения нет, поэтому EF следует соглашению по умолчанию и не отображает свои внутренние свойства. Поэтому единственное, что вам нужно исправить, это добавить аналогичное отображение для DBQueryWidget.

0 голосов
/ 30 октября 2018

Краткий ответ только потому, что он разработан.

Наблюдение за состоянием любого объекта можно разделить на две части. Первая часть является публичным состоянием, а вторая часть является внутренним состоянием объекта. При разработке технологии хранилища данных вы должны решить, что вы хотите хранить. Только одна часть или оба? EF предназначен для хранения практически любого объекта (который может иметь сложную внутреннюю структуру) в относительно простой базе данных. Итак, это хорошее предположение игнорировать внутреннюю структуру объектов.

...