您在确定将实体用作业务对象的困难方面一针见血。经过多次尝试和错误,这是我们已经适应的模式,对我们来说效果很好:
我们的应用分为模块,每个模块又分为三层:Web(前端)、Core(业务)和Data。在我们的例子中,这些层中的每一层都有自己的项目,因此有一个强制执行来防止我们的依赖项变得紧密耦合。
Core 层包含实用程序类、POCO 和存储库接口。
Web 层利用这些类和接口来获取所需的信息。例如,MVC 控制器可以将特定的存储库接口作为构造函数参数,因此我们的 IoC 框架会在创建控制器时注入该存储库的正确实现。存储库接口定义了返回我们的 POCO 对象的选择器方法(也在核心业务层中定义)。
数据 层的全部职责是实现核心层中定义的存储库接口。它有一个实体框架上下文来表示我们的数据存储,但它不是返回实体(技术上是“数据”对象),而是返回核心层中定义的 POCO(我们的“业务”对象)。
为了减少重复,我们有一个抽象的通用 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);
}
EntityMapper 在它们的构造函数中做的很少,所以如果一个控制器有多个存储库依赖项是可以的。不仅实体框架重用连接,而且实体上下文本身仅在调用存储库方法之一时创建。
每个模块还有一个特殊的Test项目,其中包含对这三层中的类的单元测试。我们甚至想出了一种方法来使我们的存储库和其他数据访问类在某种程度上是可单元测试的。现在我们已经设置了这个基本的基础架构,向我们的 Web 应用程序添加功能通常非常顺利并且不太容易出错。