11

He leído tantas publicaciones sobre Stackoverflow como puedo encontrar con respecto al uso de un patrón de unidad de trabajo dentro de una aplicación ASP.Net MVC 3 que incluye una capa comercial. Sin embargo, todavía tengo un par de preguntas con con respecto a este tema y agradecería mucho cualquier comentario que la gente pueda darme.¿Dónde debería crear la instancia de unidad de trabajo en una aplicación ASP.Net MVC 3?

Estoy desarrollando una aplicación web ASP.Net MVC 3 que utiliza EF 4.1. Me va a utilizar tanto en el repositorio y Unidad de Patrones de Trabajo con este proyecto similar a la forma en que se utilizan en this gran tutorial

La diferencia en mi proyecto es que necesito para incluir también una capa empresarial (proyecto separado en mi solución) con el fin de llevar a cabo las diversas reglas de negocio para la aplicación. El tutorial se mencionó anteriormente no tiene una capa de negocios, y por lo tanto, crea una instancia de la Unidad de Trabajo de clase del controlador

public class CourseController : Controller 
{ 
    private UnitOfWork unitOfWork = new UnitOfWork(); 

Sin embargo, mi pregunta es, ¿dónde debería crear la instancia de la unidad de trabajo clase si tengo una Business Layer?

yo personalmente creo que debería ser creado en mi controlador y luego se inyecta en la capa de negocio de esta manera:

public class PeopleController : Controller 
{ 
    private readonly IUnitOfWork _UoW; 
    private IPersonService _personService; 

    public PeopleController() 
    { 
     _UoW = new UnitOfWork(); 
     _personService = new PersonService(_UoW); 
    } 

    public PeopleController(IUnitOfWork UoW, IPersonService personService) 
    { 
     _UoW = UoW; 
     _personService = personService; 

    } 

    public ActionResult Edit(int id) 
    { 
     Person person = _personService.Edit(id); 
     return View(person); 
    } 

public class UnitOfWork : IUnitOfWork, IDisposable 
{ 
    private BlogEntities _context = new BlogEntities(); 
    private PersonRepository personRepository = null; 

    public IPersonRepository PersonRepository 
    { 
     get 
     { 

      if (this.personRepository == null) 
      { 
       this.personRepository = new PersonRepository(_context); 
      } 
      return personRepository; 
     } 
    } 

    public void Save() 
    { 
     _context.SaveChanges(); 
    } 


public class PersonService : IPersonService 
{ 
    private readonly IUnitOfWork _UoW; 

    public PersonService(IUnitOfWork UoW) 
    { 
     _UoW = UoW; 
    } 

    public Person Edit(int id) 
    { 
     Person person = _UoW.PersonRepository.GetPersonByID(id); 
     return person; 
    } 

public class PersonRepository : IPersonRepository 
{ 
    private readonly BlogEntities _context; 

    public PersonRepository(BlogEntities context) 
    { 
     _context = context; 
    } 

    public Person GetPersonByID(int ID) 
    { 
     return _context.People.Where(p => p.ID == ID).Single(); 
    } 

He leído otros diciendo que la Unidad de instanciación El trabajo no debe estar en el controlador, pero creado en Service Layer en su lugar. La razón por la que no estoy tan seguro acerca de este enfoque es porque mi controlador puede tener que usar varias capas de servicio en una transacción comercial, y si la instancia de unidad de trabajo se creó dentro de cada servicio, resultaría en varias unidades de Se crean instancias de trabajo que anulan el objetivo, es decir, una unidad de trabajo por transacción comercial.

Tal vez lo que he explicado anteriormente es incorrecto, pero si es así, agradecería mucho si alguien pudiera corregirme.

Gracias de nuevo por su ayuda.

Respuesta

15

Creo que tiene que hacer un par de cambios:

  1. Permitir su contenedor DI para inyectar una instancia UnitOfWork en sus clases de servicio en sus constructores, y dejarlo fuera de su controlador completo.

  2. Si su contenedor DI lo admite (Ninject sí, por ejemplo), configure su UnitOfWork para que se administre por solicitud; de esta forma, sus servicios recibirán un distintivo UnitOfWork por cada solicitud, y listo. O bien ...

  3. Si su contenedor DI no es compatible con las duraciones por solicitud, configúrelo para administrar el UnitOfWork como singleton, por lo que cada clase Service obtiene la misma instancia. A continuación, actualice su UnitOfWork para almacenar su objeto Entities en un almacén de datos que almacena objetos según cada solicitud, por ejemplo en HttpContext.Current.Items, como se describe en here.

Editar 1

cuanto a donde debe inyectarse la UnitOfWork; Yo diría que la capa de Servicio es el lugar correcto. Si imagina su sistema como una serie de capas donde las capas externas se ocupan de las interacciones del usuario y las capas inferiores tienen que ver con el almacenamiento de datos, cada capa debería preocuparse menos por los usuarios y más por el almacenamiento de datos. UnitOfWork es un concepto de una de las capas de "nivel inferior" y el controlador es de una capa de nivel superior; su capa Service cabe entre ellos. Por lo tanto, tiene sentido poner el UnitOfWork en la clase Service en lugar del Controller.

Editar 2

Para más detalles sobre la creación UnitOfWork y su relación con HttpContext.Current.Items:

Su UnitOfWork ya no contener una referencia a un objeto Entities, que se llevaría a cabo a través del objeto HttpContext, inyectado en el UnitOfWork detrás de una interfaz como esta:

public interface IPerRequestDataStore : IDisposable 
{ 
    bool Contains(string key); 

