如何在实体框架中过滤“包含”实体?

Posted

技术标签:

【中文标题】如何在实体框架中过滤“包含”实体?【英文标题】:How to filter "Include" entities in entity framework? 【发布时间】:2017-01-30 21:47:43 【问题描述】:

实体:

    public class Room
    
        public Room()
        
            this.Reservations = new HashSet<Reservation>();
        

        public int Id  get; set; 

        public decimal Rate  get; set; 

        public int HotelId  get; set; 

        public virtual Hotel Hotel  get; set; 

        public virtual ICollection<Reservation> Reservations  get; set; 
    

    public class Hotel
    
        public Hotel()
        
            this.Rooms = new HashSet<Room>();
        

        public int Id  get; set; 

        public string Name  get; set; 

        public virtual ICollection<Room> Rooms  get; set; 
    

    public class Reservation
    
        public int Id  get; set; 

        public DateTime StartDate  get; set; 

        public DateTime EndDate  get; set; 

        public string ContactName  get; set; 

        public int RoomId  get; set; 

        public virtual Room Room  get; set; 
    

  public class ExecutiveSuite : Room
  
  

  public class DataContext : DbContext
    
        public DbSet<Hotel> Hotels  get; set; 

        public DbSet<Reservation> Reservations  get; set; 

        public DbSet<Room> Rooms  get; set; 

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        
            modelBuilder.Entity<Room>()
                .HasKey(r => r.Id)
                .HasRequired(r => r.Hotel)
                .WithMany(r => r.Rooms)
                .HasForeignKey(r => r.HotelId);

            modelBuilder.Entity<Hotel>()
                .HasKey(h => h.Id);

            modelBuilder.Entity<Room>()
                .HasMany(r => r.Reservations)
                .WithRequired(r => r.Room)
                .HasForeignKey(r => r.RoomId);

        
    

客户端代码(控制台应用):

static void Main(string[] args)
        
            // initialize and seed the database
            using (var context = new DataContext())
            
                var hotel = new Hotel  Name = "Grand Seasons Hotel" ;
                var r101 = new Room  Rate = 79.95M, Hotel = hotel ;
                var es201 = new ExecutiveSuite  Rate = 179.95M, Hotel = hotel ;
                var es301 = new ExecutiveSuite  Rate = 299.95M, Hotel = hotel ;

                var res1 = new Reservation
                
                    StartDate = DateTime.Parse("3/12/2010"),
                    EndDate = DateTime.Parse("3/14/2010"),
                    ContactName = "Roberta Jones",
                    Room = es301
                ;
                var res2 = new Reservation
                
                    StartDate = DateTime.Parse("1/18/2010"),
                    EndDate = DateTime.Parse("1/28/2010"),
                    ContactName = "Bill Meyers",
                    Room = es301
                ;
                var res3 = new Reservation
                
                    StartDate = DateTime.Parse("2/5/2010"),
                    EndDate = DateTime.Parse("2/6/2010"),
                    ContactName = "Robin Rosen",
                    Room = r101
                ;

                es301.Reservations.Add(res1);
                es301.Reservations.Add(res2);
                r101.Reservations.Add(res3);

                hotel.Rooms.Add(r101);
                hotel.Rooms.Add(es201);
                hotel.Rooms.Add(es301);

                context.Hotels.Add(hotel);
                context.SaveChanges();
            

            using (var context = new DataContext())
            
                context.Configuration.LazyLoadingEnabled = false;
                // Assume we have an instance of hotel
                var hotel = context.Hotels.First();

                // Explicit loading with Load() provides opportunity to filter related data 
                // obtained from the Include() method 
                context.Entry(hotel)
                       .Collection(x => x.Rooms)
                       .Query()
                       .Include(y => y.Reservations)
                       .Where(y => y is ExecutiveSuite && y.Reservations.Any())
                       .Load();

                Console.WriteLine("Executive Suites for 0 with reservations", hotel.Name);

                foreach (var room in hotel.Rooms)
                
                    Console.WriteLine("\nExecutive Suite 0 is 1 per night", room.Id,
                                      room.Rate.ToString("C"));
                    Console.WriteLine("Current reservations are:");
                    foreach (var res in room.Reservations.OrderBy(r => r.StartDate))
                    
                        Console.WriteLine("\t0 thru 1 (2)", res.StartDate.ToShortDateString(),
                                          res.EndDate.ToShortDateString(), res.ContactName);
                    
                
            

            Console.WriteLine("Press <enter> to continue...");
            Console.ReadLine();
        



