Единица работы и хранилище с платформой Entity Framework 6

На основе ответа этого вопроса я создал следующий код. Мне нужно проверить, хорошо это или нет.

Вот мой класс сущностей:

public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Designation { get; set; }
}

Это моя реализация контекста db:

 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();
}

Вот реализация:

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();
    }
}

Ниже приведен код моего контроллера в ASP.NET MVC-контроллере.

 public class EmployeeController : Controller
{
    private readonly IEmployeeService _employeeService;

    public EmployeeController()
    {
        _employeeService = new EmployeeService();
    }

    public EmployeeController(IEmployeeService employeeService)
    {
        _employeeService = employeeService;
    }

    public ActionResult Index()
    {
        return View(_employeeService.GetEmployees());
    }
}

Я хочу проверить, подходит ли это для разработки TDD Test Driven Development или нет.

42 голоса | спросил Jalpesh Vadgama 22 PMpTue, 22 Apr 2014 17:18:44 +040018Tuesday 2014, 17:18:44

2 ответа


50

Я никогда не TDD'd, но не делаю этого:

public class MyDataContext<T> : DbContext where T : class

Это дает вам контекст-сущность, которая может работать для ультра-упрощенных сценариев CRUD, но не очень хорошо масштабируется и быстро дает вам головные боли, как только вам нужно иметь дело с более чем одним типом объекта в одной транзакции - потому что это инкапсуляция : > .

DbContext - это единица работы, а IDbSet<T> - это репозиторий; они являются абстракцией; обернув его своим собственным, вы создаете абстракцию над абстракцией , и вы получаете только сложность.

Эта запись в блоге подводит итог. Вкратце: обнимать DbContext, не бороться с ним.

Если вы действительно хотите /нуждаетесь в абстракции, создайте класс DbContext, реализующий некоторый интерфейс IUnitOfWork; выведите метод Commit или SaveChanges и способ получения объектов:

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

Затем вы можете легко реализовать его:

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

Мне не нравится IEmployeeService. Это выглядит как интерфейс, способный вырастить волосы и щупальца и стать довольно монстром (GetByName, FindByEmailAddress и т. Д.) - и последнее, что вы хотите, - это интерфейс, который вам нужен для изменения все время.

Я бы сделал это примерно так, но я не хочу использовать типы сущностей непосредственно в представлениях, я бы, вероятно, предоставил сервис EmployeeModel или IEmployee code> (см. этот вопрос для более подробной информации - это WPF, но я думаю, что многие из них применимы к ASP.NET/MVC), так как только класс сервиса знает класс Employee, оставляя контроллер и представление, работающие с некоторыми IEmployee, возможно, некоторый класс EmployeeModel, идея состоит в том, чтобы отделить модель данных от модели домена .

public class EmployeeService
{
    private readonly IUnitOfWork _unitOfWork;

    public EmployeeService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    IEnumerable<Employee> GetEmployees()
    {
        return _unitOfWork.Set<Employee>().ToList();
    }
}
ответил Mathieu Guindon 22 PMpTue, 22 Apr 2014 21:53:38 +040053Tuesday 2014, 21:53:38
7

Context в этой ситуации неверен. Контекст Context должен иметь все ваши dbSets. С шаблоном UnitOfWork существует только один экземпляр Context. Он используется вашими репозиториями (DbSet s) и UnitOfWork. Поскольку существует только один экземпляр, он позволяет вам вызывать многие службы, каждый из которых обновляет ваш контекст, перед вызовом UnitOfWork.Commit(). Когда вы вызываете UnitOfWork.Commit(), все сделанные вами изменения будут переданы вместе. В приведенной выше реализации вы создаете новый Context в EmployeeService, что означает, что в другой службе вы создадите другой экземпляр Context и это неверно. Идея шаблона UnitOfWork заключается в том, что вы можете объединять службы перед фиксацией, а данные сохраняются как один UnitOfWork.

Вот мой контекст из недавнего проекта, уменьшенный по размеру. В моем IDataContext есть дополнительные определения того, что мне нужно использовать из DbContext, например:

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;
            }
        }

