Entity Framework 4/Linq: как преобразовать DateTime в строку в запросе?

У меня есть следующий запрос:

from a in Products
select new ProductVM
    {
         id = a.id,
         modified = a.modified.ToString()
    }

Что дает мне ошибку:

LINQ to Entities does not recognize the method 'System.String ToString()'
method, and this method cannot be translated into a store expression.

modified в таблице «Продукты» — это DateTime. modified в классе ProductVM — это строка.

Есть идеи? Это должен быть тривиальный вопрос.


person Zachary Scott    schedule 20.03.2011    source источник


Ответы (4)


ToString() не поддерживается в Linq to Entities — существует список вспомогательных функций как часть SqlFunctions< /strong>, но это не поддерживает преобразование даты в строку.

Проще всего было бы сначала спроецировать анонимный тип в запросе, а затем привести к IEnumerable с помощью AsEnumerable() - после этого вы можете использовать ToString(), потому что теперь вы используете Linq to Objects для оставшейся части выражения запроса (на эту тему есть длинная статья здесь).

   var results = Products.Select( p => new { a.id, a.modified })
                         .AsEnumerable()
                         .Select(p => new ProductVM() 
                                { id = p.id, 
                                  modified = p.modified.ToString() 
                                });
person BrokenGlass    schedule 20.03.2011
comment
Фантастический. Я просто использовал свои POCO до этого, а затем в последнюю минуту использовал .AsEnumerable().Select(p ... и это сработало очень хорошо. - person Zachary Scott; 21.03.2011
comment
Ссылка на длинную статью по этой теме кажется неработающей. :-( - person Jon Schneider; 22.05.2015
comment
@BrokednGlass Хороший ответ. Спасибо - person Zaker; 08.06.2015
comment
Этот подход прост, но я бы не сказал, что он применим к реальному сценарию, потому что он будет перебирать всю таблицу Products. Это было предложение Where в исходном запросе, которое необходимо было отфильтровать по значению даты (преобразованному в строку), результирующий SQL-запрос был бы намного хуже. - person Groo; 12.11.2015

Вот альтернатива:

.Select( p -> SqlFunctions.StringConvert((double)
                  SqlFunctions.DatePart("m", p.modified)).Trim() + "/" +
              // SqlFunctions.DateName("mm", p.modified) + "/" + MS ERROR?
              SqlFunctions.DateName("dd", p.modified) + "/" +
              SqlFunctions.DateName("yyyy", p.modified)

По-видимому, DateName("MM", ..) указывает на название месяца, где DatePart("mm", ..) предоставляет числовое значение, таким образом, StringConvert( ), но слева результат дополняется пробелами, таким образом, .Trim().

person Zachary Scott    schedule 03.05.2013
comment
Простой и без приведения к перечисляемому сначала - person Hensembryan; 30.10.2013
comment
Действительно удивительно. Можно ли поместить этот метод в дерево выражений и использовать его повторно? Спасибо! - person Alisson; 17.06.2016
comment
SqlFunctions.DateName("dd", p.modified), похоже, не возвращает отформатированное двузначное число. Я бы ожидал «01», но он возвращает «1». Очень огорчающе. - person MyiEye; 22.09.2018
comment
SqlFunctioins.StringConvert() имеет второй параметр, указывающий длину преобразованной строки. Значение по умолчанию равно 10 и дополняется пробелами слева. (Я бы с удовольствием указал 2 и дополнил нулем!) Поэтому и нужен Trim(). - person Cindy K.; 15.03.2019
comment
Спасибо! SqlFunctions.DateName(yyyy, value) + + SqlFunctions.DateName(m, value) + + SqlFunctions.DateName(d, value) + + SqlFunctions.DateName(hh, value) + + SqlFunctions.DateName(n, value) + + SqlFunctions.DateName(s, значение) + + SqlFunctions.DateName(мс, значение) - person Jesper1; 23.01.2020

Создайте новый POCO с этой структурой (я предполагаю, что тип данных — DateTime):

public class UserProductVM {
    ...
    private DateTime _modified;

    public DateTime SetModified { set { _dateEvent = value; } }
    public string Modified { get { return _modified.ToString("dd MMM yyyy @ HH:mm:ss"); } }
    ...
}

Затем вы присваиваете значение SetModified, изменяя свой код следующим образом:

from a in Products
select new UserProductVM
{
     ...
     SetModified = a.modified
}

Обратите внимание, что я использую UserProductVM вместо ProductVM и SetModified вместо modified.

Затем, когда вы получите свойство Modified, новый POCO принесет его в виде строки, которую вы отформатировали.

person William Ardila    schedule 10.09.2016

Это может не добавить многого, но на всякий случай, если кто-то такой же сумасшедший, как я, вот полный код, если вам нужно построить дерево выражений для ответа доктора Зима, используя DatePart/DateName, включая часть времени. Очевидно, что для других целей вы можете изменить Product->YourInitialType, ProductVM->YourResultType и модифицированный->YourProperty.

Редактировать (23.01.08): Сгенерированный SQL изменился между версиями 6.0.2 и 6.1.3. Первоначально, если бы значение было нулевым, сгенерированный SQL создал бы нулевой результат. Я считал это желательным в этом случае, но я понимаю, почему в других случаях это было бы нежелательно (null + "строковое значение" = null) и могло бы привести к выводу, не равному тому, что вы бы предпочли. Я подробно опишу, как изменился вывод столбца ниже, но проблема в том, что теперь он будет выводить «// ::» для нулевых значений. Я просто обработал этот вывод в моем вызывающем коде как особый случай и вручную изменил его обратно на null, но другие могут захотеть добавить более надежные результаты, чтобы обеспечить вывод нулей как null. Также стоит отметить, что оператор SQL в новой версии очень длинный.

ParameterExpression paramExp = Expression.Parameter(typeof(Product));
string propertyName = "modified";            
Expression propertyOrField = Expression.PropertyOrField(paramExp, propertyName);

MethodInfo datePartMethod = typeof(System.Data.Entity.SqlServer.SqlFunctions).GetMethods().Where(x => x.Name == "DatePart" && x.GetParameters().Length == 2 && x.GetParameters()[1].ParameterType == typeof(DateTime?)).First();
MethodInfo dateNameMethod = typeof(System.Data.Entity.SqlServer.SqlFunctions).GetMethods().Where(x => x.Name == "DateName" && x.GetParameters().Length == 2 && x.GetParameters()[1].ParameterType == typeof(DateTime?)).First();
MethodInfo stringConvertMethod = typeof(System.Data.Entity.SqlServer.SqlFunctions).GetMethods().Where(x => x.Name == "StringConvert" && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(decimal?)).First();
MethodInfo stringConcatMethod = typeof(string).GetMethods().Where(x => x.Name == "Concat" && x.GetParameters().Length == 2 && x.GetParameters()[0].ParameterType == typeof(string) && x.GetParameters()[1].ParameterType == typeof(string)).First();
MethodInfo stringTrimMethod = typeof(string).GetMethods().Where(x => x.Name == "Trim" && x.GetParameters().Length == 0).First();
Type projectedType = typeof(ProductVM);
NewExpression newHolder = Expression.New(projectedType);  
MemberInfo member = anonType.GetMember("modified")[0];
var monthPartExpression = Expression.Call(null, datePartMethod, Expression.Constant("mm", typeof(string)), propertyOrField);
var convertedMonthPartExpression = Expression.Call(null, stringConvertMethod, Expression.Convert(monthPartExpression, typeof(decimal?)));
var convertedDayPartExpression = Expression.Call(null, dateNameMethod, Expression.Constant("dd", typeof(string)), propertyOrField);
var convertedYearPartExpression = Expression.Call(null, dateNameMethod, Expression.Constant("yyyy", typeof(string)), propertyOrField);
var convertedHourPartExpression = Expression.Call(null, dateNameMethod, Expression.Constant("hh", typeof(string)), propertyOrField);
var convertedMinutePartExpression = Expression.Call(null, dateNameMethod, Expression.Constant("n", typeof(string)), propertyOrField);
var convertedSecondPartExpression = Expression.Call(null, dateNameMethod, Expression.Constant("ss", typeof(string)), propertyOrField);

var allAddedExpression = Expression.Call(null, stringConcatMethod, 
            convertedMonthPartExpression, 
            Expression.Call(null, stringConcatMethod,
                Expression.Constant("/", typeof(string)), 
                Expression.Call(null, stringConcatMethod, 
                    convertedDayPartExpression, 
                    Expression.Call(null, stringConcatMethod, 
                        Expression.Constant("/", typeof(string)), 
                        Expression.Call(null, stringConcatMethod,
                            convertedYearPartExpression,
                            Expression.Call(null, stringConcatMethod,
                                Expression.Constant(" ", typeof(string)),
                                Expression.Call(null, stringConcatMethod,
                                    convertedHourPartExpression,
                                    Expression.Call(null, stringConcatMethod,
                                        Expression.Constant(":", typeof(string)),
                                        Expression.Call(null, stringConcatMethod,
                                            convertedMinutePartExpression,
                                            Expression.Call(null, stringConcatMethod,
                                                Expression.Constant(":", typeof(string)),
                                                convertedSecondPartExpression

))))))))));
var trimmedExpression = Expression.Call(allAddedExpression, stringTrimMethod, new Expression[] { });    
var month = Expression.Bind(member, trimmedExpression);

MemberInitExpression memberInitExpression =
    Expression.MemberInit(
        newHolder,
        new MemberBinding[] { month });
var lambda = Expression.Lambda<Func<Product, ProductVM>>(memberInitExpression, paramExp);
person Brandon Barkley    schedule 14.04.2015