Unidad de trabajo y repositorio con marco de entidad 6 -- # campo con entity-framework campo con asp.net-mvc camp codereview Relacionados El problema

Unit of Work and Repository with Entity Framework 6


46
vote

problema

Español

Basado en la respuesta de este Pregunta He creado el siguiente código. Necesito comprobar si es bueno o no.

Aquí está mi clase de entidad:

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

Esta es mi implementación de contexto de 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; }     } }   

Ahora he implementado el 9988777665544335 Business Logic Class y el IEmployee Clase de servicio:

   public interface IEmployeeService {     List<Employee> GetEmployees(); }   

Aquí está la implementación:

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

El siguiente es mi código de controlador en el controlador MVC ASP.NET.

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

Quiero verificar si es un buen enfoque para el desarrollo impulsado por la prueba de TDD o no.

Original en ingles

Based on the reply of this question I have created the following code. I need to check whether it's good or not.

Here is my entity class:

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

This is my db context implementation:

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

Now I have implemented the EmployeeService business logic class and the IEmployee service class:

 public interface IEmployeeService {     List<Employee> GetEmployees(); } 

Here is the implementation:

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

The following is my controller code in ASP.NET MVC controller.

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

I want to check whether it is a good approach for TDD Test Driven Development or not.

        

Lista de respuestas

8
 
vote

El DbContext8 en esta situación no es correcta. El DbContext9 debe tener todo su IDbSet<T>0 . Con el Patrón IDbSet<T>1655443321 Solo hay 1 instancia del IDbSet<T>2 . Sus repositorios son utilizados por sus repositorios ( 99887776655443323 S) y por el IDbSet<T>4 . Dado que solo hay una sola instancia, le permite llamar a muchos servicios, cada uno de los cuales actualice su contexto, antes de llamar a IDbSet<T>5 . Cuando llame IDbSet<T>6 Todos los cambios que ha realizado se enviarán juntos. En su implementación anterior, está creando un nuevo 998877766655443327 en el IDbSet<T>8 que significa en otro servicio que terminará creando otra instancia del IDbSet<T>9 y Eso es incorrecto. La idea detrás del patrón DbContext0 puede encadenar los servicios juntos antes de cometer y los datos se guardan como un solo DbContext1 .

Aquí está mi contexto de un proyecto reciente, reducido en tamaño. Mi DbContext2 tiene algunas definiciones adicionales para lo que necesito usar desde DbContext3 como:

  DbContext4  

Aquí está mi DbContext5 . Hay diferentes formas de hacer esto. En algunos casos, las personas exponen todos los repositorios ( DbContext6 ) aquí, y luego inyectan el DbContext7 en sus clases y extraiga cualquier repositorios que necesitan. Personalmente, no me gusta tener algo en mis servicios que expone a toda la tienda de datos, así que sigo el enfoque de ocultación. Como puede ver, el enfoque que estoy siguiendo solo tiene un 998877766555443338 . Dado que el DbContext9 y todos los repositorios ( 99887766555443340 S) Compartir la misma instancia única del IUnitOfWork1 están actuando en los mismos datos. < / p>

  IUnitOfWork2  

Esto nos lleva a sus clases de servicio. La captura aquí es que si elige inyectar IUnitOfWork3 s, entonces tiene que extraerlos del contexto e inyectarlos porque no puede crearlos directamente. Usando la unidad como la COI (recomiendo AUTOFAC, pero tuve que usar la unidad para este proyecto). Parece esto:

  IUnitOfWork4  

Para poder apoyar este tipo de código, necesitará algo como lo que está arriba:

  IUnitOfWork5 

por su preocupación original, no querrá hacer algo por "cada entidad". Personalmente, el esfuerzo es tan pequeño. No me preocupa con él, pero si eso es importante para usted, entonces está el enfoque 998877666555443346 . En ese enfoque, inyectamos esa única instancia del IUnitOfWork7 y el repositorio extrae el IUnitOfWork8 del contexto utilizando alguna funcionalidad de EF y tiene una clase como:

  IUnitOfWork9 

Luego, la clase de servicio se ve así:

  Commit0  

El problema con el repositorio genérico es que tiene que crear sus implementaciones para crear, insertar y eliminar también. Esto puede obtener feo rápidamente si intenta comenzar a usar Commit1 y adjuntar entidades al contexto a través del repositorio. Por ejemplo, si no quiso cargar el registro antes de actualizarlo, tendría que saber cómo adjuntarlo y configurar su estado en el contexto. Esto puede ser tedioso y problemático porque simplemente no es tan directo. Una vez que agregue en algunas relaciones infantiles y administre las colecciones de niños, las cosas realmente van al infierno rápido. Si no necesita burlarse de sus repositorios para las pruebas, aconsejaría este enfoque a menos que encuentre una implementación en línea que entienda.

Con todas estas soluciones, la clave está comprendiendo cómo funciona IOC y configurando su contenedor de IOC en consecuencia. Dependiendo del contenedor que utilice, puede obtener realmente confundiendo cómo registrar cosas, especialmente cuando los genéricos están involucrados. Yo uso automofac siempre que pueda por su sencillez. Nunca la unidad excepto cuando el cliente insiste.

