基于对这个问题的回答,我创建了以下代码。我需要检查它是否好。

这是我的实体类:

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,不要吵架。公开DbContextIUnitOfWork方法以及获取实体的方法:

public interface IUnitOfWork
{
    void Commit();
    IDbSet<T> Set<T>() where T : class;
}


然后您可以轻松实现它: >
我也不喜欢Commit。这看起来像一个可以长出头发和触手并变成怪物的接口(SaveChangesIEmployeeService等),而您想要的最后一件事就是需要随时更改的接口。我会这样做,但我不愿意在视图中直接使用实体类型,我可能会让服务公开GetByNameFindByEmailAddress(有关更多详细信息,请参阅此问题-它是WPF,但我认为它很多都适用于ASP.NET/MVC),以便仅使服务类知道EmployeeModel类,而使控制器和视图无法完成某些IEmployee实现,可能是某些Employee类,想法是分离数据模型来自域模型。

public class MyDataContext : DbContext, IUnitOfWork
{
    public void Commit()
    {
        SaveChanges();
    }
}


评论


\ $ \ begingroup \ $
实体框架6.0具有内置的工作单元表示形式,请参阅我发布的带有问题的链接
\ $ \ endgroup \ $
– Jalpesh Vadgama
2014年4月22日在18:04

\ $ \ begingroup \ $
我做到了。而且我不同意创建通用的IRepository 接口,更不用说使用通用的DbContext 创建接口,出于此答案和链接博客中所述的原因。
\ $ \ 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

#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