Как присвоить свойству значение IQueryable?
Сначала я использую код Entity Framework 4.1. В моей сущности у меня есть три свойства даты / времени:
public class MyEntity
{
[Key]
public Id { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
[NotMapped]
public DateTime? QueryDate { get; set; }
// and some other fields, of course
}
В базе данных у меня всегда есть заполненные даты От/До. Я обращаюсь к ним с запросом, используя простое предложение where. Но в результирующий набор я хочу включить дату, которую запросил. Мне нужно сохранить это, чтобы сработала какая-то другая бизнес-логика.
Я работаю над методом расширения, чтобы сделать это, но я сталкиваюсь с проблемами:
public static IQueryable<T> WhereDateInRange<T>(this IQueryable<T> queryable, DateTime queryDate) where T : MyEntity
{
// this part works fine
var newQueryable = queryable.Where(e => e.FromDate <= queryDate &&
e.ToDate >= queryDate);
// in theory, this is what I want to do
newQueryable = newQueryable.Select(e =>
{
e.QueryDate = queryDate;
return e;
});
return newQueryable;
}
Это не работает. Это работает, если Я использую IEnumerable, но я хочу сохранить его как IQueryable, чтобы все выполнялось на стороне базы данных, и этот метод расширения все еще можно использовать в любой части другого запроса. Когда это IQueryable, я получаю ошибку компиляции следующего вида:
Лямбда-выражение с телом оператора не может быть преобразовано в дерево выражений
Если бы это был SQL, я бы просто сделал что-то вроде этого:
SELECT *, @QueryDate as QueryDate
FROM MyEntities
WHERE @QueryDate BETWEEN FromDate AND ToDate
Итак, вопрос в том, как я могу преобразовать дерево выражений I уже должны включить это дополнительное назначение имущества? Я заглянул в Иккериабль.Выражение и IQueryable.Поставщик.CreateQuery-где-то там есть решение. Может быть, выражение присваивания можно добавить к существующему дереву выражений? Я недостаточно знаком с методами дерева выражений, чтобы понять это. Есть идеи?
Пример Использования
Чтобы прояснить, цель состоит в том, чтобы иметь возможность выполнить что-то вроде этого:
var entity = dataContext.Set<MyEntity>()
.WhereDateInRange(DateTime.Now)
.FirstOrDefault();
И имеют значение datetime.Теперь persisited в QueryDate результирующей строки, не имея более одной строки, возвращенной из запроса базы данных. (С решением интерфейс IEnumerable, возвращается несколько строк, перед метода firstordefault выбирает строку, которую мы хотим.)
Еще Одна Идея
Я мог бы пойти дальше и сопоставить QueryDate как реальное поле, и установить его DatabaseGeneratedOption вычисленным. Но тогда мне понадобится какой-то способ ввести "@QueryDate as QueryDate " в SQL, созданный EF select заявления. Поскольку он вычисляется, EF не будет пытаться предоставить значения во время обновления или вставки. Так как же я могу ввести пользовательский SQL в инструкции select?
4 ответа:
Ладислав абсолютно прав. Но поскольку вы, очевидно, хотите получить ответ на вторую часть вашего вопроса, вот как вы можете использовать Assign. Но с эф это не сработает.
using System; using System.Linq; using System.Linq.Expressions; namespace SO5639951 { static class Program { static void Main() { AdventureWorks2008Entities c = new AdventureWorks2008Entities(); var data = c.Addresses.Select(p => p); ParameterExpression value = Expression.Parameter(typeof(Address), "value"); ParameterExpression result = Expression.Parameter(typeof(Address), "result"); BlockExpression block = Expression.Block( new[] { result }, Expression.Assign(Expression.Property(value, "AddressLine1"), Expression.Constant("X")), Expression.Assign(result, value) ); LambdaExpression lambdaExpression = Expression.Lambda<Func<Address, Address>>(block, value); MethodCallExpression methodCallExpression = Expression.Call( typeof(Queryable), "Select", new[]{ typeof(Address),typeof(Address) } , new[] { data.Expression, Expression.Quote(lambdaExpression) }); var data2 = data.Provider.CreateQuery<Address>(methodCallExpression); string result1 = data.ToList()[0].AddressLine1; string result2 = data2.ToList()[0].AddressLine1; } } }
Обновление 1
Вот тот же код после некоторой настройки. Я прочитал выражение" Block", которое EF подавился в коде выше, чтобы продемонстрировать с абсолютной ясностью, что это выражение" Assign", которое EF не поддерживает. Обратите внимание, что Assign работает в принципе с универсальным выражением деревья, это поставщик EF, который не поддерживает Assign.
using System; using System.Linq; using System.Linq.Expressions; namespace SO5639951 { static class Program { static void Main() { AdventureWorks2008Entities c = new AdventureWorks2008Entities(); IQueryable<Address> originalData = c.Addresses.AsQueryable(); Type anonType = new { a = new Address(), b = "" }.GetType(); ParameterExpression assignParameter = Expression.Parameter(typeof(Address), "value"); var assignExpression = Expression.New( anonType.GetConstructor(new[] { typeof(Address), typeof(string) }), assignParameter, Expression.Assign(Expression.Property(assignParameter, "AddressLine1"), Expression.Constant("X"))); LambdaExpression lambdaAssignExpression = Expression.Lambda(assignExpression, assignParameter); var assignData = originalData.Provider.CreateQuery(CreateSelectMethodCall(originalData, lambdaAssignExpression)); ParameterExpression selectParameter = Expression.Parameter(anonType, "value"); var selectExpression = Expression.Property(selectParameter, "a"); LambdaExpression lambdaSelectExpression = Expression.Lambda(selectExpression, selectParameter); IQueryable<Address> finalData = assignData.Provider.CreateQuery<Address>(CreateSelectMethodCall(assignData, lambdaSelectExpression)); string result = finalData.ToList()[0].AddressLine1; } static MethodCallExpression CreateSelectMethodCall(IQueryable query, LambdaExpression expression) { Type[] typeArgs = new[] { query.ElementType, expression.Body.Type }; return Expression.Call( typeof(Queryable), "Select", typeArgs, new[] { query.Expression, Expression.Quote(expression) }); } } }
Нет, я не думаю, что есть решение. Это правда, что вы можете изменить дерево выражений, но вы получите точно такое же исключение, как и при запросе linq, потому что этот запрос на самом деле является тем, что вы построите в дереве выражений. Проблема не в дереве выражений, а в отображении. EF не может сопоставить
QueryData
с результатом. Более того, вы пытаетесь делать проекцию. Проекция не может быть выполнена на сопоставленную сущность, и анонимный тип не может быть возвращен из метода.Вы можете выключить конечно, сделайте выбор, о котором вы упомянули, но просто вы не можете сопоставить его с вашей сущностью. Для этого необходимо создать новый тип:
var query = from x in context.MyData where x.FromDate <= queryDate && x.ToDate >= queryDate select new MyDateWrapper { MyData = x, QueryDate = queryDate };
Automapper имеет запрашиваемые расширения, я думаю, что он может решить ваши потребности. Вы можете использовать ProjectTo для вычисления свойства во время выполнения.
EF Core 2 устанавливает значение игнорируемого свойства во время выполнения
Http://docs.automapper.org/en/stable/Queryable-Extensions.html
Пример конфигурации:
configuration.CreateMap(typeof(MyEntity), typeof(MyEntity)) .ForMember(nameof(Entity.QueryDate), opt.MapFrom(src => DateTime.Now));
Использование:
queryable.ProjectTo<MyEntity>();
Спасибо за все ценные отзывы. Это звучит так, как будто ответ: "Нет , вы не можете сделать это таким образом".
Итак, я придумал обходной путь. Это очень специфично для моей реализации, но это делает трюк.
public class MyEntity { private DateTime? _queryDate; [ThreadStatic] internal static DateTime TempQueryDate; [NotMapped] public DateTime? QueryDate { get { if (_queryDate == null) _queryDate = TempQueryDate; return _queryDate; } } ... } public static IQueryable<T> WhereDateInRange<T>(this IQueryable<T> queryable, DateTime queryDate) where T : MyEntity { MyEntity.TempQueryDate = queryDate; return queryable.Where(e => e.FromDate <= queryDate && e.ToDate >= queryDate); }
Волшебство заключается в том, что я использую статическое поле потока для кэширования даты запроса, чтобы она была доступна позже в том же потоке. Тот факт, что я получаю его обратно в геттере QueryDate, зависит от моих потребностей.
Очевидно, что это не решение EF или LINQ к первоначальному вопросу, но он действительно достигает того же эффекта, удаляя его из этого мира.