using ( var context = new DataContext() )


        //context.Configuration.LazyLoadingEnabled = false;

        // Assume we have an instance of hotel
        var hotel = context.Hotels.First();
        var rooms = context.Rooms.Include( r => r.Reservations ).Where( r => r is ExecutiveSuite && r.Reservations.Any() ).Where( r => r.Hotel.Id == hotel.Id );
        Console.WriteLine( "Executive Suites for 0 with reservations", hotel.Name );

        foreach ( var room in hotel.Rooms )
        
           Console.WriteLine( "\nExecutive Suite 0 is 1 per night", room.Id,
                             room.Rate.ToString( "C" ) );
           Console.WriteLine( "Current reservations are:" );
           foreach ( var res in room.Reservations.OrderBy( r => r.StartDate ) )
           
              Console.WriteLine( "\t0 thru 1 (2)", res.StartDate.ToShortDateString(),
                                res.EndDate.ToShortDateString(), res.ContactName );
           
        
     

我尝试将其投影并放入匿名对象中:

       var hotel = context.Hotels.Select(h =>
        new 
           
            Id = h.Id,
            Name = h.Name,
            Rooms = h.Rooms.Where(r => r.Reservations is ExecutiveSuite && r.Reservations.Any())
        ).First();

但我得到一个例外:“DbIsOfExpression 需要一个表达式参数,它的多态结果类型与类型参数兼容。”

现在,如果您注意到,我以两种不同的方式实现它,首先是通过显式加载相关实体,其次是通过两个不同的查询,我的问题是,有没有一种方法可以加载我的对象图并过滤我“包含”的实体,只需一次从数据库中访问?

【问题讨论】:

在这两个示例中,只有 2 个对数据库的查询。第一个用于酒店,然后用于客房和预订。你还想要什么? 为什么不在include Include() 中的所有内容?比如:context.Hotels.Include("Rooms.Reservations")? @sachin 包括其他相关实体,然后尽可能使用单次访问数据库来过滤/排序这些相关实体。 @haim770 包括其他相关实体,然后尽可能使用单次访问数据库来过滤/排序这些相关实体。 @RandelRamirez,试试看 【参考方案1】:

有两种过滤包含实体的方法。

使用投影(参见@Eldho 答案) 使用第三方库

免责声明:我是项目的所有者Entity Framework Plus

EF+ Query IncludeFilter 允许轻松过滤包含的实体。

