Entity Framework 核心 table 过滤器(其中 table 有不止一种用途)
Entity Framework Core table filters (where a table has more than one use)
我继承了一个 SQL 服务器数据库,其中原始开发人员使用单个 table 来表示所有 "lookup" 类型,而不是为每个类型指定一个 table。 ..
create table Lookup (
LookupID int,
LookupType int,
LookupKey int,
LookupValue varchar(50)
)
然后使用 table(例如)根据 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
我需要将此 table 与 Entity Framework 核心一起使用,因为我希望能够在查询数据时撤回查找值。以下面的table为例...
create table Customer (
CustomerID int,
CustomerTitleID int, <- LookupType = 1
PetTypeID int -- LookupType = 2
)
数据看起来像...
ID TitleID PetTypeID
1 1 1
我可以定义一个"Lookup"class...
public class Lookup {
public int LookupID {get; set;}
public int LookupTypeID {get; set;}
public int LookupKey {get; set;}
public string LookupValue {get; set;}
}
我可以定义一个"Customer" class...
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 中)我可以为 Customer/Lookup 关系指定一个 "Principle Key"...
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);
我找不到任何方法来为每个 "Lookup" class 设置过滤器以将其限制为 "LookupTypeID"。
我已经尝试创建自定义 "PetType" class 并将其与 "Lookup" 以及过滤器(在 DbContext.OnModelCreating 中)相关联...
entity.HasQueryFilter(lookup => lookup.LookupType == 2);
但是 EF 不喜欢多个实体类型与 table 关联,除非实体类型也相关("PetType" 必须继承自 "Lookup")。
然后我从 "Lookup" 继承了自定义 "PetType" class 并尝试了相同的过滤器...
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 自动对每个查找(显然由我指定)应用过滤器,以便选择正确的行。
您实际上可以通过在 EF Core 中创建 TPH 继承层次结构来解决这个问题 "design"。例如:
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
我继承了一个 SQL 服务器数据库,其中原始开发人员使用单个 table 来表示所有 "lookup" 类型,而不是为每个类型指定一个 table。 ..
create table Lookup (
LookupID int,
LookupType int,
LookupKey int,
LookupValue varchar(50)
)
然后使用 table(例如)根据 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
我需要将此 table 与 Entity Framework 核心一起使用,因为我希望能够在查询数据时撤回查找值。以下面的table为例...
create table Customer (
CustomerID int,
CustomerTitleID int, <- LookupType = 1
PetTypeID int -- LookupType = 2
)
数据看起来像...
ID TitleID PetTypeID
1 1 1
我可以定义一个"Lookup"class...
public class Lookup {
public int LookupID {get; set;}
public int LookupTypeID {get; set;}
public int LookupKey {get; set;}
public string LookupValue {get; set;}
}
我可以定义一个"Customer" class...
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 中)我可以为 Customer/Lookup 关系指定一个 "Principle Key"...
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);
我找不到任何方法来为每个 "Lookup" class 设置过滤器以将其限制为 "LookupTypeID"。
我已经尝试创建自定义 "PetType" class 并将其与 "Lookup" 以及过滤器(在 DbContext.OnModelCreating 中)相关联...
entity.HasQueryFilter(lookup => lookup.LookupType == 2);
但是 EF 不喜欢多个实体类型与 table 关联,除非实体类型也相关("PetType" 必须继承自 "Lookup")。
然后我从 "Lookup" 继承了自定义 "PetType" class 并尝试了相同的过滤器...
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 自动对每个查找(显然由我指定)应用过滤器,以便选择正确的行。
您实际上可以通过在 EF Core 中创建 TPH 继承层次结构来解决这个问题 "design"。例如:
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