如何在实体框架中过滤“包含”实体?
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 => y.Reservations.Where(resy=>resy.type==ExecutiveSuite && resy.Any())).ToListAsync();
【讨论】:
在撰写本文时(2022 年),这是现代应用程序的正确答案【参考方案4】:我正在考虑为此带来新的视角。 尽管这不能解决问题,但它可能会对您有所帮助。 使用 AutoMapper,您可以在将集合放入目标对象之前对其进行过滤。我已经设置了我的解决方案,在任何操作之前将所有内容都映射到 DTO 中,因此我使用 AutoMapper 作为这些包含的过滤器。 像魅力一样工作......
【讨论】:
很遗憾你没有包含一些示例代码。 嗯,好吧,AutoMapper 会自动这样工作。如果您使用 ProjectTo(我个人认为,如果您要直接访问 DB,您真的应该使用它)然后 AutoMapper 会生成底层表达式树并得到解决。因此,当您构建 AutoMapper 配置文件时,您基本上可以在那里“映射”它,然后就可以了。它在基本的 AutoMapper 示例中进行了描述。以上是关于如何在实体框架中过滤“包含”实体?的主要内容,如果未能解决你的问题,请参考以下文章