【问题标题】:Entity Framework Core table filters (where a table has more than one use)Entity Framework Core 表过滤器(表有多个用途)
【发布时间】:2019-08-08 11:28:27
【问题描述】:

我继承了一个 SQL Server 数据库,其中原始开发人员使用单个表来表示所有“查找”类型,而不是为每个类型指定一个表...

create table Lookup (
    LookupID int,
    LookupType int,
    LookupKey int,
    LookupValue varchar(50)
)

然后使用该表(例如)根据 LookupType 提供不同的列表,因此您会看到诸如...的数据。

ID     Type     Key     Value
1      1        1       Mr
2      1        2       Mrs
3      1        3       Miss
4      2        1       Dog
5      2        2       Cat
6      2        3       Hamster

我需要将此表与 Entity Framework Core 一起使用,因为我希望能够在查询数据时拉回查找值。以下表为例...

create table Customer (
    CustomerID int,
    CustomerTitleID int, <- LookupType = 1
    PetTypeID int -- LookupType = 2
)

数据看起来像......

ID     TitleID     PetTypeID
1      1           1

我可以定义一个“查找”类...

public class Lookup {
    public int LookupID {get; set;}
    public int LookupTypeID {get; set;}
    public int LookupKey {get; set;}
    public string LookupValue {get; set;}
} 

我可以定义一个“客户”类...

public class Customer {
    public int CustomerID {get; set;}
    public Lookup CustomerTitle {get; set;}
    public int CustomerTitleID {get; set;}
    public Lookup PetType {get; set;}
    public int PetTypeID {get; set;}
}

问题是,虽然(在 DbContext.OnModelCreating 中)我可以为客户/查找关系指定一个“主键”...

entity<Customer>().HasOne<Lookup>(c => c.CustomerTitle).WithMany().WithForeignKey(c => c.CustomerTitleID).WithPrincipleKey(l => l.LookupKey);
entity<Customer>().HasOne<Lookup>(c => c.PetType).WithMany().WithForeignKey(c => c.PetTypeID).WithPrincipleKey(l => l.LookupKey);

我找不到任何方法为每个“查找”类设置过滤器以通过“查找类型ID”来限制它。

我已尝试创建自定义“PetType”类并将其与“查找”以及过滤器(在 DbContext.OnModelCreating 中)关联...

entity.HasQueryFilter(lookup => lookup.LookupType == 2);

但是 EF 不喜欢将多个实体类型与表关联,除非实体类型也相关(“PetType”必须从“Lookup”继承)。

然后我从“Lookup”继承了那个自定义的“PetType”类并尝试了相同的过滤器......

entity.HasQueryFilter(petType=> petType.LookupType == 2);

但 EF 只允许在根级别进行这样的过滤器。

我也尝试过使用视图,但是虽然 DbSet 实体可以是 DbQuery 实体的子属性,但它似乎不能反过来工作。

我错过了另一种方式吗?我希望能够实现的最终结果是..

from customer in dbContext.Customers
    .Select new
    {
        customer.CustomerID,
        Title = customer.CustomerTitle.LookupValue,
        PetType = customer.PetType.LookupValue
    }

并让 EF 自动对每个 Lookup 应用过滤器(显然由我指定),以便选择正确的行。

