EF按实体类型条件包含

8

请假设以下系统架构:

public class Mammal
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Dog : Mammal
{
    public int TailId { get; set; }
    public Tail Tail { get; set; }
}

public class Bat : Mammal
{
    public int WingId { get; set; }
    public Wing Wing { get; set; }
}

public class Buffalo : Mammal
{
    public virtual ICollection<Horn> Horns { get; set; }
}

public class Tail
{
    public int Id { get; set; }
    ...
}

public class Wing
{
    public int Id { get; set; }
    ...
}

public class Horn
{
    public int Id { get; set; }
    ...
}

现在,我的背景是这样的:
public class MyContext : DbContext
{
    public DbSet<Mammal> Mammals { get; set; }
}

因此,我希望只使用一个 SQL 查询,并包含(和加载)所有嵌套实体,类似于:

var query = myContext.Mammals
    .IncludeIfTypeIs<Dog>(d => d.Tail)
    .IncludeIfTypeIs<Bat>(b => b.Wing)
    .IncludeIfTypeIs<Buffalo>(b => b.Horns)
    ...
    ...
;

我知道我可以分别做到这一点,但我不想这样做,因为我有很多实体,并且我需要最小化数据库请求。
我不想使用延迟加载,因为这也会产生许多数据库请求。
如何实现这一点?

我认为这是不可能的,因为include操作是基于集合工作的,并根据此准备查询并执行SQL连接,但对于您的情况,似乎是在Mammal的单个实例上进行操作,我认为这完全违背了LINQ to SQL的目的。 - Jenish Rabadiya
谢谢您的评论,但我认为那样会违背linq to sql的初衷。如果没有这个功能,我就必须为每种动物类型创建一个查询,并分别调用它们,这将导致我的应用程序变得复杂和缓慢。在我的应用程序中,例如,我可以拥有不同类型的角,具有不同的属性,并且我还想有条件地加载这些属性... - Alexandre TRINDADE
你需要遍历所有的动物并检查它们的类型,只有当它是那种类型时才包含它吗? - Jenish Rabadiya
是的,但我的请求必须包括所有哺乳动物类型。我需要一个加载了所有嵌套属性的所有哺乳动物列表。当然,我可以迭代并加载这些属性,但会生成数百万个数据库请求... - Alexandre TRINDADE
6个回答

3

EF Core 在 2.1 及更高版本中支持此功能。请参阅 Github Issue 这里

var query = myContext.Mammals
    .Include(d => (d as Dog).Tail)
    .Include(b => (b as Bat).Wing)
    .Include(b => (b as Buffalo).Horns)

这将在一次查询中包含所有属性。

这里是关于此的官方文档链接。


0

我遇到了类似的问题,这是我解决它的方法:

 public IEnumerable<OrderLine> GetAllOrderLinesData()
    {
        var _StockOrderLines = appContext.OrderLines.OfType<StockOrderLine>()
            .Include(p => p.Order).Include(p => p.Product);

        var _AnnualOrderLines = appContext.OrderLines.OfType<AnnualOrderLine>()
            .Include(p => p.Order).Include(p => p.Customer);

        return _StockOrderLines.ToList<OrderLine>().Union<OrderLine>(_AnnualOrderLines.ToList<OrderLine>());
    }

你可以将类改成你自己的哦 :)

0
你可以尝试这样做:
public static class Extensions
{
    public static IQueryable<Mammal> IncludeExtraEntities<Mammal,T>(this IQueryable<Mammal> query, T derivedType) where T :Mammal
    {
        if (derivedType is Dog)
            return query.Include("Tail");
        if (derivedType is Bat)
            return query.Include("Wing");
        return query;
    }
}

然后在您的数据库调用中:

var query = myContext.Mammals.IncludeExtraEntities(typeof(Dog));

或许这个会起作用。