El punto más importante al elegir su enfoque es asegurarse de que esté en línea con lo que necesita. Siempre diría que es bueno usar el patrón hasta cierto punto. Sin embargo, si no está escribiendo pruebas de unidad y no necesita simularse clases para las pruebas, podría omitir el 99887766655443352 y simplemente use su 99887766655443353 y omita los repositorios y solo use Commit4 s. Ellos cumplen lo mismo cosas. Mientras esté inyectando cosas correctamente, obtendrá los otros beneficios del patrón y la limpieza de su diseño, pero pierde la capacidad de burlarse de los objetos para las pruebas.

Luego, su código se vería así que es tan simple como puede obtener:

  Commit5 

 

The Context in this situation isn't correct. The Context should have all your dbSets. With the UnitOfWork pattern there is only 1 instance of the Context. It is used by your repositories (DbSets) and by the UnitOfWork. Since there is only a single instance it allows you to call many services, each of which update your context, before calling UnitOfWork.Commit(). When you call UnitOfWork.Commit() all the changes you've made will get submitted together. In your above implementation you are creating a new Context in the EmployeeService which means in another service you will end up creating another instance of the Context and that is incorrect. The idea behind the UnitOfWork pattern is that you can chain together services before committing and the data gets saved as a single UnitOfWork.

Here's my context from a recent project, reduced in size. My IDataContext has some additional definitions for what I need to use from DbContext like:

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

Here's my UnitOfWork. There are different ways of doing this. In some cases people expose all the repositories (DbSets) here, and then inject the UnitOfWork into their classes and extract whatever repositories they need. Personally I don't like having something in my services that exposes the entire data store so I follow the hiding approach. As you can see the approach I'm following only has a Commit(). Since the UnitOfWork and all the repositories (DbSets) share the same single instance of the Context they are all acting on the same data.

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

This brings us to your service classes. The catch here is that if you choose to inject IDbSets then you have to extract them from the context and inject them because you can't create them directly. Using Unity as the IOC (I recommend Autofac but had to use Unity for this project) it looks like this:

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

In order to support this kind of code you'll need to something like what is above:

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

Per your original concern you were not wanting to do something for "every" entity. Personally the effort is so small I don't concern myself with it but if that's important to you then there is the GenericRepository approach. In that approach we inject that single instance of the IContext and the repository extracts the IDbSet from the context using some EF functionality and you have a class like:

public GenericRepository<T> : IGenericRespository<T> {     private SchoolContext _context;      public GenericRepository(IContext context)     {        _context = context;     }      public Get(int id)     {         return _context.Set<T>().Find(id);     } } 

Then the service class looks like this:

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

The problem with the generic repository is that you have to create your implementations for Create, Insert, and Delete as well Fetch. This can get ugly quickly if you try to start using DbEntity and attaching entities to the context through the repository. For example if you didn't want to load the record before updating it you would have to know how to attach it and set it's state in the context. This can be tedious and troublesome because it's just not that straight forward. Once you add in some child relationships and managing child collections things really go to hell fast. If you're not needing to Mock your repositories for testing I would advise against this approach unless you find an implementation online that you understand.

With all these solutions the key is understanding how IOC works and configuring your IOC container accordingly. Depending on the container you use it can get really confusing how to register stuff, especially when generics are involved. I use Autofac whenever I can because of it's simplicity. I would never Unity except when the client insists.

The most important point when choosing your approach is to make sure it's in line with what you need. I would always say it's good to use the pattern to some degree. However if you are not writing unit tests and not needing to Mock classes for testing then you could skip the UnitOfWork and just use your IContext and skip the Repositories and just use DbSets. They accomplish the same things. As long as you are injecting things properly you get the other benefits of the pattern and the cleanliness in your design but you lose the ability to Mock the objects for testing.

Then your code would look like this which is as simple as it can get:

public EmployeeService(IContext context)  {      this.employees = context.Employees; } 
 
 
 
 
53
 
vote
vote
La mejor respuesta
 

Nunca he tdd'd, pero no hagas eso:

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

Esto le da una entidad contextual, que podría funcionar para escenarios de crud ultra simplistas, pero no se escala muy bien y le dará a los dolores de cabeza tan pronto como necesite lidiar con más de un tipo de entidad única En una sola Transaction , porque eso es lo que una unidad de trabajo se encapsula: A Transaction .

DbContext es Un unidad de trabajo, y 9988776655544332 es un repositorio; ellos son una abstracción; Al envolverlo con el suyo, estás haciendo una abstracción sobre una abstracción , y no obtienes nada más que complejidad.

esta entrada de blog lo resume bastante bien. En pocas palabras: abrazar dbcontext, no luches.

Si realmente desea / necesita una abstracción, haga su DbContext3 Clase Implementar algunos IUnitOfWork Interfaz; Exponga un método 99887776655443355544335 SaveChanges655443366554433665544336

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

Luego, puede implementarlo fácilmente:

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

