EF6 используя пользовательское свойство в запросе linq - PullRequest
0 голосов
/ 15 октября 2018

У меня есть класс со следующим свойством:

[NotMapped]
public string Key
{
    get
    {
        return string.Format("{0}_{1}", Process.Name, LocalSequenceNumber);
    }
}

Локальный порядковый номер - это вычисленное целое число, подкрепленное кешем в форме параллельного словаря.

Я хочуиспользуйте указанное выше свойство Key в запросе LINQ, но получите исключение:

Указанный элемент типа 'Key' не поддерживается в LINQ to Entities.Поддерживаются только инициализаторы, элементы сущностей и свойства навигации сущностей.

Я понимаю, почему я получаю эту ошибку, но я не слишком уверен, как ее исправить.В настоящее время свойство Key обеспечивает хорошую инкапсуляцию для моего класса, с которой я не хочу расставаться.Любые предложения с точки зрения библиотек или простых шаблонов, чтобы обойти это?

Изменить: Вот запрос, который вызывает исключение:

db.Cars.SingleOrDefault(c => c.Id == id && c.Key == key);

Ответы [ 3 ]

0 голосов
/ 15 октября 2018

Существует множество сторонних пакетов, которые могут решить эту проблему.Я также считаю, что в EF.Core есть методы, которые могут помочь, однако я предложу 2 «чистых Entity Framework 6» решения.

  1. Выполните ваш запрос в две части - часть SQL, затемчасть «в коде».

db.Cars.Where(c => c.Id == id).ToList().SingleOrDefault(c => c.Key == key)

это по-прежнему сохранит вашу логику инкапсулированной в классе, но вы не получите преимущества выполнения SQL.

То, что я люблю называть «проектором».Это немного более многословно.

По сути, вы создаете «представление» EF POCO, которое представляет объект передачи данных.он имеет свойства, необходимые для вашего представления, а также определяет способ проецирования данных из базы данных в представление.

// Poco:
public class Car {
  public int Id {get;set;}
  public string LocalSequenceNumber {get;set;}
  public int ProcessId {get;set; }
  public virtual Process Process {get;set;}
  // ...
}
public class Process {
 // ...
}

// View+Projector:
public class CarView
{ 
  public int Id {get;set;}
  public string Color {get;set;}
  public string Key {get;set;}
  public static Expression<Func<Car, CarView>> Projector = car => new CarView {
    Id = car.Id,
    Color = car.Color,
    Key = car.Process.Name + " " + car.LocalSequenceNumber 
  }
}

// calling code
var car = db.Cars.Select(CarView.Project).SingleOrDefault(cv => cv.Id == id && cv.Key == key)

Это позволит оценить весь код в базе данных, в то же время инкапсулируя вашу бизнес-логику в коде..

0 голосов
/ 15 октября 2018

Увы, вы забыли рассказать нам, что такое Process.Name и LocalSequenceNumber.Из идентификаторов кажется, что они не являются частью вашего Cars, а являются значениями в вашем локальном процессе.Почему бы не вычислить Ключ перед вашим запросом?

var key = string.Format("{0}_{1}", Process.Name, LocalSequenceNumber);
db.Cars.SingleOrDefault(c => c.Id == id && c.Key == key);

Если, с другой стороны, Process.Name или LocalSequenceNumber являются Car свойствами, вам придется изменить IQueryable.Expression, то естьв вашем запросе LINQ, используя только те свойства и методы, которые могут быть переведены вашим IQueryable.Provider в SQL.

К счастью, ваш Provider знает ToSTring() и концепцию конкатенации строк. Так что вы можете использовать это

Поскольку вы используете свойство Key в Queryable.Where, я предлагаю расширитьIQueryable с функцией WhereKey.Если функции расширения для вас немного волшебны, см. Демистифицированные методы расширения

public static IQueryable<Car> WhereKey(this IQueryable<Car> cars, int id, string key)
{
    return cars.Where(car => car.Id == id
            && key == car.Process.Name.ToString() + "_" + car.LocalSequenceNumber.ToString());
}

Использование:

int carId = ...
string carKey = ...
var result = myDbContext.Cars
    .WhereKey(carId, carKey)
    .FirstOrDefault();

Рассмотрите возможность создания WhereKey, который проверяет толькоключ.Объедините с Where, который выбирает Id.

 var result = myDbContext.Cars
    .Where(car => car.Id == id)
    .WhereKey(carKey)
    .FirstOrDefault();

Если Process.Name или LocalSequenceNumber не является частью Car, добавьте его в качестве параметра.Вы получаете суть.

Рассмотрите возможность создания WhereKey, который проверяет только ключ.Объедините с Where, который выбирает Id.

При желании, вы можете создать WhereKeyFirstOrDefault(), но я сомневаюсь, что это будет полезно.

0 голосов
/ 15 октября 2018

Пакет DelegateDecompiler https://github.com/hazzik/DelegateDecompiler обрабатывает этот тип сценария.

Украсьте ваше свойство атрибутом Computed, тогда запросы, подобные следующим, должны работать, если вы добавляете метод Decompile:

db.Cars.Decompile().SingleOrDefault(c => c.Id == id && c.Key == key)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...