【问题讨论】:

    标签: entity-framework ef-core-2.2


    【解决方案1】:

    您实际上可以通过在 EF Core 中创建 TPH 继承层次结构来解决此“设计”。例如:

    using Microsoft.EntityFrameworkCore;
    using System;
    using System.Data;
    using System.Linq;
    
    namespace EfCoreTest
    {
    
        public abstract class Lookup
        {
            public int LookupID { get; set; }
            public int LookupTypeID { get; set; }
            public int Key { get; set; }
            public string Value { get; set; }
        }
    
        public class CustomerTitle : Lookup
        {
    
        }
        public class PetType : Lookup
        {
    
        }
        public class Customer
        {
            public int CustomerID { get; set; }
            public CustomerTitle CustomerTitle { get; set; }
            public int CustomerTitleID { get; set; }
            public PetType PetType { get; set; }
            public int PetTypeID { get; set; }
        }
    
    
        public class Db : DbContext
        {
            readonly string connectionString;
    
            public DbSet<Customer> Customers { get; set; }
            public DbSet<Lookup> Lookup { get; set; }
            public DbSet<CustomerTitle> CustomerTitles { get; set; }
            public DbSet<PetType> PetTypes { get; set; }
    
            public Db(): this("server=.;database=EfCoreTest;Integrated Security=true")
            {
    
            }
            public Db(string connectionString)
            {
                this.connectionString = connectionString;
            }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer(connectionString);
                base.OnConfiguring(optionsBuilder);
            }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Lookup>()
                    .HasAlternateKey(e => new { e.LookupTypeID, e.Key });
    
                modelBuilder.Entity<Lookup>()
                    .HasDiscriminator(e => e.LookupTypeID);
    
                modelBuilder.Entity<CustomerTitle>()
                    .HasDiscriminator()
                    .HasValue(1);
                modelBuilder.Entity<PetType>()
                    .HasDiscriminator()
                    .HasValue(2);
    
                var fks = from et in modelBuilder.Model.GetEntityTypes()
                          from fk in et.GetForeignKeys()
                          where typeof(Lookup).IsAssignableFrom(fk.PrincipalEntityType.ClrType)
                          select fk;
                foreach (var fk in fks)
                {
                    fk.DeleteBehavior = DeleteBehavior.Restrict;
                }
    
                base.OnModelCreating(modelBuilder);
            }
        }
    
    
    
        class Program
        {
            static void Main(string[] args)
            {
    
    
                using (var db = new Db())
                {
                    db.Database.EnsureDeleted();
                    db.Database.EnsureCreated();
    
                    var Mr = new CustomerTitle() { Key = 1, Value = "Mr" };
                    var Mrs = new CustomerTitle() { Key = 2, Value = "Mrs" };
                    var Miss = new CustomerTitle() { Key = 3, Value = "Miss" };
                    db.CustomerTitles.AddRange( new []{ Mr,Mrs,Miss});
    
                    var Dog = new PetType() { Key = 1, Value = "Dog" };
                    var Cat = new PetType() { Key = 2, Value = "Cat" };
                    var Hamster = new PetType() { Key = 3, Value = "Hamster" };
                    db.PetTypes.AddRange(new[] { Dog,Cat,Hamster });
    
                    db.SaveChanges();
                }
    
                using (var db = new Db())
                {
                    var titles = db.CustomerTitles.ToDictionary(t => t.Value);
                    var petTypes = db.PetTypes.ToDictionary(t => t.Value);
    
                    var cust = new Customer();
                    cust.CustomerTitle = titles["Mr"];
                    cust.PetType = petTypes["Hamster"];
    
                    db.SaveChanges();
                }
    
    
                Console.WriteLine("Hit any key to exit");
                Console.ReadKey();
    
    
            }
        }
    }
    

    或者,您可以为查找创建视图,例如:

    create or alter view CustomerTitle
    as
    select [Key] CustomerTitleId, Value
    from Lookup
    where LookupTypeID = 1
    

    【讨论】:

    • 谢谢,但我收到 System.InvalidOperationException:“无法为实体类型“CustomerTitle”设置鉴别器属性,因为它不是继承层次结构的根。” (如果重要的话,我使用的是 EF Core 2.2.4)。
    • 我的错;在 CustomerTitle 类的 HasDiscriminator 中包含属性名称。更正了,但现在我得到以下信息: System.InvalidOperationException: 'Cannot set discriminator value '2' for discriminator property 'Discriminator' 因为它不能分配给类型为 'System.String' 的属性。';即使属性是 int 并且我设置的值是 int。
    • 尝试将该示例粘贴到一个空项目中并在集成到现有项目之前运行它。
    • 刚刚完成(认为这是我的错误);一切都如您所愿。不确定我是否理解 EF 如何知道查找“Key”而不是“LookupID” - 这是基于约定的配置,如果是的话,是否有一些文档可以指向我?但仍然无法让它在我自己的项目中运行 - 我会在知道原因后尽快回复您。
    • 它正在查找 LookupID,而不是 Key。例如 Customer.CustomerTitleID -> Lookup.LookupID。
    猜你喜欢
    • 1970-01-01
    • 2022-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-18
    • 2017-02-17
    相关资源
    最近更新 更多