#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 являются вашими объектами.
Вот полная демонстрация, которую я сделал:
Комментарии:
1. Он по-прежнему дает мне все 38 транспортных средств, где только один из них никем не назначен. Но я получил ненужный WHERE out, так что это положительно, спасибо.