Вы ударили по гвоздю в голову, указав на сложность использования сущностей в качестве бизнес-объектов.После долгих проб и ошибок вот шаблон, который мы установили, который работал очень хорошо для нас:
Наше приложение разделено на модули, и каждый модуль разделен на три уровня: Веб (фронтальный)конец), ядро (бизнес) и данные.В нашем случае каждому из этих уровней присваивается свой собственный проект, поэтому существует жесткое принудительное применение, предотвращающее тесную связь наших зависимостей.
Слой Core содержит служебные классы, POCO,и интерфейсы репозитория.
Слой Web использует эти классы и интерфейсы для получения необходимой информации.Например, контроллер MVC может принимать конкретный интерфейс репозитория в качестве аргумента конструктора, поэтому наша инфраструктура IoC внедряет правильную реализацию этого репозитория при создании контроллера.Интерфейс репозитория определяет методы селектора, которые возвращают наши объекты POCO (также определенные на бизнес-уровне Core).
Вся ответственность уровня Data заключается в реализации интерфейсов репозитория, определенных на уровне Core.Он имеет контекст Entity Framework, который представляет наше хранилище данных, но вместо того, чтобы возвращать сущности (которые технически являются «объектами данных»), он возвращает POCO, определенные на уровне Core (наши «бизнес-объекты»).
Чтобы уменьшить количество повторений, у нас есть абстрактный обобщенный класс EntityMapper
, который предоставляет базовые функциональные возможности для сопоставления сущностей с POCO.Это делает большинство наших реализаций репозитория чрезвычайно простыми.Например:
public class EditLayoutChannelEntMapper : EntityMapper<Entity.LayoutChannel, EditLayoutChannel>,
IEditLayoutChannelRepository
{
protected override System.Linq.Expressions.Expression<Func<Entity.LayoutChannel, EditLayoutChannel>> Selector
{
get
{
return lc => new EditLayoutChannel
{
LayoutChannelId = lc.LayoutChannelId,
LayoutDisplayColumnId = lc.LayoutDisplayColId,
ChannelKey = lc.PortalChannelKey,
SortOrder = lc.Priority
};
}
}
public EditLayoutChannel GetById(int layoutChannelId)
{
return SelectSingle(c => c.LayoutChannelId == layoutChannelId);
}
}
Благодаря методам, реализованным базовым классом EntityMapper, в вышеуказанном репозитории реализован следующий интерфейс:
public interface IEditLayoutChannelRepository
{
EditLayoutChannel GetById(int layoutChannelId);
void Update(EditLayoutChannel editLayoutChannel);
int Insert(EditLayoutChannel editLayoutChannel);
void Delete(EditLayoutChannel layoutChannel);
}
EntityMappers очень мало делают в своих конструкторах, поэтомухорошо, если у контроллера есть несколько зависимостей репозитория.Entity Framework не только повторно использует соединения, но и сами контексты Entity создаются только при вызове одного из методов репозитория.
Каждый модуль также имеет специальный проект Test , который содержит модультесты для классов в этих трех уровнях.Мы даже придумали способ сделать наши репозитории и другие классы доступа к данным несколько тестируемыми на уровне модулей.Теперь, когда у нас настроена базовая инфраструктура, добавление функциональности в наше веб-приложение, как правило, довольно плавное и не слишком подвержено ошибкам.