Справка по синтаксису Linq to entities при наличии двух таблиц

#entity-framework #linq #linq-to-entities

#entity-framework #linq #linq-to-entities

Вопрос:

Ожидаемый результат:

Я хотел бы иметь IQueryable<Vehicle> , который содержит транспортные средства, которые никому не назначены.

Данные:

У меня есть две таблицы:

Транспортное средство

  • идентификатор int
  • имя nvarchar
  • Дата и время удалены

AssignedToVehicle

  • int идентификатор пользователя
  • int VehicleId
  • Дата и время не назначены

Что я пробовал:

 query = context.Vehicle.Where(v => v.DeletedAt == null amp;amp; 
            !v.AssignedToVehicle.Where(a=>a.VehicleId == v.Id amp;amp; a.UnassignedAt != null).Any());
 

Мой план состоял в том, чтобы исключить все удаленные транспортные средства с помощью v.DeletedAt == null, а затем посмотреть в AssignedToVehicle, чтобы узнать, есть ли в нем какие-либо строки с этим транспортным средством. Если нет, то я хочу, чтобы он был в моем списке.

Какие у меня есть варианты, чтобы получить список неназначенных транспортных средств?

Комментарии:

1. Похоже, ваш запрос правильный и максимально возможный. В чем проблема?

2. Запрос должен также возвращать несуществующие транспортные средства в AssignedToVehicle? или все транспортные средства существуют в AssignedToVehicle?

3. @SvyatoslavDanyliv Он возвращает все транспортные средства. Я обнаружил, что если я удалю «amp;amp; a.UnassignedAt != null», то это сработает, потому что я получаю тот, который не назначен. Но это работает только потому, что кто-то другой не был назначен и не назначен снова. Я не понимаю, почему критерий more вернет больше строк?

4. @Sajid Он должен возвращать все транспортные средства, которых нет в AssignedToVehicle, или, если он существует, то неназначенный параметр не должен быть нулевым

5. @MichaelWinther попробуйте это : query = context.Vehicle.Where(v => v.AssignedToVehicle.Any(a => a.VehicleId == v.Id amp;amp; a.UnassignedAt != null) || !v.AssignedToVehicle.Any(a => a.VehicleId == v.Id));

Ответ №1:

Итак, у вас есть таблица с Vehicles и VehicleAssignments , с отношением «один ко многим» между Vehicles и VehicleAssignments: каждое транспортное средство имеет ноль или более назначений транспортных средств; каждое назначение транспортного средства принадлежит ровно одному транспортному средству, а именно тому, на который ссылается внешний ключ VehicleId .

(Я немного изменил ваши имена, поэтому я могу использовать существительные во множественном числе и единственном числе, чтобы легко ссылаться на таблицы и строки в таблицах)

Поскольку вы используете entity framework, у вас будут классы, похожие на следующие:

 class Vehicle
{
    public int Id {get; set;}
    public string Name {get; set;}
    public DateTime? DeletedAt {get; set;}  // null if not deleted yet

    // every Vehicle has zero or more VehicleAssignments (one-to-many)
    public virtual ICollection<VehicleAssignement> VehicleAssignements {get; set;}
}

class VehicleAssignment
{
    public int Id {get; set;}
    public string Name {get; set;}
    public DateTime? DeletedAt {get; set;}  // null if not deleted yet

    // every VehicleAssignement belongs to exactly one Vehicle, using foreign key
    public int VehicleId {get; set;}
    public virtual Vehicle Vehicle {get; set;}
}
 

Этого достаточно, чтобы entity Framework обнаружила ваше отношение «один ко многим». Если вы хотите, вы можете добавить атрибуты или использовать fluent API, но это необходимо только в том случае, если вы хотите отклониться от стандартов.

Я хотел бы иметь IQueryable, который содержит транспортные средства, которые никому не назначены.

Или, если быть более точным: вам нужен запрос, который возвращает все транспортные средства, которые не удалены (= имеют значение для свойства DeletedAt, равное null) И которые не имеют не удаленных назначений транспортных средств.