    void Store<T>(string key, T value); 

    T Get<T>(string key); 
} 

El objeto HttpContext entonces aplicar IPerRequestDataStore así:

public class StaticHttpContextPerRequestDataStore : IPerRequestDataStore 
{ 
    public bool Contains(string key) 
    { 
     return HttpContext.Current.Items.Contains(key); 
    } 

    public void Store<T>(string key, T value) 
    { 
     HttpContext.Current.Items[key] = value; 
    } 

    public T Get<T>(string key) 
    { 
     if (!this.Contains(key)) 
     { 
      return default(T); 
     } 

     return (T)HttpContext.Current.Items[key]; 
    } 

    public void Dispose() 
    { 
     var disposables = HttpContext.Current.Items.Values.OfType<IDisposable>(); 

     foreach (var disposable in disposables) 
     { 
      disposable.Dispose(); 
     } 
    } 
} 

Como acotación al margen, la he llamado StaticHttpContextPerRequestDataStore, ya que utiliza la propiedad estática HttpContext.Current; eso no es ideal para pruebas unitarias (otro tema en total), pero al menos el nombre indica la naturaleza de su dependencia.

Su UnitOfWork continuación, pasa el IPerRequestDataStore que se le da a cada uno de sus Repository objetos para que puedan acceder al Entities; esto significa que no importa cuántas instancias UnitOfWork cree, usará el mismo objeto Entities en una solicitud porque se almacena y recupera en el IPerRequestDataStore.

Tendrías una base abstracta Repository que utilizaría su IPerRequestDataStore a lazy-cargar su objeto Entities así:

public abstract class RepositoryBase : IDisposable 
{ 
    private readonly IPerRequestDataStore _dataStore; 

    private PersonRepository personRepository; 

    protected RepositoryBase(IPerRequestDataStore dataStore) 
    { 
     this._dataStore = dataStore; 
    } 

    protected BlogEntities Context 
    { 
     get 
     { 
      const string contextKey = "context"; 

      if (!this._dataStore.Contains(contextKey)) 
      { 
       this._dataStore.Store(contextKey, new BlogEntities()); 
      } 

      return this._dataStore.Get<BlogEntities>(contextKey); 
     } 
    } 

    public void Dispose() 
    { 
     this._dataStore.Dispose(); 
    } 
} 

Su PeopleRepository (por ejemplo) se vería así:

public class PeopleRepository : RepositoryBase, IPersonRepository 
{ 
    public PeopleRepository(IPerRequestDataStore dataStore) 
     : base(dataStore) 
    { 
    } 

    public Person FindById(int personId) 
    { 
     return this.Context.Persons.FirstOrDefault(p => p.PersonId == personId); 
    } 
} 

Y finalmente, aquí está la creación de su PeopleController:

IPerRequestDataStore dataStore = new StaticHttpContextDataStore(); 
UnitOfWork unitOfWork = new UnitOfWork(dataStore); 
PeopleService service = new PeopleService(unitOfWork); 
PeopleController controller = new PeopleController(service); 

Uno de los conceptos centrales aquí es que los objetos tienen sus dependencias inyectadas en ellos a través de sus constructores; esto generalmente se acepta como una buena práctica, y más fácilmente le permite componer objetos de otros objetos.

+0

Gracias por esto Steve. Actualmente, nunca he usado o implementado un contenedor DI, aunque definitivamente no estoy descartando. Sin embargo, para esta aplicación que estoy construyendo, si elijo no implementar un contenedor DI y me quedé con DI manual, ¿estaría bien utilizar el código que he mencionado en mi pregunta (en particular, inyectar la UoW en el Servicio)? – tgriffiths

+0

Sin duda puede conectar todo manualmente; en ese caso, debe usar la segunda opción de crear un objeto que utiliza 'HttpContext.Current.Items' para almacenar su objeto Entidades según cada solicitud. Ya sea que uses un contenedor o no, aún diría que el 'UnitOfWork' debería ir a la capa de servicio; He actualizado mi respuesta para aclarar esto un poco :) –

+0

Gracias de nuevo Steve, ¡aunque parece que estoy cada vez más confundido! Usted dice que necesitaré crear un objeto que use HttpContext.Current.Items para almacenar mis Entidades según cada solicitud, pero mi ObjectContext almacenará/administrará mis Entidades y se creará dentro de mi Unidad de La clase de trabajo y la UoW solo existirán por solicitud HTTP. – tgriffiths

Cuestiones relacionadas