你很准确地指出了将实体用作业务对象的困难。经过多次尝试,我们已经确定了一个模式,将应用程序分为模块,并将每个模块分为三个层:Web(前端)、Core(业务)和Data。在我们的情况下,每个层都有自己的项目,因此有一项强制措施防止我们的依赖关系变得耦合紧密。
Core层包含实用类、POCOs和仓储接口。
Web层利用这些类和接口获取所需信息。例如,MVC控制器可以将特定的仓储接口作为构造函数参数,因此当创建控制器时,我们的IoC框架会注入该仓储的正确实现。仓储接口定义了选择器方法,返回在Core业务层中定义的POCO对象。
Data层的全部责任是实现在Core层中定义的仓储接口。它具有表示数据存储的Entity Framework上下文,但不返回实体(技术上是“数据”对象),而是返回在Core业务层中定义的POCOs(我们的“业务”对象)。
为了减少重复,我们有一个抽象的通用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不仅重用连接,而且只有在调用存储库方法之一时才创建实体上下文。
每个模块还有一个特殊的“测试”项目,其中包含这三个层中类的单元测试。我们甚至想出了一种使我们的存储库和其他数据访问类有些可单元测试的方法。现在我们已经建立了这个基本的基础设施,向我们的Web应用程序添加功能通常非常顺畅,而且不太容易出错。