我们使用 SQL Server 和 EF Core 将 C# 对象映射到数据库。对象模型有两个抽象级别,如下所示:
public abstract class Animal
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Parrot : Animal
{
public bool CanSpeak { get; set; }
}
public abstract class Mammal : Animal
{
public bool HasPouch { get; set; }
}
public class Dog : Mammal
{
public bool HasTail { get; set; }
}
public class Cat : Mammal
{
public double MaxHeight { get; set; }
}
public class Horse : Mammal
{
public string Breed { get; set; }
public int RacingSpeed { get; set; }
}
DbContext
继承类的主要部分:
public DbSet<Animal> Animals { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>()
.ToTable("Animals")
.HasDiscriminator<string>("AnimalType")
.HasValue<Parrot>("Parrot")
.HasValue<Dog>("Dog")
.HasValue<Cat>("Cat")
.HasValue<Horse>("Horse");
// Additional configurations if needed
}
现在我将展示 3 种类型的 LINQ 查询,其中前两种可以工作,但最后一种会导致运行时错误(
IvalidOperationException
- 类似于:LINQ 表达式 'DbSet<Animal
>().OfType< Mammal
>()'无法翻译)。
此查询从层次结构的最基础 abstract 类中查询属性,该类在
Animals
表的 EF 配置中使用:
List<Animal> list = context.Animals
.Where(x => x.Name == "Max")
.ToList();
没问题。
这个需要从具体类查询属性并且工作得很好:
List<Animal> catsUnder50cm = context.Animals
.Where(x => x is Cat && (x as Cat).MaxHeight <= 50)
.ToList();
然而,对于这个,虽然编译正常,但我得到了上面提到的运行时错误:
List<Animal> marsupials = context.Animals
.Where(x => x is Mammal && (x as Mammal).HasPouch)
.ToList();
我认为这是因为
Mammal
是一个抽象类。我可以这样重写:
List<Animal> marsupials2 = context.Animals
.Where(x => (x is Dog && (x as Dog).HasPouch) || (x is Cat && (x as Cat).HasPouch) || (x is Horse && (x as Horse).HasPouch) )
.ToList();
但是正如您所看到的,查询同时变得非常复杂和丑陋。从
Mammal
继承的类越多,查询就越复杂。这正是我们的情况。
问题是:
是否可以在 LINQ 查询中使用不属于模型一部分的“中间”抽象类的属性?
如果没有,我们能否以某种方式将该抽象类“添加”到 EF 数据模型中?
Mammal
数据类型应映射到 Animals
的同一个表。这可能吗?
εὕρηκᾰ
我想我找到了解决方案。不过我不太喜欢它。
作为解决方法,我需要将“中间”抽象类映射到同一个表。并将其新属性 (
HasPouch
) 映射到同一列。
基本上,您应该在
DbContext
配置中执行类似的操作:
EntityTypeBuilder<Mammal> mammalBuilder = modelBuilder.Entity<Mammal>();
mammalBuilder.ToTable("Animals");
mammalBuilder.Property(x => x.HasPouch).HasColumnName("HasPouch");