这是我的实体类:
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Designation { get; set; }
}
这是我的数据库上下文实现:
public class MyDataContext<T> : DbContext where T:class
{
private IDbSet<T> _dbSet;
public MyDataContext() : base("name=DefaultConnectionString")
{
_dbSet = this.Set<T>();
}
public MyDataContext(IDbSet<T> dbSet )
: base("name=DefaultConnectionString")
{
this._dbSet = dbSet;
}
public IDbSet<T> DbSetOjbect
{
get { return _dbSet; }
}
}
现在我已经实现了
EmployeeService
业务逻辑类和IEmployee
服务类:实现: public interface IEmployeeService
{
List<Employee> GetEmployees();
}
以下是我在ASP.NET MVC控制器中的控制器代码。
public class EmployeeService : IEmployeeService
{
private IDbSet<Employee> employee;
public EmployeeService()
{
var employeeContext = new MyDataContext<Employee>();
employee = employeeContext.DbSetOjbect;
}
public EmployeeService(IDbSet<Employee> employee)
{
this.employee = employee;
}
public List<Employee> GetEmployees()
{
return employee.ToList();
}
}
我想检查它是否是TDD测试驱动开发的好方法。
#1 楼
我从没做过TDD,但不要这样做:public class MyDataContext<T> : DbContext where T : class
这为您提供了一个实体上下文,可能适用于极其简单的CRUD方案,但扩展性不佳,一旦您需要在单个事务中处理多个实体类型,就会很快使您头疼-因为这就是一个
DbContext
是一个工作单元,而IDbSet<T>
是一个存储库;它们是抽象的;通过用自己的包装将其包装起来,就可以对一个抽象进行抽象,而除了复杂性之外,您什么都没有得到。简而言之:拥抱DbContext,不要吵架。公开DbContext
或IUnitOfWork
方法以及获取实体的方法:public interface IUnitOfWork
{
void Commit();
IDbSet<T> Set<T>() where T : class;
}
然后您可以轻松实现它: >
我也不喜欢
Commit
。这看起来像一个可以长出头发和触手并变成怪物的接口(SaveChanges
,IEmployeeService
等),而您想要的最后一件事就是需要随时更改的接口。我会这样做,但我不愿意在视图中直接使用实体类型,我可能会让服务公开GetByName
或FindByEmailAddress
(有关更多详细信息,请参阅此问题-它是WPF,但我认为它很多都适用于ASP.NET/MVC),以便仅使服务类知道EmployeeModel
类,而使控制器和视图无法完成某些IEmployee
实现,可能是某些Employee
类,想法是分离数据模型来自域模型。public class MyDataContext : DbContext, IUnitOfWork
{
public void Commit()
{
SaveChanges();
}
}
#2 楼
在这种情况下的Context
不正确。该Context
应该拥有所有dbSets
。使用UnitOfWork
模式时,只有1个Context
实例。您的存储库(DbSet
)和UnitOfWork
使用它。因为只有一个实例,所以它允许您调用许多服务,每个服务都会在调用UnitOfWork.Commit()
之前更新您的上下文。致电UnitOfWork.Commit()
时,您所做的所有更改将一起提交。在上述实现中,您将在Context
中创建一个新的EmployeeService
,这意味着在另一项服务中,您将最终创建Context
的另一个实例,这是不正确的。 UnitOfWork
模式背后的想法是,您可以在提交之前将服务链接在一起,并将数据保存为单个UnitOfWork
。 这是我最近的项目的背景,尺寸减小了。我的
IDataContext
还有一些我需要使用的其他定义,例如:public interface IDataContext : IDisposable
{
DbChangeTracker ChangeTracker { get; }
int SaveChanges();
DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
DbSet<TEntity> Set<TEntity>() where TEntity : class;
DbSet Set(Type entityType);
int> SaveChanges();
public IDbSet<Function> Functions { get; set; }
public IDbSet<PlaceHolder> PlaceHolders { get; set; }
public IDbSet<Configuration> Configurations { get; set; }
public IDbSet<Client> Clients { get; set; }
public IDbSet<ParentClient> ParentClients { get; set; }
}
public class DataContext : DbContext, IDataContext
{
public DataContext()
{
Configurations = Set<Configuration>();
Clients = Set<Client>();
ParentClients = Set<ParentClient>();
}
public IDbSet<Function> Functions { get; set; }
public IDbSet<PlaceHolder> PlaceHolders { get; set; }
public IDbSet<Configuration> Configurations { get; set; }
public IDbSet<Client> Clients { get; set; }
public IDbSet<ParentClient> ParentClients { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
new UserConfiguration().AddConfiguration(modelBuilder.Configurations);
new ParentClientConfiguration().AddConfiguration(modelBuilder.Configurations);
new ClientConfiguration().AddConfiguration(modelBuilder.Configurations);
new EmailConfiguration().AddConfiguration(modelBuilder.Configurations);
Configuration.LazyLoadingEnabled = false;
}
}
这是我的
DbContext
。有不同的方法可以做到这一点。在某些情况下,人们在此处公开所有存储库(UnitOfWork
),然后将DbSet
注入其类中并提取所需的任何存储库。就个人而言,我不喜欢在服务中使用某些东西来暴露整个数据存储,因此我采用隐藏方法。如您所见,我遵循的方法只有一个UnitOfWork
。由于Commit()
和所有存储库(UnitOfWork
)共享DbSet
的相同单个实例,因此它们都作用于相同的数据。服务类别。这里的问题是,如果您选择注入Context
,则必须从上下文中提取它们并注入它们,因为您不能直接创建它们。使用Unity作为IOC(我建议使用Autofac,但必须在该项目中使用Unity),它看起来像这样:为了支持这种代码,您需要执行类似上面的操作: “ 实体。就个人而言,这种工作量很小,我不关心它,但是如果这对您很重要,则可以使用
IDbSet
方法。在这种方法中,我们注入了GenericRepository
的单个实例,并且存储库使用一些EF功能从上下文中提取了IContext
,并且您拥有类似的类:服务类如下所示:通用存储库的问题在于,您必须创建用于创建,插入和删除以及提取的实现。如果您尝试开始使用IDbSet
并通过存储库将实体附加到上下文,则此操作很快就会变得很丑陋。例如,如果您不想在更新之前加载记录,则必须知道如何附加记录并在上下文中设置其状态。这可能是乏味且麻烦的,因为它不是那么简单。一旦添加了一些子关系并管理了子集合,事情就真的很快就死了。如果您不需要模拟存储库进行测试,则建议您不要使用此方法,除非您找到自己了解的在线实现。使用所有这些解决方案,关键是了解IOC的工作原理并配置您的相应地,IOC容器。根据您使用的容器,它可能使注册内容变得非常混乱,尤其是在涉及泛型时。由于简单,我会尽可能使用Autofac。除非客户坚持,否则我永远不会团结。
选择您的方法时,最重要的一点是确保它符合您的需求。我总是会说在某种程度上使用该模式是很好的。但是,如果您不编写单元测试并且不需要模拟类进行测试,则可以跳过
DbEntity
并仅使用UnitOfWork
并跳过存储库并仅使用IContext
。他们完成同样的事情。只要注入正确的东西,您就可以在设计中获得图案和清洁度的其他好处,但是您将无法模拟对象进行测试。非常简单:public interface IUnitOfWork : IDisposable
{
ICollection<ValidationResult> Commit();
}
public class UnitOfWork : IUnitOfWork
{
private readonly IDataContext _context;
public UnitOfWork(IDataContext context)
{
_context = context;
}
public ICollection<ValidationResult> Commit()
{
var validationResults = new List<ValidationResult>();
try
{
_context.SaveChanges();
}
catch (DbEntityValidationException dbe)
{
foreach (DbEntityValidationResult validation in dbe.EntityValidationErrors)
{
IEnumerable<ValidationResult> validations = validation.ValidationErrors.Select(
error => new ValidationResult(
error.ErrorMessage,
new[]
{
error.PropertyName
}));
validationResults.AddRange(validations);
return validationResults;
}
}
return validationResults;
}
public void Dispose()
{
_context.Dispose();
}
}
评论
\ $ \ begingroup \ $
您看到我发布的问题链接了吗?在其中明确提到了实体框架6.0已在内部实现的工作单元。您提供的解决方案在实体框架5.0之前是正确的,但在6.0之前不再适用
\ $ \ endgroup \ $
– Jalpesh Vadgama
14年4月22日在16:59
评论
\ $ \ begingroup \ $
实体框架6.0具有内置的工作单元表示形式,请参阅我发布的带有问题的链接
\ $ \ endgroup \ $
– Jalpesh Vadgama
2014年4月22日在18:04
\ $ \ begingroup \ $
我做到了。而且我不同意创建通用的IRepository
\ $ \ endgroup \ $
– Mathieu Guindon♦
2014年4月22日在18:12
\ $ \ begingroup \ $
好的,谢谢!!我将检查它是否可以与TDD一起使用
\ $ \ endgroup \ $
– Jalpesh Vadgama
2014年4月22日18:39
\ $ \ begingroup \ $
就像公共IDbSet
\ $ \ endgroup \ $
– Jalpesh Vadgama
2014年4月24日在11:32
\ $ \ begingroup \ $
如果您有一个应用程序,其中所有内容都托管在单个MS SQL数据库中,则此答案是正确的。不要那样做但是,存储库并不总是在MS SQL数据库中。因此,微服务开始出现,每个实体需要一个上下文,从而允许实体存在于任何地方:云服务,数据库,SAP,Salesforce,NoSQL,Xml,Excel,无论在哪里。导航属性不是由EF处理的,而是由单独的单独JSON调用或CompoundMicroservice(可汇总两个或更多微服务)处理的。在微服务架构中,可能需要这种设计。
\ $ \ endgroup \ $
–琉璃
16年12月21日在20:56