【问题标题】:Is it possible to access a shared TPH column in EF Core without using intermediate classes?是否可以在不使用中间类的情况下访问 EF Core 中的共享 TPH 列?
【发布时间】:2021-07-06 11:49:58
【问题描述】:

在 EF Core TPH 设置中使用 shared columns 时,是否可以在投影期间访问共享列

    class Program
    {
        public static readonly ILoggerFactory MyLoggerFactory
            = LoggerFactory.Create(builder => { 
                builder.AddConsole(); 
            });

        static async Task Main(string[] args)
        {
            using (var context = new ClientContext())
            {
                context.Database.EnsureDeleted();
                context.Database.EnsureCreated();

                var actions = await context.Actions
                    .Select(a => new
                    {
                        Id = a.Id,

                        // this works - but really messy and complex in real world code
                        Message = (a as ActionA).Message ?? (a as ActionB).Message,

                        // this throws "Either the query source is not an entity type, or the specified property does not exist on the entity type."
                        // is there any other way to access the shared column Message?
                        // Message = EF.Property<string>(a, "Message"),
                    })
                    .ToListAsync();

                actions.ForEach(a => Console.WriteLine(a.Id + a.Message));
            }
        }

        public class ActionBase
        {
            public int Id { get; set; }

            // ... other shared properties
        }

        public class ActionA : ActionBase
        {
            // shared with B
            [Required]
            [Column("Message")]
            public string Message { get; set; }

            // ... other specific properties
        }

        public class ActionB : ActionBase
        {
            // shared with A
            [Required]
            [Column("Message")]
            public string Message { get; set; }

            // ... other specific properties
        }

        public class ActionC : ActionBase
        {
            public string SomethingElse { get; set; }

            // ... other specific properties
        }

        class ClientContext : DbContext
        {
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                // TO USE SQL
                //optionsBuilder
                //    .UseLoggerFactory(MyLoggerFactory)
                //    .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=TPHSharedColumn;Trusted_Connection=True;MultipleActiveResultSets=true;Connect Timeout=30")
                //    .EnableSensitiveDataLogging(false);
                // TO USE INMEMORY
                optionsBuilder
                   .UseLoggerFactory(MyLoggerFactory)
                   .UseInMemoryDatabase(Guid.NewGuid().ToString());
            }

            protected override void OnModelCreating(ModelBuilder builder)
            {
                base.OnModelCreating(builder);

                builder.Entity<ActionA>().HasData(new ActionA()
                {
                    Id = 1,
                    Message = "A"
                });
                builder.Entity<ActionB>().HasData(new ActionB()
                {
                    Id = 2,
                    Message = "B"
                });
                builder.Entity<ActionC>().HasData(new ActionC()
                {
                    Id = 3,
                    SomethingElse = "C"
                });
            }

            public DbSet<ActionBase> Actions { get; set; }
        }
    }

在这个简单的示例中,当然可以将 Message 移动到基类 - 但这可能会意外添加带有 MessageActionC,因为我需要删除 @ 987654326@属性。

我也知道我可以添加一个 ActionWithRequiredMessage 中间类来继承 ActionAActionB,但同样 - 在更复杂的现实世界示例中,这是不可行的,因为还有其他共享列和 C#不允许从多个类继承 - 而且 EF Core 似乎不喜欢为此使用接口。

我只是想找到一种方法来直接访问共享列 - 并在投影中使用它。

有人知道这是否可行吗?

【问题讨论】:

    标签: entity-framework-core table-per-hierarchy


    【解决方案1】:

    我找不到它的文档,但在 EF Core 5.x 中,您可以使用 any 具有映射到它的属性的派生实体访问共享列,例如所有这些工作

    Message = (a as ActionA).Message,
    
    Message = (a as ActionB).Message,
    
    Message = ((ActionA)a).Message,
    
    Message = ((ActionB)a).Message,
    

    【讨论】:

    • 我很想让它工作,但是当我在上面的示例代码(5.0.7 和 6.0-preview5)中尝试它时,它只会解析给定类型的 Message。因此,如果我使用 ActionA 类型,上述程序的输出将为 1A、2、3,如果我使用 ActionB,则输出为 1、2B、3(而不是想要的 1A、2B、3)。
    • 您是否尝试过使用真正的数据库提供商?完全按原样测试您的示例,EF Core 5.0.7/SqlServer,并使用上述任何方法获取 (1, "A"), (2, "B") 和 (3, null)。
    • 你是绝对正确的,它确实在 Sql Server 提供程序中工作。我总是先使用 InMemory 编写测试,所以我从来没有想过这可能是提供程序的问题。我将接受此答案并向 EF Core 团队提交问题,以查看 InMemory 是否可以与 SQL Server 实现功能对等,或者它是否是 SQL Server 中的错误,我想在我的代码中使用此解决方法之前了解它。 (这样我就不会被未来的“升级”击中..)
    猜你喜欢
    • 2017-12-14
    • 2021-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多