ASP.MVC: репозиторий, который отражает IQueryable, но не Linq to SQL, DDD
Я хочу создать репозиторий DDD, который возвращает объекты IQueryable, которые соответствуют базовым классам Linq to SQL, за вычетом любых отношений. Я могу легко вернуть Entities минус отношения с Linq select new {field, field, ... } проекция. Как мне кодировать класс Repository Entity? Как бы я возвратил объект из репозитория, имеющий класс репозитория, а не класс 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>" %>
Редактировать:
Котцак наделил полномочиями код и заставил его работать, поэтому он зарабатывает флажок. Тем не менее, Марк Симанн отметил, что этот метод будет вызывать побочные эффекты. Он был прав в том, что проецирование или поднабор вашего POCO - это плохо. После того, как код заработал, я закончил тем, что сделал еще один объект, что вызывает ненужные сложности. В конечном итоге я изменил код, чтобы отразить предложения Марка.
Чтобы добавить к предложениям Котцака: Возвращаемое значение хранилища было IQueryable. Тип ссылки модели директивы страницы был
Inherits="System.Web.Mvc.ViewPage<IQueryable<MyStore.Models.MiniProduct>>"
Доступ к полям модели:
Model.SingleOrDefault().ProductNo
Model.SingleOrDefault().Name
...
И это привело к
foreach (MyStore.Models.MiniProduct myproduct in Model) {}
Спасибо вам обоим за ответы.
2 ответа
Попробуйте что-то вроде этого:
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 для этого, чтобы фильтр).
Предполагая, что ваши классы LINQ to SQL (L2S) генерируются автоматически и отражают вашу базовую базу данных, краткий ответ таков: не открывайте IQueryable ни для одного из ваших классов L2S- это будет Leaky Abstraction.
Немного более длинный ответ:
Весь смысл репозиториев состоит в том, чтобы скрыть доступ к данным за абстракцией, чтобы вы могли заменить или изменить свой код доступа к данным независимо от вашей доменной модели. Это не будет возможно, если вы будете основывать интерфейс Repository на типах, определенных в конкретной реализации (ваш компонент доступа к данным на основе L2S (DAC)) - даже если вы можете предоставить новую реализацию своего интерфейса Repository, вам потребуется обратитесь к вашему ЦАП L2S. Это не будет особенно приятно, если вы вдруг решите переключиться на LINQ to Entities или службу хранения таблиц Azure.
Доменные объекты должны быть определены технологически нейтральным способом. Лучше всего это делать как простые старые объекты C# (POCO).
Кроме того, показ IQueryable дает вам возможность выполнять прогнозы. Это может показаться привлекательным, но на самом деле довольно опасно в контексте DDD.
В DDD мы должны проектировать доменные объекты так, чтобы они инкапсулировали доменную логику и обеспечивали все инварианты.
В качестве примера рассмотрим концепцию сущности (не сущности LINQ to Entity, а сущности DDD). Объекты почти всегда идентифицируются постоянным идентификатором, поэтому один общий инвариант заключается в том, что идентификатор должен быть определен. Мы могли бы написать базовый класс 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 это приведет к Leaky Abstractions и Anemic Domain Models.
В следующей версии Entity Framework мы должны получить поддержку POCO, чтобы она могла приблизить нас к этой цели. Тем не менее, насколько я понимаю, присяжные по-прежнему отсутствуют.
Для более подробного обсуждения очень похожей темы см. IQueryable - Tight Coupling.