如何在Entity Framework中筛选“包含(Include)”的实体?

27

实体:

    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("\t{0} 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( "\t{0} 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个查询与数据库交互。第一个查询是关于酒店的,然后是房间和预订信息。你还想要什么? - sachin
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - haim770
@sachin 如果可能的话,包括其他相关实体,然后使用单次数据库查询来过滤/排序这些相关实体。 - Randel Ramirez
@haim770 如果可能的话,包括其他相关实体,然后使用单次数据库查询来过滤/排序这些相关实体。 - Randel Ramirez
@haim770,那会加载所有相关实体,这与实际目标相反。我已经进行了编辑。不过还是谢谢你的回答。 :) - Randel Ramirez
显示剩余2条评论
4个回答

25

筛选Entity有两种方法。

  • 使用投影(请见@Eldho的回答)
  • 使用第三方库

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

EF+查询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中直接内置的过滤包含功能:https://www.learnentityframeworkcore5.com/whats-new-in-ef-core-5/filtered-included


1
@RandelRamirez,我刚刚进行了一次测试,一切似乎都正常。你得到了什么结果?这个功能的一个限制是之前加载的相关实体将始终包括在内(即使它不满足IncludeFilter谓词)。如果您愿意,您也可以在我们的GitHub论坛上报告此问题,以便更容易地跟踪,而不是使用Stack Overflow:https://github.com/zzzprojects/EntityFramework-Plus/issues - Jonathan Magnan
“without explicitly loading”是什么意思?你需要使用.Load()或者.ToList()(或其他任何LINQ立即方法)。 - Jonathan Magnan
我这里有一个代码库,如果您有时间,可以查看一下并查看我想要的结果。因为我可能没有正确使用您的api,所以没有必要提交错误报告。谢谢! :) https://github.com/randelramirez/EF6_LoadingEntitiesAndNavigation 项目名称为FilteringAndOrderingRelatedEntities。 - Randel Ramirez
@JonathanMagnan嘿John,EF+是否支持.NET 5.0?特别是包含筛选器对我来说很有趣。 - WBuck
这对我来说是完美的解决方案...直到我发现你不能在IncludeFilter()中使用投影-Select()。这使得它对我来说无法启动。:( - xanadont
显示剩余6条评论

16
注意目前无法过滤加载哪些相关实体。包含将始终带入所有相关实体。Msdn参考 在此处请求这个功能 here 为了过滤子集合,可以尝试使用select来对模型或匿名投影进行筛选。
var anonymousProjection = dbContext.CustomerEntity
                                 .Where(c => ! c.IsDeleted)
                                 .Select(x=> new 
                                  {
                                       customers = x,
                                       orders = x.Orders.Where(h=>h.IsDeleted)
                                  }).ToList();

类似答案


我进行了编辑,但在尝试将其投影到匿名对象中时出现了异常。 - Randel Ramirez
尝试使用强类型,例如将其映射到DTO或ViewModel,就像这个链接中所示:http://stackoverflow.com/a/12410349/1876572 - Eldho

12

不惜一切代价升级到EF 5.0+,并利用EF 5.0+的急切加载功能,特别是Microsoft Docs急切加载 - 过滤包含

示例:

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


6
在撰写本文时(2022年),这是现代应用程序的正确答案。 - Charlino
“不惜一切代价”这个词汇很少是企业的正确答案。企业必须考虑到许多因素,成本是其中一个重要因素。 - Kabua

-1

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


很遗憾你没有包含一些示例代码。 - kipusoep
嗯,好的,AutoMapper会自动地处理这个。如果你使用ProjectTo(我的个人意见是,如果你直接访问数据库,你真的应该使用它),那么AutoMapper会生成底层表达式树并解析它。因此,在构建AutoMapper配置文件时,你基本上只需要在那里“映射”它,然后就可以了。这在基本的AutoMapper示例中有描述。 - Cubelaster

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