Вот мой UnitOfWork. Есть разные способы сделать это. В некоторых случаях люди выставляют все репозитории (DbSet s) здесь, а затем вводят UnitOfWork в свои классы и извлекают любые репозитории, в которых они нуждаются. Лично мне не нравится иметь что-то в моих сервисах, которое предоставляет все хранилище данных, поэтому я следую подходу к скрытию. Поскольку вы можете видеть, что подход, который я выполняю, имеет только Commit(). Поскольку UnitOfWork и все репозитории (DbSet s) имеют один и тот же единственный экземпляр Context, все они действуют на одни и те же данные.

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();
        }
    }

Это приводит нас к вашим классам обслуживания. Ловушка здесь заключается в том, что если вы решите ввести IDbSet s, вам нужно извлечь их из контекста и ввести их, потому что вы не можете их создать напрямую. Используя Unity в качестве IOC (я рекомендую Autofac, но должен был использовать Unity для этого проекта) выглядит следующим образом:

var context = container.Resolve<IDataContext>();
container.RegisterInstance(context.Functions, manager(typeof (ContainerControlledLifetimeManager)));
container.RegisterInstance(context.AuditRounds, manager(typeof (ContainerControlledLifetimeManager)));
container.RegisterInstance(context.Clients, manager(typeof (ContainerControlledLifetimeManager)));

Чтобы поддерживать такой код, вам нужно что-то вроде выше:

public EmployeeService(IDbSet<Employee> employee) 
    {
        this.employee = employee;
    }

В соответствии с вашей первоначальной проблемой вы не хотели что-то делать для «каждого» объекта. Лично усилия настолько малы, что я не занимаюсь им, но если это важно для вас, то существует подход GenericRepository. В этом подходе мы добавляем один экземпляр IContext, а репозиторий извлекает IDbSet из контекста, используя некоторые функции EF, и у вас есть класс вроде:

public GenericRepository<T> : IGenericRespository<T>
{
    private SchoolContext _context;

    public GenericRepository(IContext context)
    {
       _context = context;
    }

    public Get(int id)
    {
        return _context.Set<T>().Find(id);
    }
}

Затем класс службы выглядит следующим образом:

public EmployeeService(IGenericRespository<Employee> employee) 
{
    this.employee = employee;
}

Проблема с общим репозиторием заключается в том, что вам необходимо создать свои реализации для Create, Insert и Delete, а также Fetch. Это может стать уродливым, если вы попытаетесь начать использовать DbEntity и присоединить объекты к контексту через репозиторий. Например, если вы не хотите загружать запись перед ее обновлением, вам нужно будет знать, как ее присоединить и установить ее состояние в контексте. Это может быть утомительным и неприятным, потому что это просто не так прямо. Когда вы добавляете некоторые дочерние отношения и управляете дочерними коллекциями, вещи действительно идут к черту быстро. Если вам не нужно издеваться над вашими репозиториями для тестирования, я бы посоветовал вам не применять этот подход, если вы не найдете онлайн-версию, которую вы понимаете.

При всех этих решениях ключевым является понимание того, как IOC работает и настраивает ваш контейнер IOC соответственно. В зависимости от используемого вами контейнера может возникнуть путаница, как регистрировать материал, особенно при использовании дженериков. Я использую Autofac, когда только могу, потому что это простота. Я никогда не был бы Единством, кроме случаев, когда клиент настаивает.

Самый важный момент при выборе вашего подхода - убедиться, что он соответствует тому, что вам нужно. Я бы всегда сказал, что хорошо использовать шаблон в некоторой степени. Однако, если вы не пишете модульные тесты и вам не нужны классы Mock для тестирования, вы можете пропустить UnitOfWork и просто использовать свой IContext и пропустить репозитории и просто использовать DbSet s. Они выполняют одни и те же вещи. Пока вы правильно вводите вещи, вы получаете другие преимущества шаблона и чистоту вашего дизайна, но вы теряете способность имитировать объекты для тестирования.

Тогда ваш код будет выглядеть так же просто, как может:

public EmployeeService(IContext context) 
{
     this.employees = context.Employees;
}
ответил user3334137 22 PMpTue, 22 Apr 2014 19:11:53 +040011Tuesday 2014, 19:11:53

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132