不行,因为我需要在同一个查询中加载所有类型的嵌套实体。 - Alexandre TRINDADE
1
但是你怎么获取类型呢?你是想获取查询中提取的任何“哺乳动物”的类型吗?所以如果其中一种哺乳动物是狗,就加载尾巴以获得一个结果,如果另一种是蝙蝠,则加载翅膀以获取该结果? - Alexander Derck
2
据我所知,无法预先加载不同派生类型的实体。 - Alexander Derck
是的,这正是我想要的... 我怀疑那是不可能的。 - Alexandre TRINDADE
你可以为每个派生类型执行一个查询,将它们预加载并将结果合并在一起。这样可以节省大量的数据库调用。 - Alexander Derck
是的,这已经是我的实际解决方案了... 我正在寻找一个更强大、更快速的解决方案... - Alexandre TRINDADE

0
你可以创建一个方法来包含一系列表达式(或者可能是扩展方法)。
public static IQueryable<Mammal> GetMammals(params Expression<Func<T, Object>>[] includeExps)
{
     var query = context.Mammals.AsQueryable();
     if (includeExps != null)
        query = includeExps.Aggregate(query, (current, exp) => current.Include(exp));

     return query;

}

然后,在你的代码中:

//Bat and Dog will be included here
var mammals = GetMammals(i => i.Bat, i => i.Dog);

希望能对你有所帮助!


这不起作用。我猜你的类型T是哺乳动物(你没有指定)。因此,如果我调用GetMammals,它会等待一个哺乳动物的属性(狗和蝙蝠是哺乳动物类型,而不是一个属性)。例如,如果我尝试做m => m.Wing,它不会编译,因为哺乳动物没有Wing属性(蝙蝠有此属性)。所以,条件包含必须在包含之前转换类型... - Alexandre TRINDADE
哦,我误解了。所以,我认为像@Alexander Derck说的那样是不可能的。 - Fabio Luz

0
问题是,如果只使用基类的DbSet且不使用延迟加载,您如何加载派生类型的属性?恐怕这是不可能的。
但是,您可以尝试以下方法:
public class MyContext : DbContext
{
    public DbSet<Mammal> Mammals { get; set; }
    public DbSet<Dog>    Dogs    { get; set; }
    public DbSet<Bat>    Bats    { get; set; }
}

你也可以为每个类型单独实现一个特定于类型的方法

public static class Extensions
{
    public static IQueryable<Dog> IncludeExtraEntities<Dog>(this IQueryable<Dog> query) where Dog : Mammal
    {
            return query.Include("Tail");
    }

    public static IQueryable<Bat> IncludeExtraEntities<Bat>(this IQueryable<Bat> query) where Bat: Mammal
    {
            return query.Include("Wing");
    }
}

然后,你只需要简单地调用:

myContext.Dogs.IncludeExtraEntities();

根据您的类型调用相应的方法。


谢谢,但那不是我想要的。我需要调用Mammals.IncludeForDog().IncludeForBat().IncludeForBuffalo()... - Alexandre TRINDADE
所以我将拥有一份动物列表,其中包括狗、蝙蝠和水牛,并且每个动物的属性都已正确加载。 - Alexandre TRINDADE

0
你对Include期望太高了。你在Include中输入的lambda表达式只是一个提供字符串的智能程度看起来很高的字符串提供者。在幕后,它的成员表达式被解剖以获取属性的名称,仅此而已。该名称被输入到接受字符串参数的Include方法中。该方法执行实际工作。 lambda表达式必须指向IQueryable类型中的导航属性。你不能做...
myContext.Mammals.Include(d => d.Tail)

...因为Tail不是基础类型的属性。你只能做...

myContext.Mammals.OfType<Dog>().Include(d => d.Tail)

你能得到的最好结果就是类似于这样的东西

from m in context.Mammals
let tail = (m as Dog).Tail
let wing = (m as Bat).Wing
let horns= (m as Buffalo).Horns
select new { Mammal = m, tail, wing, horns }

由于一切都被转换为SQL,您不必担心空引用异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接