context.Entry(hotel)
       .Collection(x => x.Rooms)
       .Query()
       .IncludeFilter(y => y.Reservations
                            .Where(z => z is ExecutiveSuite && z.Reservations.Any())
       .Load();

在引擎盖下,图书馆确实做了一个投影。

维基:EF+ Query Include Filter

编辑:回答子问题

你几乎做到了。房间已包含并过滤,但您未包含预订。

var hotel = context.Hotels
    // Include only executive suite with a reservation
    .IncludeFilter(x => x.Rooms.Where(y => y is ExecutiveSuite && y.Reservations.Any()))
    // Include only reservation from executive suite
    .IncludeFilter(x => x.Rooms.Where(y => y is ExecutiveSuite).Select(z => z.Reservations))
    .First();

编辑:回答评论

我们如何使用包含过滤器包含多级属性

您可以通过指定每个路径(每个 IncludeFilter 一个)来包含多级

所以qry.Include("Rooms.Hotel")变成:

qry.IncludeFilter(x => x.Rooms)
   .IncludeFilter(x => x.Rooms.Select(y => y.Hotel))

编辑:回答评论

EF+ 是否支持 dotnet 5.0?

是的,它支持 dotnet 5.0 和 EF Core 5.0。但是,对于 IncludeFilter,您还应该直接在 EF Core 5 中查看已过滤的 include:https://www.learnentityframeworkcore5.com/whats-new-in-ef-core-5/filtered-included

【讨论】:

我尝试在没有显式加载的情况下使用它,但这似乎不起作用。 => context.hotels.IncludeFilter(h => h.rooms.Where(x is ExecutiveSuite && x.Reservations.Any())) 为什么会这样?我得到了不同的结果。我用对了吗 @RandelRamirez,我刚刚做了一个测试,一切似乎都正常。你得到哪个结果?此功能的一个限制是始终包含先前加载的相关实体(即使它不满足 IncludeFilter 谓词)。如果你愿意,你也可以在我们的 GitHub 论坛上报告这个问题,这样比使用 Stack Overflow 更容易跟踪:github.com/zzzprojects/EntityFramework-Plus/issues “没有显式加载”是什么意思?您需要使用 .Load() 或 .ToList() (或任何其他 LINQ 即时方法) 我这里有一个 repo,如果你有时间你可以看看它,看看我想要得到的结果。因为我可能以错误的方式使用您的 api,因此无需提交错误。谢谢! :) github.com/randelramirez/EF6_LoadingEntitiesAndNavigation 项目名称是 FilteringAndOrderingRelatedEntities 让我知道新答案是否有效。我这边也得到了同样的结果。【参考方案2】:

请注意,目前无法过滤加载了哪些相关实体。包含将始终引入所有相关实体Msdn reference

请求此功能here

为了过滤子集合你可以尝试select那个来建模或者匿名投影。

var anonymousProjection = dbContext.CustomerEntity
                                 .Where(c => ! c.IsDeleted)
                                 .Select(x=> new 
                                  
                                       customers = x,
                                       orders = x.Orders.Where(h=>h.IsDeleted)
                                  ).ToList();

Similar answers

【讨论】:

我进行了编辑,当我尝试将其投影到匿名对象中时出现异常 尝试强类型,例如映射到 dto 或 viewmodel 像这样***.com/a/12410349/1876572【参考方案3】:

不惜一切代价升级到 EF 5.0+ 并利用 EF 5.0+ 急切加载功能,特别是 Microsoft Docs Eager Loading - Filtered Include

例子:

context.hotel.Include(y =&gt; y.Reservations.Where(resy=&gt;resy.type==ExecutiveSuite &amp;&amp; resy.Any())).ToListAsync();

【讨论】:

在撰写本文时(2022 年),这是现代应用程序的正确答案【参考方案4】:

我正在考虑为此带来新的视角。 尽管这不能解决问题,但它可能会对您有所帮助。 使用 AutoMapper,您可以在将集合放入目标对象之前对其进行过滤。我已经设置了我的解决方案,在任何操作之前将所有内容都映射到 DTO 中,因此我使用 AutoMapper 作为这些包含的过滤器。 像魅力一样工作......

【讨论】:

很遗憾你没有包含一些示例代码。 嗯,好吧,AutoMapper 会自动这样工作。如果您使用 ProjectTo(我个人认为,如果您要直接访问 DB,您真的应该使用它)然后 AutoMapper 会生成底层表达式树并得到解决。因此,当您构建 AutoMapper 配置文件时,您基本上可以在那里“映射”它,然后就可以了。它在基本的 AutoMapper 示例中进行了描述。

以上是关于如何在实体框架中过滤“包含”实体?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Asp.net MVC 和实体框架中分页时应用过滤器?

您如何在实体框架的数据库级别执行复杂的“或”过滤器?

使用实体框架过滤BindingSource

实体框架 where 子句从特定列过滤

如何动态构建实体框架查询?

如果不支持包含,您如何在 LINQ to Entities(实体框架)中执行 SQL 样式的“IN”语句?