Entities:
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);
}
}
The client code(console app):
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 );
}
}
}
I tried projecting and putting it in an anonymous object:
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();
but I get an exception: "DbIsOfExpression requires an expression argument with a polymorphic result type that is compatible with the type argument."
Now, if you would notice, I implemented it in two different ways, first was by explicitly loading the related entities, second was by having two different queries, my question would be, is there a way I can load my object graph and filter the entities I "Include" with just a single trip from the database?
To filter data, use linq. You can not use Filter property of BindingSource when the underlying list is BindingList<T> ; Only underlying lists that implement the IBindingListView interface support filtering. To remove filter, just set the data source of your binding source to the local storage of your entities again.
Entity Framework Classic Include The Include method lets you add related entities to the query result. In EF Classic, the Include method no longer returns an IQueryable but instead an IncludeDbQuery that allows you to chain multiple related objects to the query result by using the AlsoInclude and ThenInclude methods.
Entity Framework supports three ways to load related data - eager loading, lazy loading and explicit loading. The techniques shown in this topic apply equally to models created with Code First and the EF Designer.
As a performance measure, Include() allows you to indicate which related entities should be read from the database as part of the same query.
There are two ways to filter include Entity.
Disclaimer: I'm the owner of the project Entity Framework Plus
The EF+ Query IncludeFilter allows to easily filter included entities.
context.Entry(hotel)
.Collection(x => x.Rooms)
.Query()
.IncludeFilter(y => y.Reservations
.Where(z => z is ExecutiveSuite && z.Reservations.Any())
.Load();
Under the hood, the library does exactly a projection.
Wiki: EF+ Query Include Filter
EDIT: Answer subquestion
You almost did it. The rooms were included and filtered, but you didn't include the reservations.
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();
EDIT: Answer Comment
How can we include multilevel properties with include filter
You can include multilevel by specifying each path (one per IncludeFilter)
So qry.Include("Rooms.Hotel")
become:
qry.IncludeFilter(x => x.Rooms)
.IncludeFilter(x => x.Rooms.Select(y => y.Hotel))
EDIT: Answer Comment
does EF+ have dotnet 5.0 support?
Yes, it supports dotnet 5.0 and EF Core 5.0. However, for IncludeFilter, you should also look at the filtered include built-in directly in EF Core 5: https://www.learnentityframeworkcore5.com/whats-new-in-ef-core-5/filtered-included
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With