Есть ли способ использовать `dynamic` в лямбда-выражении?
Во-первых, spec. Мы используем MVC5,.NET 4.5.1 и Entity framework 6.1.
В нашем бизнес-приложении MVC5 у нас много повторяющегося кода CRUD. Моя задача - "автоматизировать" большую часть, что означает извлечение ее на базовые классы и ее повторное использование. Прямо сейчас у меня есть базовые классы для контроллеров, моделей представлений и моделей сущностей EF6.
Мой абстрактный базовый класс, который наследует все объекты EF6:
public abstract class BaseEntity<TSubclass>
where TSubclass : BaseEntity<TSubclass>
{
public abstract Expression<Func<TSubclass, object>> UpdateCriterion();
}
UpdateCriterion
метод используется в AddOrUpdate
методе контекста базы данных. У меня есть общий параметр для подклассов, потому что UpdateCriterion
должен возвращать лямбда-выражение, которое использует точный тип подкласса, а не интерфейс или базовый класс. Очень упрощенный подкласс, реализующий этот абстрактный базовый класс, будет выглядеть так:
public class Worker : BaseEntity<Worker>
{
public int ID { get; set; }
public int Name { get; set; }
public override Expression<Func<Worker, object>> UpdateCriterion()
{
return worker => worker.ID;
}
}
После этого, в SaveOrUpdate
действии моего базового контроллера, у меня был бы такой код:
public ActionResult Save(TViewModel viewModel)
{
if (ModelState.IsValid)
{
var entityModel = viewModel.ConstructEntityModel();
db.Set<TEntityModel>().AddOrUpdate<TEntityModel>(entityModel.UpdateCriterion(), entityModel);
db.SaveChanges();
}
}
Благодаря этому подклассам базового контроллера не нужно внедрять метод Save
, как это было раньше. Теперь все это работает, и на самом деле он работает очень хорошо, несмотря на фанковый синтаксис (я имею в виду class BaseEntity<TSubclass> where TSubclass : BaseEntity<TSubclass>
, серьезно?).
Вот моя проблема. Для большинства объектов поле ID
является ключом, но для некоторых это не так, поэтому я не могу правильно обобщить реализацию суперкласса. Итак, на данный момент каждый подкласс объекта реализует его собственный UpdateCriterion
. Но, поскольку для большинства (90% +) объектов e => e.ID
является правильной реализацией, у меня много дублирования. Поэтому я хочу переписать базовый класс сущности на что-то вроде этого:
public abstract class BaseEntity<TSubclass>
where TSubclass : BaseEntity<TSubclass>
{
public virtual Expression<Func<TSubclass, object>> UpdateCriterion()
{
return entity => ((dynamic)entity).ID;
}
}
Цель состоит в том, чтобы обеспечить реализацию по умолчанию, использующую ID как ключ, и позволить подклассам переопределять ее, если они используют другой ключ. Я не могу использовать интерфейс или базовый класс с полем ID, потому что не все его имеют. Я решил использовать dynamic
для вывода ID
поля, но я получаю следующую ошибку: Error: An expression tree may not contain a dynamic operation
.
Итак, любая идея о том, как это сделать? Будет ли отражение работать в базе UpdateCriterion
?
Ответы
Ответ 1
Нет, вы не можете использовать динамический запрос Linq to Entities.
Но вы можете построить выражение Lambda во время выполнения.
public virtual Expression<Func<TSubclass, object>> UpdateCriterion()
{
var param = Expression.Parameter(typeof(TSubclass));
var body = Expression.Convert(Expression.Property(param, "ID"), typeof(object));
return Expression.Lambda<Func<TSubclass, object>>(body, param);
}
Если тип TSubclass не имеет свойства ID Expression.Property(param, "ID" ), генерирует исключение.
Кроме того, вы можете использовать MetadataWorkspace из своей модели сущности, чтобы получить столбец первичного ключа для TSubclass.
Ответ 2
Если вы определяете класс BaseEntity, вы можете добавить свойство виртуального чтения, которое возвращает фактическое свойство идентификатора. Я верю, что EF рассматривает свойства только для чтения как "вычисленные", поэтому они не сохраняются в db.
public abstract class BaseEntity<TSubclass> where TSubclass : BaseEntity<TSubclass>
{
public abstract object ID { get; }
public virtual Expression<Func<TSubclass, object>> UpdateCriterion()
{
return entity => entity.ID;
}
}
public partial class Foo : BaseEntity<Foo>
{
public Int32 FooId { get; set; }
public override object ID { get { return FooId; } }
}
Просто мысль - я только пытался компилировать в LinqPad и проверять значение вызова UpdateCriterion.:)
Ответ 3
Посмотрите этот ответ. Он использует отражение, чтобы получить свойство ID. Я думаю, это решает вашу проблему:
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
Затем вы заменяете выражение лямбда на
return entity => GetPropValue(entity, "ID");
Я не тестировал, так как у меня нет полноценного кода для его проверки. Если это работает, сообщите нам.