代码之家  ›  专栏  ›  技术社区  ›  rory.ap

为什么从DbQuery返回的实体框架核心poco必须是公共的?

  •  1
  • rory.ap  · 技术社区  · 7 年前

    我用的是 Microsoft.EntityFrameworkCore.DbQuery 从数据库中表值函数的结果返回POCOs。我注意到没有一个poco设置了它们的任何属性——它们都是默认值——只要它们的访问修饰符不是 public . 在我的例子中,POCO都是显式实现接口的,因此消费者不知道(或者不应该知道)POCO定义,所以我想将它们设置为 internal .

    为什么POCO必须公开?或者,有没有办法让他们不必公开?下面是一些示例代码来演示这一点:

    核心库.dll(.NET标准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; }
        }
    }
    

    数据访问.dll(.NET标准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核心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数据库中的Widget表

    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 命名空间到 公众的 ,现在poco被正确填充:

    enter image description here

    2 回复  |  直到 7 年前
        1
  •  1
  •   fdafadf    7 年前

    简而言之,这只是因为它是设计出来的。

    观察是任何物体的状态都可以分为两部分。第一部分是公共状态,第二部分是客体的内部状态。在设计数据存储技术时,应该决定要存储什么。只有一部分还是两部分?EF设计用于在相对简单的数据库中存储几乎任何对象(可能具有复杂的内部结构)。所以,忽略对象的内部结构是一个很好的假设。

        2
  •  0
  •   Gert Arnold    7 年前

    实体框架核心不按其默认约定映射内部属性。唯一的区别 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数据库 .

    推荐文章