EF: Include with where clause

前端 未结 4 1371
执念已碎
执念已碎 2020-11-21 07:45

As the title suggest I am looking for a way to do a where clause in combination with an include.

Here is my situations: I am responsible for the support of a large a

相关标签:
4条回答
  • 2020-11-21 08:27

    Disclaimer: I'm the owner of the project Entity Framework Plus

    EF+ Query IncludeFilter feature allows filtering related entities.

    var buses = Context.Busses
                       .Where(b => b.IsDriving)
                       .IncludeFilter(x => x.Passengers.Where(p => p.Awake))
                       .ToList();
    

    Wiki: EF+ Query IncludeFilter

    0 讨论(0)
  • 2020-11-21 08:40

    For any one still curious about this. there builtin functionality for doing this in EF Core. using .Any inside of a where clause so the code would like similar to something like this

    <!-- language: language: c# -->
    
    _ctx.Parent
        .Include(t => t.Children)
        .Where(t => t.Children.Any(t => /* Expression here */))
    
    0 讨论(0)
  • 2020-11-21 08:44

    This feature has now been added to Entity Framework core 5. For earlier versions you need a work-around (note that EF6 is an earlier version).

    Entity Framework 6 work-around

    In EF6, a work-around is to first query the required objects in a projection (new) and let relationship fixup do its job.

    You can query the required objects by

    Context.Configuration.LazyLoadingEnabled = false;
    // Or: Context.Configuration.ProxyCreationEnabled = false;
    var buses = Context.Busses.Where(b => b.IsDriving)
                .Select(b => new 
                             { 
                                 b,
                                 Passengers = b.Passengers
                                               .Where(p => p.Awake)
                             })
                .AsEnumerable()
                .Select(x => x.b)
                .ToList();
    

    What happens here is that you first fetch the driving buses and awake passengers from the database. Then, AsEnumerable() switches from LINQ to Entities to LINQ to objects, which means that the buses and passengers will be materialized and then processed in memory. This is important because without it EF will only materialize the final projection, Select(x => x.b), not the passengers.

    Now EF has this feature relationship fixup that takes care of setting all associations between objects that are materialized in the context. This means that for each Bus now only its awake passengers are loaded.

    When you get the collection of buses by ToList you have the buses with the passengers you want and you can map them with AutoMapper.

    This only works when lazy loading is disabled. Otherwise EF will lazy load all passengers for each bus when the passengers are accessed during the conversion to DTOs.

    There are two ways to disable lazy loading. Disabling LazyLoadingEnabled will re-activate lazy loading when it is enabled again. Disabling ProxyCreationEnabled will create entities that aren't capable of lazy loading themselves, so they won't start lazy loading after ProxyCreationEnabled is enabled again. This may be the best choice when the context lives longer than just this single query.

    But... many-to-many

    As said, this work-around relies on relationship fixup. However, as explained here by Slauma, relationship fixup doesn't work with many-to-many associations. If Bus-Passenger is many-to-many, the only thing you can do is fix it yourself:

    Context.Configuration.LazyLoadingEnabled = false;
    // Or: Context.Configuration.ProxyCreationEnabled = false;
    var bTemp = Context.Busses.Where(b => b.IsDriving)
                .Select(b => new 
                             { 
                                 b,
                                 Passengers = b.Passengers
                                               .Where(p => p.Awake)
                             })
                .ToList();
    foreach(x in bTemp)
    {
        x.b.Pasengers = x.Passengers;
    }
    var busses = bTemp.Select(x => x.b).ToList();
    

    ...and the whole thing becomes even less appealing.

    Third-party tools

    There is a library, EntityFramework.DynamicFilters that makes this a lot easier. It allows you to define global filters for entities, that will subsequently be applied any time the entity is queried. In your case this could look like:

    modelBuilder.Filter("Awake", (Person p) => p.Awake, true);
    

    Now if you do...

    Context.Busses.Where(b => b.IsDriving)
           .Include(b => b.People)
    

    ...you'll see that the filter is applied to the included collection.

    You can also enable/disable filters, so you have control over when they are applied. I think this is a very neat library.

    There is a similar library from the maker of AutoMapper: EntityFramework.Filters

    Entity Framework core work-around

    Since version 2.0.0, EF-core has global query filters. These can be used to set predefined filter on entities that are to be included. Of course that doesn't offer the same flexibility as filtering Include on the fly. Although global query filters are a great feature, so far the limitation is that a filter can't contain references to navigation properties, only to the root entity of a query. Hopefully in later version these filters will attain wider usage.

    0 讨论(0)
  • 2020-11-21 08:48

    In my case the Include was an ICollection, and also did not want to return them, I just needed to get the main entities but filtered by the referenced entity. (in other words, Included entity), what I ended up doing is this. This will return list of Initiatives but filtered by InitiativeYears

    return await _context.Initiatives
                    .Where(x => x.InitiativeYears
                        .Any(y => y.Year == 2020 && y.InitiativeId == x.Id))
                    .ToListAsync();
    

    Here the Initiatives and the InitiativeYears has following relationship.

    public class Initiative
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public ICollection<InitiativeYear> InitiativeYears { get; set; }
    }
    
    public class InitiativeYear
    {
        public int Year { get; set; }
        public int InitiativeId { get; set; }
        public Initiative Initiative { get; set; }
    }
    
    0 讨论(0)
提交回复
热议问题