LINQ для юридических лиц - лучшие практики для прохождения моделей - PullRequest
3 голосов
/ 12 июля 2010

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

interface IUserRespository
{
     User GetUserByID(int id);
     void AddUser(User u);
     ... and so on
}

Однако иногда мы хотим сделать более сложный запрос к БД, который включает в себя группирование и агрегирование. Например, мы хотим получить общее значение всех заказов всех пользователей (результирующий набор будет иметь только два столбца: UserName и TotalAmount). Теперь мой вопрос: как будет выглядеть метод хранилища? В интернете LINQ есть десятки примеров того, как сделать запрос с суммой и сгруппировать, но все эти примеры возвращают анонимные типы. Итак, как наш репозиторий может вернуть результат такого запроса? Другой распространенный ответ: оберните это в классе. Таким образом, метод может выглядеть так:

interface IUserRepository
{
    UserOrderTotal[] GetOrderTotalsForAllUsers();
}

UserOrderTotal должен быть небольшим классом с двумя свойствами, возвращаемыми запросом: UserName и TotalAmount. Этот класс будет определен где-то в одном из наших файлов .cs. Это действительно единственное решение? Это выглядит очень плохо, потому что вводит новую «временную» модель где-то за пределами модели основных сущностей (edmx). В идеале я хотел бы дополнить мою модель User новым полем TotalAmount, которое будет заполняться только в этом запросе, поэтому интерфейс может выглядеть следующим образом:

interface IUserRepository
{
    User[] GetOrderTotalsForAllUsers();
}

И он будет возвращать сущности пользователя со всеми полями, установленными по умолчанию, кроме Name и TotalAmount. Проблема в том, что я не могу скомпилировать модель с полем, которое не сопоставлено со столбцом базы данных. Я получаю сообщение об ошибке 3004. Ошибка при сопоставлении фрагментов, начинающихся со строки 2226: сопоставление не указано для свойств User.TotalAmount в поле «Задать пользователя». Объект с ключом (PK) не будет возвращаться, когда: объект имеет тип [FPSoMeterModel.User ]

Я делаю это неправильно? Эта проблема тривиальна, или, может быть, я задаю неправильные вопросы? Создание класса-оболочки для каждого запроса (!), Который включает агрегацию, кажется немного нелепым. Кажется, что весь материал LINQ побуждает людей НЕ использовать многоуровневую архитектуру, а строить запросы в том же слое, где отображаются данные ... Как вы, ребята, справляетесь с этим, вы используете классы "хранилища" с LINQ, если да - как вы возвращаете сложные результаты запроса?

1 Ответ

1 голос
/ 12 июля 2010

Я бы не стал определять плохую практику нового класса UserOrderTotal, наоборот.Есть два способа посмотреть на такой объект.

(1) Этот класс UserOrderTotal можно увидеть как Объект передачи данных (DTO).Таким образом, он не является частью вашего домена.DTO обычно используются для передачи сущностей по проводам или для предоставления доменных объектов в плоской форме на уровень представления.Я использую их все время.В этой ситуации объект используется как проекция на данные: как вид.Шаблон MVVM использует тот же принцип, когда часть ViewModel MVVM является DTO, специфичным для пользовательского интерфейса.

(2) Этот класс UserOrderTotal можно видеть как часть вашего домена.С точки зрения доменного дизайна объект может быть частью языка пользователя.В такой ситуации было бы логично определить его как объект домена.Разница, однако, заключается в том, что он не будет определен в файле edmx de Entity Framework (EF).Я думаю, что в EF возможно определить сущности, которые не имеют сопоставления с таблицей базы данных, но я не знаю, облегчает ли это процесс.Лично я не вижу проблемы с определением этих доменных объектов вне файла edmx в том же проекте, где находится edmx.

О возврате массива User объектов: это кажется плохой идеей.Есть несколько проблем с этим подходом.Прежде всего, ваш код не очень хорошо передает свои намерения.Вы используете объект User, но оставляете все свойства пустыми.Эта практика очень трудна для тех, кто пытается использовать этот код.Вы фактически используете объект User для чего-то, что не является: а именно, для агрегата.

Кроме того, вы заметили, что EF не может обработать это дополнительное свойство, добавленное вами в модель.Хотя в целом можно добавить свойства в модель EF (используя частичные классы), вы должны быть очень консервативны в этом.В то время как вещи будут компилироваться, они не будут работать во время выполнения, когда вы будете использовать эти свойства в запросах LINQ.

Создание класса-оболочки для каждого запроса (!), Который включает агрегирование, кажется немного нелепым.

Я так не думаю.ИМО это правильно делать.Это является следствием использования EF в многоуровневой архитектуре.Я должен признать, что иметь EF в одном слое намного проще, но в долгосрочной перспективе это становится действительно грязным.Чем больше проект и чем дольше вам нужно его поддерживать, тем больше оправдано добавление слоев абстракций.

Как вы, ребята, справляетесь с этим, используете ли вы классы "репозиторий" с LINQ

Приложение, которое я разработал, часто имеет служебный / бизнес-уровень, который отделяет чтения от мутаций.Для операций чтения / запросов я использую статические классы с методами, которые возвращают коллекцию объектов домена или DTO.Часто такой «метод обслуживания» является специфическим для определенной части пользовательского интерфейса.Мутации заключены в «служебные команды».Пользовательский интерфейс создает сервисную команду и запускает ее.Команда выражает один случай использования или пользовательскую историю и является атомарной частью логики.Сервисный уровень обеспечит создание транзакции базы данных (при необходимости) при выполнении команды.Для пользовательского интерфейса использование команды обычно выглядит так:

void UpdateButton_Click(object sender, EventArgs e)
{
    if (!this.Page.IsValid)
    {
        return;
    }

    var command = new UpdateUserSettingsCommand();

    command.UserName = this.UserName.Text;
    command.MailAddress = this.MailAddress.Text;

    command.Execute();
}

Надеюсь, все это имеет смысл.Если вы хотите узнать больше о том, как я использую DTO, прочитайте мой SO-ответ .

Надеюсь, это поможет.

...