No me gusta IEmployeeService tampoco. Esto parece una interfaz que puede crecer el cabello y los tentáculos y se vuelve bastante monstruo ( 998877766555443310 , 99887766555443311 , etc.) - y lo último que desea es una interfaz que necesita para cambiar todo el tiempo.

Lo haría algo así, pero me reacio a usar los tipos de entidades directamente en las vistas, probablemente tendría el servicio Expondir 99887776555443312 o 99887776655443313 En su lugar (consulte esta pregunta para más detalles - es wpf, Pero creo que mucho de ella se aplica a ASP.NET/MVC), a fin de tener solo a la clase de servicio de la clase 99887766655443314 , dejando el controlador y la vista que trabaja en un lugar DbContext5 < / Código> Implementación, probablemente un poco DbContext6 clase, idea de separar el modelo de datos del modelo de dominio .

.
  DbContext7  

 

I've never TDD'd, but don't do that:

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

This gives you a context-per-entity, which might work for ultra-simplistic CRUD scenarios, but doesn't scale very well and will quickly give you headaches as soon as you need to deal with more than a single entity type in a single transaction - because that's what a unit-of-work encapsulates: a transaction.

DbContext is a unit-of-work, and IDbSet<T> is a repository; they are an abstraction; by wrapping it with your own, you're making an abstraction over an abstraction, and you gain nothing but complexity.

This blog entry sums it up pretty well. In a nutshell: embrace DbContext, don't fight it.

If you really want/need an abstraction, make your DbContext class implement some IUnitOfWork interface; expose a Commit or SaveChanges method and a way to get the entities:

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

Then you can easily implement it:

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

I don't like IEmployeeService either. This looks like an interface that can grow hair and tentacles and become quite a monster (GetByName, FindByEmailAddress, etc.) - and the last thing you want is an interface that needs to change all the time.

I'd do it something like this, but I'm reluctant to use the entity types directly in the views, I'd probably have the service expose EmployeeModel or IEmployee instead (see this question for more details - it's WPF, but I think lots of it applies to ASP.NET/MVC), so as to only have the service class aware of the Employee class, leaving the controller and the view working off some IEmployee implementation, probably some EmployeeModel class, idea being to separate the data model from the domain model.

public class EmployeeService {     private readonly IUnitOfWork _unitOfWork;      public EmployeeService(IUnitOfWork unitOfWork)     {         _unitOfWork = unitOfWork;     }      IEnumerable<Employee> GetEmployees()     {         return _unitOfWork.Set<Employee>().ToList();     } } 
 
 
         
         

Relacionados problema

2  Lógica de negocios en controlador  ( Business logic in controller ) 
LED por la premisa recientemente leída "Controladores magros y modelos de grasa" He llegado a la conclusión de que mi controlador podría ser demasiado gordo ...

6  Mono alrededor con mono encuesta y ASP.NET MVC  ( Monkeying around with survey monkey and asp net mvc ) 
Intro He sido un desarrollo de escritorio durante mucho tiempo, y nunca he tenido que Mono con el desarrollo web hasta hace muy poco. Tengo la necesidad de ...

14  Pruebas de repositorio y controlador  ( Repository and controller tests ) 
últimamente he estado investigando cómo la mejor unidad prueba un repositorio de EF y le ha dado un repositorio adecuadamente probado, qué para probar en el...

1  Vista parcial para listas desplegables de uso frecuente  ( Partial view for frequently used drop down lists ) 
Tengo una entrada que se necesita en muchas formas, así que creé una vista parcial para eso. En la vista parcial, consulta la base de datos y generaré una lis...

1  Guardar datos a una sesión  ( Saving data to a session ) 
Estoy un poco confundido Si ahorrar la información al código de sesión a continuación, pertenece a la acción del controlador como se muestra a continuación o ...

3  Master Detalle Insertar en Entity Framework 6 Base de datos Primer MVC 5  ( Master detail insert in entity framework 6 database first mvc 5 ) 
Tengo un DB con una tabla maestra llamada "Facturas" y otra tabla de detalles "Facturas_Detalle". Me gustaría insertarlos, por lo que esta es una "base de dat...

3  Seguimiento simple de usuarios en línea en ASP.NET  ( Simple tracking online users in asp net ) 
Escribí un seguimiento de usuarios simples en línea para mi proyecto ASP.NET MVC. en global.asax, agregué: protected void Session_Start(Object sender, ...

23  ¿Cómo escribir elegantes trozos condicionales de marcado en vistas a la pista de afeitar?  ( How to write elegant conditional bits of markup in razor views ) 
Supongamos el siguiente modelo: static void Main(string[] args) { var subject = new Subject<string>(); var my = subject.GroupBy(x => x); my.S...

3  Carro de la compra AJAX con cantidad +/- Botones  ( Ajax shopping cart with quantity buttons ) 
Soy un desarrollador junior (Pareja de años) y muy nuevo en AJAX. Estoy escribiendo una solicitud de inventario interna construida en MVC y en este momento ...

5  Repositorio genérico para aplicaciones web  ( Generic repository for web apps ) 
Estaba desarrollando una aplicación web usando entity Framework 6 y MVC 5. Para la capa de acceso a datos, alivié el trabajo y escribí un repositorio genérico...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos