Juile Lerman关于“企业中的EF”的多元化课程给我留下了深刻的印象,并决定构建我的演示应用程序。
我正在使用VS 2012和最新版本的EF,SQL Server和MVC。我正在构建一个应用SOLID原则的演示应用程序。我这样做是为了更好地理解如何实现DI和单元测试。
我在这个演示应用程序中使用了DB first方法。它只包含一个名为UserDetails的表,下面是它在SQL Server中的外观。我将使用此表进行CRUD操作。
以下是我如何分层我的应用程序:
1. WESModel解决方案:该层包含我的Model1.edmx文件和上下文类,如下所示。
namespace WESModel
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using WESDomain;
public partial class WESMVCEntities : DbContext
{
public WESMVCEntities()
: base("name=WESMVCEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<UserDetail> UserDetails { get; set; }
}
}
2. WESDomain解决方案:该层包含我的Domain类(或POCO类)。这些POCO类实际上是在我的WESModel层中自动生成的。我把它们移到了这一层。以下是单个POCO类的外观。
namespace WESDomain
{
using System;
using System.Collections.Generic;
public partial class UserDetail:IUserDetail
{
public int Id { get; set; }
public string UserName { get; set; }
}
}
3:WESDataLayer解决方案:该层包含对上述2层dll的引用。该层具有我的Repository类,如下所示。现在,我将IRepository保持在同一个类:)
namespace WESDataLayer
{
public class UserDetailRepository : IUserDetailRepository
{
WESMVCEntities context = new WESMVCEntities();
public IQueryable<IUserDetail> All
{
get { return context.UserDetails; }
}
public IQueryable<IUserDetail> AllIncluding(params Expression<Func<IUserDetail, object>>[] includeProperties)
{
IQueryable<IUserDetail> query = context.UserDetails;
foreach (var includeProperty in includeProperties) {
query = query.Include(includeProperty);
}
return query;
}
public IUserDetail Find(int id)
{
return context.UserDetails.Find(id);
}
public void InsertOrUpdate(UserDetail userdetail)
{
if (userdetail.Id == default(int)) {
// New entity
context.UserDetails.Add(userdetail);
} else {
// Existing entity
context.Entry(userdetail).State = EntityState.Modified;
}
}
public void Delete(int id)
{
var userdetail = context.UserDetails.Find(id);
context.UserDetails.Remove(userdetail);
}
public void Save()
{
context.SaveChanges();
}
public void Dispose()
{
context.Dispose();
}
}
public interface IUserDetailRepository : IDisposable
{
IQueryable<IUserDetail> All { get; }
IQueryable<IUserDetail> AllIncluding(params Expression<Func<UserDetail, object>>[] includeProperties);
UserDetail Find(int id);
void InsertOrUpdate(UserDetail userdetail);
void Delete(int id);
void Save();
}
}
4:ConsoleApplication1解决方案 :这是我的UI层。它将成为我最终应用程序中的MVC应用程序。在这里,我只是查询数据库并显示数据。这就是代码的外观。
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
IUserDetailRepository repo = new UserDetailRepository();
var count = repo.All.ToList().Count().ToString();
Console.WriteLine("Count: {0}", count);
Console.ReadLine();
}
}
}
问题:我的UI层没有任何引用EF DLL。但是,它有一个Repository类的实例。在MVC应用程序中,我的控制器将具有存储库类或UnitOfWork的实例。
a)这是正确的做法吗?
b)有什么办法可以抽象出来吗?
c)如果将来我想用Dapper或任何其他ORM工具换掉EF怎么办?
d)我如何在我的项目中使用我的DI工具?它应该在哪一层?
e)单元测试。我知道StructureMap,并希望在这个项目中使用它,以便将来我应该能够与Ninject交换它。我如何实现这一目标?
感谢您阅读这个大问题,如果有人能指出我正确的方向,我真的很感激。
问题:我的UI层没有任何引用EF DLL。但是,它有一个Repository类的实例。在MVC应用程序中,我的控制器将具有存储库类或UnitOfWork的实例。
是的,UI图层类不得对EF有任何引用。但要做到这一点,他们不能引用具体的存储库。在MVC应用程序中,如果您不使用服务层,则Controller将仅在IUserDetailRepository上引用,并等待构造中的具体类型。关于UnitOfWork,这取决于你的实现:-)
a)这是正确的做法吗?
正确的做法是称为“松散耦合”,似乎你的设计选择了这种方式。
b)有什么办法可以抽象出来吗?
是的,您可以使用依赖性解析程序。这样,无需引用具体类型,您将只有基于抽象的代码
c)如果将来我想用Dapper或任何其他ORM工具换掉EF怎么办?
您必须具有数据访问层,例如,包含IXxxRepository契约的具体实现的库。在您的情况下,它将是EF实现。当您更改Dapper时,您将不得不重新实现此图层。重构有一个可接受的限制。
d)我如何在我的项目中使用我的DI工具?它应该在哪一层?
放置DI工具的最佳位置是UI层。在应用程序启动时,您将配置依赖项绑定,一切都将自动运行;)
e)单元测试。我知道StructureMap,并希望在这个项目中使用它,以便将来我应该能够与Ninject交换它。我如何实现这一目标?
你想拔下你的Dependency Resolver插上另一个吗?没问题,只需在编码DR配置时预测,以便与您的应用程序保持最小耦合。在某些情况下,有一些限制耦合的技巧......在我目前正在开发的项目中,我们首先有一个MVC应用程序和一个服务层,业务层,数据访问层和基础设施层。我们使用Ninject作为DR,并且基础结构和Web UI层是唯一对Ninject有引用的层。拔掉它很容易,我们已经用这种方式尝试过Unity。
还有一件事,你不应该有UserDetail的合同。没有必要,对无状态类使用依赖注入,而不是像DTO这样的所有类。
如果使用隐式变量类型而不是显式变量类型(即消除var
关键字),则可以更轻松地确定依赖项。尽可能使用接口( IUserDetailRepository
)而不是使用类( UserDetailRepository
)。
例子:
1)允许编译器确定类型
var repo = new UserDetailRepository();
2)类型由类引用确定
UserDetailRepository repo = new UserDetailRepository();
3)类型由接口确定
IUserDetailRepository repo = new UserDetailRepository();
通过允许类型由接口而不是编译器确定,您可以交换符合相同接口的不同引用(即IUserDetailRepository repo = new DapperUserDetailRepository();
此外,您处于一个名为Inversion of Control(IoC)的原则的边界,这是使用特定IoC容器( Ninject , CastleWinsor , Unity等)自动解决依赖关系的做法,从而您永远不会调用new
关键字直。
既然你提到了StructureMap,下面是一个如何工作的例子:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
IContainer container = ConfigureDependencies();
IUserDetailRepository repo = container.GetInstance<IUserDetailRepository>();
var count = repo.All.ToList().Count().ToString();
Console.WriteLine("Count: {0}", count);
Console.ReadLine();
}
private static IContainer ConfigureDependencies() {
return new Container(x =>{
x.For<IUserDetailRepository>().Use<UserDetailRepository>();
});
}
}
}