LINQ2SQL: как изменить значения полей при загрузке анонимных объектов?

!!! Пожалуйста, не перенаправляйте эту статью, так как она не решает проблему, описанную ниже.

Скажем, у нас есть такая таблица в базе данных:

SomeTable

  • ID (int)
  • DT (datetime)

Мы настроили контекст данных Linq2Sql. И мы настроили объект для SomeTable: Метод OnLoaded изменяет DT таким образом, что DateTimeKind DT становится Utc (изначально это Unspecified).

Теперь вот проблема:

Если мы запрашиваем данные с использованием целой сущности, вызывается метод OnLoaded:

From x In ourDataContext.SomeTable Select x

Но если мы запрашиваем только часть таблицы (и, следовательно, генерируем анонимный тип), OnLoaded не вызывается:

From x In ourDataContext.SomeTable Select x.DT

Понятно, что OnLoaded определен в сущности SomeTable, а не в анонимном типе.

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

Ответ 1

У нас была аналогичная проблема, так как нам нужно было получать часть полей из объекта как анонимного объекта и всегда знать, что мы имеем DateTimeKind полей даты как DateTimeKind.UTC без использования дополнительных функций в запросе LINQ.

Мы пробовали много вещей, но мы нашли только одно достаточно хорошее решение - генерация кода для Linq2Sql с T4.

P.S. Если вы хотите узнать больше о генерации кода Linq2Sql с помощью T4, вы можете начать с http://www.hanselman.com/blog/T4TextTemplateTransformationToolkitCodeGenerationBestKeptVisualStudioSecret.aspx

Ответ 2

Linq2Sql генерирует частичные классы для таблиц, что упрощает их расширение. Просто добавьте файл SomeTable.cs к вашему решению (в пределах того же пространства имен, что и ваш автоматически сгенерированный контекст db) и определите дополнительное свойство с любым поведением, которое вам нужно:

public partial class SomeTable {
    public System.DateTime CustomDT {
        get { return DT.AddYears(120); }
    }
}

Теперь вы можете запросить его, как обычно:

        var e = ctx.SomeTable.Select(x => new { x.CustomDT }).First();
        Console.WriteLine(e.CustomDT);

Обновление

Основываясь на комментариях, я думаю, что проблема, с которой вы столкнулись, связана с неправильным разделением обязанностей. Вы пытаетесь передать бизнес-логику (преобразование данных) на ваш DAL. Хотя L2S обеспечивает некоторую гибкость здесь (как показано выше), у вас есть другие варианты, если решение не удовлетворяет:

  • Явный уровень выше L2S DAL. Обычно это шаблон хранилища который возвращает DTO, очень похожие на автогенерируемые L2S. В этом случае вы можете скрыть свойство DT, заставляющее потребителей использовать только CustomDT.
  • Поместите логику в базу данных (представления, вычисленные столбцы, SP). я не будет рассматривать этот подход для нового проекта, но это может быть жизнеспособный вариант для некоторых устаревших приложений.

Ответ 3

Вы можете указать DateTimeKind в запросе:

from x in ourDataContext.SomeTable 
select DateTime.SpecifyKind(x.DT, DateTimeKind.Utc)

Если вы будете делать это часто, метод расширения может помочь сделать его менее подробным:

public static class Ext
{
    public static DateTime AsUtc(this DateTime dateTime)
    {
        return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
    }

    public static DateTime? AsUtc(this DateTime? dateTime)
    {
        if(dateTime == null) return null;
        return AsUtc(dateTime.Value);
    }
}

Затем ваш запрос будет выглядеть следующим образом:

from x in ourDataContext.SomeTable select x.DT.AsUtc()

Ответ 4

Вы можете использовать linq-to-sql для части запроса и использовать linq-to-objects для захвата требуемого свойства DateTime (вы фактически не возвращаете анонимный тип).

(From x In ourDataContext.SomeTable _
 Select x).AsEnumerable() _
          .Select(Function(x) x.DT)

Ответ 5

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

SomeTable.Select( x => new SomeTable {
    DateField = x.DateField
})

В противном случае нет простого решения.