Reputation: 105
The following is a small EF6 Program to demonstrate the issue.
public abstract class Base
{
public int Id { get; set; }
public abstract int TypeId { get; }
}
public class SubA : Base
{
public override int TypeId => 1;
}
public class SubAA : SubA
{
public override int TypeId => 2;
}
public class SubB : Base
{
public override int TypeId => 3;
}
public class SubC : Base
{
public override int TypeId => 4;
}
public class DevartContext : DbContext
{
public virtual DbSet<Base> Bases { get; set; }
public DevartContext()
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Base>()
.Map<SubA>(x => x.Requires(nameof(SubA.TypeId)).HasValue(1))
.Map<SubAA>(x => x.Requires(nameof(SubAA.TypeId)).HasValue(2))
.Map<SubB>(x => x.Requires(nameof(SubB.TypeId)).HasValue(3))
.Map<SubC>(x => x.Requires(nameof(SubC.TypeId)).HasValue(4));
}
}
public class Program
{
public static void Main(string[] args)
{
using (DevartContext ctx = new DevartContext())
{
// prevent model-changes from wrecking the test
ctx.Database.Delete();
ctx.Database.Create();
var result = ctx.Bases.Where(x => x.TypeId == 1);
// throws on materialization, why?
foreach (var entry in result)
{
Console.WriteLine(entry);
}
}
Console.ReadLine();
}
}
The jist of it is this: We have a TPH-Model with an explicitly configured Discriminator (TypeId
in this case). We then try to query a specific subtype using that TypeId because using the is
operator in our hypothetical example would also return SubAAs, not just SubAs.
I could obviously modify the above to something like Where(x => x is SubA && !(x is SubAA))
but this is obviously going to break as soon as I add SubAB, and automating this by building an exact-filter-linq-to-entities-helper-method is obviously really slow because that method has to do a decent amount of reflection. Not to mention that the generated SQL of the above is horrendous because EF/My SQL Provider does not optimize it properly.
Now trying to do the above results in a NotSupportedException
being thrown when the query gets materialized, which basically states that because TypeId
is not a member of the Entity, I cannot use it for filtering.
I went to look around for ways to circumvent this, but the best thing I could find was a snippet for automatically generating the Where(x => x is SubA && !(x is SubAA))
version to solve the problem, which is likely to be what I will have to do to get around this.
So my question is: Why does EntityFramework not support this?
Upvotes: 3
Views: 1107
Reputation: 17033
This soultion working excatly as you wish donot change anything ^^ "never change a running system" :)
You can use enum instead of integers, this give your code more type safety!
static void Main(string[] args)
{
using (DevartContext ctx = new DevartContext())
{
// prevent model-changes from wrecking the test
ctx.Database.Delete();
ctx.Database.Create();
ctx.Bases.Add(new SubA());
ctx.Bases.Add(new SubAA());
ctx.Bases.Add(new SubB());
ctx.SaveChanges();
var result = ctx.Bases.Where(x => x.TypeId == 1);
// throws on materialization, why?
foreach (var entry in result)
{
Console.WriteLine(entry);
}
}
Console.ReadLine();
}
public abstract class Base
{
public int Id { get; set; }
public virtual int TypeId { get; protected set; }
}
public class SubA : Base
{
public override int TypeId { get;protected set; } = 1;
}
public class SubAA : SubA
{
public override int TypeId { get; protected set; } = 2;
}
public class SubB : Base
{
public override int TypeId { get; protected set; } = 3;
}
public class SubC : Base
{
public override int TypeId { get; protected set; } = 4;
}
public class DevartContext : DbContext
{
public DbSet<Base> Bases { get; set; }
public DevartContext()
{
}
}
Result in DB:
Id TypeId Discriminator
1 1 SubA
2 2 SubAA
3 3 SubB
Upvotes: 1