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交换它。我如何实现这一目标?
感谢您阅读这个大问题,如果有人能指出我正确的方向,我真的很感激。
答案 0 :(得分:3)
问题:我的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交换它。我如何实现这一目标?
您想拔掉您的依赖关系解析器以插入另一个吗?没问题,只需在编码DR的配置时进行预测,以便与您的应用程序保持最小耦合。在某些情况下,有一些限制耦合的技巧......在我目前正在开发的项目中,我们首先有一个MVC应用程序和一个服务层,业务层,数据访问层和基础设施层。我们使用Ninject作为DR,并且基础结构和Web UI层是唯一具有Ninject参考的层。拔掉它很容易,我们已经用这种方式尝试过Unity。
还有一件事,你不应该有UserDetail的合同。没有必要,在无状态类上使用依赖注入,而不是在DTO之类的所有类上使用。
答案 1 :(得分:2)
如果使用隐式变量类型而不是显式变量类型(即消除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>();
});
}
}
}
答案 2 :(得分:0)
简言之:
您的模型依赖IRepository
(IRepository的实现可以是任何东西,Dapper,EF,ADO.Net等)来持久化数据,进行查询等。您的模型具有业务规则。
您的视图(控制台,WPF,Web)依赖于视图和模型之间的层,presenter
(MVP),controller
(MVC)或viewmodel
(MVVM) )。
该中间层与Model一起使用以保存数据。
您可以使用依赖点来使用DI。
单元测试可以应用于任何图层,但请确保专门涵盖业务规则。
IUserDetailRepository
看起来像一个存储库,你的模型应该使用它。这样就可以将数据库实现分离到接口的抽象之后,如前所述,真正的实现可能是EF,dapper等。
在MVC模型中,控制器调用Model来应用业务规则并保留数据。