Или на обычном языке: вам нужны все транспортные средства, у которых есть хотя бы одно назначение. Как транспортное средство, так и назначение не могут быть удалены.

Для этого есть два метода:

  • Самый простой: используйте virtual ICollection<...> . Entity framework знает ваше отношение «один ко многим» и создаст для вас правильное групповое соединение
  • Выполните групповое соединение самостоятельно.

Используйте виртуальную ICollection

 IQueryable<Vehicle> assignedVehicles = dbContext.Vehicles
    .Where(vehicle => vehicle.DeletedAd == null
        amp;amp; vehicle.VehicleAssignements
            .Where(vehicleAssignment => vehicleAssignment.DeletedAdd == null)
            .Any());
 

Другими словами: из коллекции транспортных средств сохраняйте только те транспортные средства, которые еще не удалены, и у которых есть хотя бы одно назначение транспортного средства, которое еще не удалено.

Вы также можете начать с VehicleAssignments:

 IQueryable<Vehicle> assignedVehicles = dbContext.VehicleAssignments
    .Where(assignment => assignment.DeletedAt == null)
    .Select(assignment => assignment.Vehicle)
    .Where(vehicle => vehicle.DeletedAt == null)
    .Distinct();
 

Другими словами: из коллекции VehicleAssignments сохраняйте только те назначения, которые еще не удалены. Из каждого из оставшихся назначений транспортных средств выберите его одно и единственное транспортное средство. Результат: последовательность транспортных средств. Из этой последовательности сохраняйте только те, которые не удалены, и удаляйте дубликаты.

Сделайте (групповое) объединение самостоятельно

Небольшими шагами:

 IQueryable<Vehicle> nonDeletedVehicles = dbContext.Vehicles
    .Where(vehicle => vehicle.DeletedAd == null);
IQueryable<VehicleAssignments> nonDeletedAssignments = dbContext.VehiclAssignments
    .Where(vehicleAssignment => vehicleAssignment.DeletedAdd == null);

IQueryable<Vehicle> vehiclesWithAnyAssignment = nonDeletedVehicles
.GroupJoin(noneDeletedAssignments,

vehicle => vehicle.Id,                 // from each Vehicle take the primary key
assignment => assignment.VehicleId,    // from each Assignment take the foreign key

// parameter resultSelector: use each vehicle, with its zero or more assignments,
// to make one new:
(vehicle, assignmentsOfThisVehicle) => 

      // if there is at least one assignment: take the vehicle, otherwise take null
      assignmentsOfThisVehicle.Any() ? vehicle : (Vehicle)null)

// remove all nulls (= vehicles that didn't have an assignment)
.Where(vehicle => vehicle != null);
 

Вы также можете выполнить простое объединение, которое автоматически удалит не назначенные транспортные средства. Вам придется удалить дубликаты:

 var result = nonDeletedVehicls.Join(nonDeletedAssignments,

    vehicle => vehicle.Id,                    // primary key
    assignment => assignment.VehicleId,       // foreign key

    // parameter resultSelector: whenever you find a matching [vehicle, assignment]
    // combination, keep the Vehicle, because this Vehicle has at least one Assignment
    (vehicle, assignment) => vehicle)

    // remove Duplicates (= Vehicles with several assignments)
    .Distinct(),
 

Ответ №2:

Вы могли бы попробовать что-то вроде:

 var result = vehicles.Where(car => car.DeletedAt == null amp;amp;
                            !assignedToVehicle.Any(assigned => assigned.VehicleId == car.Id))
                     .AsQueryable();
 

Где vehicles и assignedToVehicle являются вашими объектами.

Вот полная демонстрация, которую я сделал:

Скрипка .NET

Комментарии:

1. Он по-прежнему дает мне все 38 транспортных средств, где только один из них никем не назначен. Но я получил ненужный WHERE out, так что это положительно, спасибо.