简体   繁体   中英

EF Conditional Include by Entity Type

Please, suppose that architecture:

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; }
    ...
}

Now, my context:

public class MyContext : DbContext
{
    public DbSet<Mammal> Mammals { get; set; }
}

So, I want to make ONLY ONE sql query, and include (and load) all nested entities, something like:

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

I know that I can do that separately, but I don't want because I have many entities, and I need to minimize database requests.

I don't want use lazy loading because this will make many database requests also.

How to achieve that?

EF Core supports this in version 2.1 and higher. See the Github Issue here

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

This will include all the properties in one query.

Here is a link to the official documentation on this.

Can you try something like this:

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;
    }
}

Then in your db call:

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

Maybe this will work.

You could create a method to include a list of expressions. (or perhaps an extension method).

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;

}

And then, in your code:

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

Hope it helps!

The question is, how would you load properties of derived types, using only a DbSet of the base class and without lazy loading? I am afraid this is not possible.

You can however do this:

public class MyContext : DbContext
{
    public DbSet<Mammal> Mammals { get; set; }
    public DbSet<Dog>    Dogs    { get; set; }
    public DbSet<Bat>    Bats    { get; set; }
}

You could also simply implement one method, type-specific for each of your types

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");
    }
}

Then, you can simply call:

myContext.Dogs.IncludeExtraEntities();

And the method depending on your type will be called.

You're expecting too much from Include . The lambda expression you enter in Include is merely a string provider that looks far too intelligent. Under the hood its member expression is dissected to get the name of the property, that's all. The name is entered into the Include method that accepts a string parameter. That method does the actual work.

The lambda expression has to point to a navigation property on the type in the IQueryable . You can't do ...

myContext.Mammals.Include(d => d.Tail)

... because Tail is not a property of the base type. You can only do ...

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

The best you can get is something like

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 }

Since everything is translated into SQL you don't have to worry about null reference exceptions.

i faced problem like that and here how i solved it:

 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>());
    }

you can change classes as yours :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM