Подтвердить что ты не робот

ASP.MVC: репозиторий, который отражает IQueryable, но не Linq to SQL, DDD. Как поставить вопрос

Я хочу создать репозиторий DDD, который возвращает объекты IQueryable, которые соответствуют классам Linq to SQL, за вычетом любых отношений. Я могу легко вернуть Entities минус отношения с Linq, чтобы выбрать новую {поле, поле,...} проекцию. Как закодировать класс Entity Repository? Как мне вернуть объект из репозитория, имеющего класс репозитория, а не класс Linq to SQL и все еще заполнять его несколькими объектами из выделения Linq? Как я могу ссылаться на этот возвращенный класс в моей ViewModel?

Я очень новичок в этом, поэтому очевидные ошибки. Я пропускаю лодку и должен возвращать только полные Сущности из хранилища, а не проекцию? Мне все равно придется разграничивать отношения Linq to SQL, прежде чем отправлять их из репозитория. Неужели я полностью оторван от базы? Я действительно хочу сохранить тип данных IQueryable.

Например, мой код Linq to SQL в моем репозитории:

public class MiniProduct
{
    public MiniProduct( string ProductNo, string Name, string Description, double Price)
    {    this.ProductNo = ProductNo;
         this.Name = Name;
         this.Description = Description;
         this.Price = Price;
    }
}

public IQueryable<MiniProduct> GetProductsByCategory( string productCategory)
{
    return ( from p in db.Products
             from c in db.Categories
             from pc in db.ProductCategories
             where c.CategoryName == productCategory &&
                   c.CatID == pc.CatID &&
                   pc.ProductID == p.ProductID
             select new { p.ProductNo, p.Name, p.Description, p.Price } );
    // how to return IQueryable<MiniProduct> instead of IQueryable<AnonymousType>???
}

И в представлении (пытается сильно набрать ViewModel), каков будет мой тип данных модели и как ссылаться на представление?

<% Page Inherits="System.Web.Mvc.ViewPage<MyStore.Models.MiniProduct>" %>

Edit:

Cottsak уполномочил код и заставил его работать, поэтому он получает флажок. Однако Марк Семанн отметил, что этот метод вызовет побочные эффекты. Он был прав в том, что проектирование или подстановка вашего POCO плоха. После того, как я начал работать над кодом, я превратил тонну более одного объекта объекта, что вызывает ненужные осложнения. В конечном итоге я изменил код, чтобы отразить предложения Mark.

Чтобы добавить к предложениям Cottsak: Мое значение возвращаемого репозитория было IQueryable. Тип ссылки на директиву страницы был

Inherits="System.Web.Mvc.ViewPage<IQueryable<MyStore.Models.MiniProduct>>"

Доступ к полям модели:

Model.SingleOrDefault().ProductNo
Model.SingleOrDefault().Name
...

И это привело к

foreach (MyStore.Models.MiniProduct myproduct in Model) {}

Спасибо вам за ответы.

4b9b3361

Ответ 1

Попробуйте что-то вроде этого:

public class AnnouncementCategory : //...
{
    public int ID { get; set; }
    public string Name { get; set; }
}

.. и в вашем репо:

public IQueryable<AnnouncementCategory> GetAnnouncementCategories()
{
    return from ac in this._dc.AnnouncementCategories
           let announcements = this.GetAnnouncementsByCategory(ac.ID)
           select new AnnouncementCategory
           {
               ID = ac.ID,
               Name = ac.Text,
               Announcements = new LazyList<Announcement>(announcements)
           };
}

private IQueryable<Announcement> GetAnnouncementsByCategory(int categoryID)
{
    return GetAnnouncements().Where(a => a.Category.ID == categoryID);
}

Таким образом, вместо проецирования в анонимный тип, я проектирую новый экземпляр класса AnnouncementCategory. Вы можете игнорировать функцию GetAnnouncementsByCategory, если хотите, это используется для цепочки коллекции связанных объектов Announcement для каждой категории, но так, чтобы их можно было лениво загружать с помощью IQueryable (т.е. Так, что когда я в конечном итоге вызовите это свойство, мне не нужно вызывать всю коллекцию. Я могу сделать больше LINQ для этого, чтобы фильтровать).

Ответ 2

Предполагая, что ваши классы LINQ to SQL (L2S) автоматически генерируются и отражают вашу базовую базу данных, короткий ответ таков: не подвергайте IQueryable ни одному из ваших классов L2S - это будет Leaky Abstraction.

Немного более длинный ответ:

Весь смысл репозиториев заключается в том, чтобы скрыть доступ к данным за абстракцией, чтобы вы могли заменить или изменить код доступа к данным независимо от вашей модели домена. Это не будет возможно, если вы основываете интерфейс хранилища на типах, определенных в конкретной реализации (ваш компонент доступа к данным на основе L2S (DAC)), даже если вы могли бы предоставить новую реализацию интерфейса вашего репозитория, вам нужно будет обратитесь к своему ЦАП L2S. Это было бы не особенно приятно, если бы вы вдруг решили переключиться на LINQ на Entities или службу хранения таблиц Azure.

Объекты домена должны быть определены технологически нейтральным способом. Это лучше всего сделать как обычные объекты С# (POCO).

Кроме того, выставляя IQueryable, вы получаете возможность выполнять проекции. Это может показаться привлекательным, но на самом деле довольно опасно в контексте DDD.

В DDD мы должны создать объекты домена, чтобы они инкапсулировали логику домена и гарантировали все инварианты.

В качестве примера рассмотрим концепцию Entity (не LINQ to Entityities Entity, а DDD Entity). Объекты почти всегда идентифицируются постоянным идентификатором, поэтому один общий инвариант состоит в том, что идентификатор должен быть определен. Мы могли бы написать базовый класс Entity следующим образом:

public abstract class Entity
{
    private readonly int id;

    protected Entity(int id)
    {
        if(id <= 0)
        {
            throw new ArgumentOutOfRangeException();
        }
        this.id = id;
    }

    public int Id
    {
        get { return this.id; }
    }
}

Такой класс прекрасно применяет свои инварианты (в этом случае идентификатор должен быть положительным числом). В некоторых классах может быть много других, более специфичных для домена инвариантов, но многие из них, скорее всего, не согласуются с концепцией проектирования: вы могли бы определить проекцию, которая пропускает определенные свойства (такие как ID), но он будет аварийно завершен во время выполнения, потому что значения по умолчанию для типов (например, 0 для int) будут генерировать исключения.

Другими словами, даже если вам нужны только определенные свойства объекта домена, будет иметь смысл полностью увлажнить его, потому что это единственный способ удовлетворить его инварианты.

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

В заключение, разоблачение IQueryable звучит как очень привлекательная стратегия, но с текущей реализацией L2S это приведет к липким абстракциям и анонимным моделям доменов.

В следующей версии Entity Framework мы должны получить поддержку POCO, чтобы она приблизила нас к этой цели. Однако, насколько мне известно, жюри все еще не работает на этом счету.


Более подробное обсуждение очень похожей темы см. в IQueryable - это плотная связь.