Как фильтровать объекты "Включить" в инфраструктуру сущности?

Объекты:

    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 требует аргумента выражения с полиморфным типом результата, который совместим с аргументом типа".

Теперь, если вы заметите, я реализовал это двумя разными способами, во-первых, явной загрузкой связанных сущностей, во-вторых, с помощью двух разных запросов, мой вопрос был бы, есть ли способ загрузить мой граф объектов и отфильтровать юридические лица, которые я "включаю" только с одной поездкой из базы данных?

Ответы

Ответ 1

Существует два способа фильтрации, включая Entity.

  • Использование проекции (см. ответ @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+ Фильтр запроса включает

РЕДАКТИРОВАТЬ: ответить на подзапрос

Вы почти сделали это. Комнаты были включены и отфильтрованы, но вы не включили бронирование.

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))

Ответ 2

Обратите внимание, что в настоящее время невозможно отфильтровать, какие связанные объекты загружены. Включить всегда принесет все связанные объекты Msdn ссылки

Запросите эту функцию здесь

Чтобы отфильтровать дочернюю коллекцию, вы можете попробовать select для моделирования или анонимной проекции.

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

Подобные ответы

Ответ 3

Я думал о том, чтобы привнести новую перспективу в это. Даже если это не решит проблему, это может помочь вам. Используя AutoMapper, вы можете отфильтровать коллекции, прежде чем помещать их в целевой объект. Я настроил свое решение таким образом, чтобы все выполнялось в DTO перед любым действием, поэтому я использую AutoMapper в качестве фильтра для этих включений. Работает как шарм...