entity-framework - migrations - entity framework tutorial español
Primer código de Entity Framework 6: ¿la implementación del repositorio es buena? (7)
Estoy a punto de implementar un diseño de Entity Framework 6 con un repositorio y una unidad de trabajo.
Hay tantos artículos alrededor y no estoy seguro de cuál es el mejor consejo: por ejemplo, me gusta mucho el patrón implementado aquí: por las razones sugeridas en el artículo here
Sin embargo, Tom Dykstra (Senior Programming Writer on Microsoft''s Web Platform & Tools Content Team)
sugiere que debería hacerse en otro artículo: here
Me suscribo a Pluralsight
, y se implementa de una manera ligeramente diferente prácticamente cada vez que se usa en un curso, por lo que es difícil elegir un diseño.
Algunas personas parecen sugerir que la unidad de trabajo ya está implementada por DbContext
como en esta post , por lo que no deberíamos necesitar implementarla en absoluto.
Me doy cuenta de que este tipo de pregunta se ha hecho antes y esto puede ser subjetivo, pero mi pregunta es directa:
Me gusta el enfoque en el primer artículo (Code Fizzle) y quería saber si es quizás más fácil de mantener y tan fácilmente comprobable como otros enfoques y seguro para seguir adelante.
Cualquier otro punto de vista es más que bienvenido.
@Chris Hardie está en lo cierto, EF implementa UoW fuera de la caja. Sin embargo, muchas personas pasan por alto el hecho de que EF también implementa un patrón de repositorio genérico de la caja:
var repos1 = _dbContext.Set<Widget1>();
var repos2 = _dbContext.Set<Widget2>();
var reposN = _dbContext.Set<WidgetN>();
... y esta es una implementación de repositorio genérica bastante buena que está incorporada en la herramienta misma.
¿Por qué tomarse la molestia de crear una tonelada de otras interfaces y propiedades, cuando DbContext le brinda todo lo que necesita? Si desea abstraer el DbContext detrás de las interfaces de nivel de aplicación, y desea aplicar la segregación de consulta de comandos, podría hacer algo tan simple como esto:
public interface IReadEntities
{
IQueryable<TEntity> Query<TEntity>();
}
public interface IWriteEntities : IReadEntities, IUnitOfWork
{
IQueryable<TEntity> Load<TEntity>();
void Create<TEntity>(TEntity entity);
void Update<TEntity>(TEntity entity);
void Delete<TEntity>(TEntity entity);
}
public interface IUnitOfWork
{
int SaveChanges();
}
Puede usar estas 3 interfaces para todo el acceso de su entidad, y no tener que preocuparse de inyectar 3 o más repositorios diferentes en el código comercial que funciona con 3 o más conjuntos de entidades. Por supuesto, seguiría usando IoC para garantizar que solo haya 1 instancia de DbContext por solicitud web, pero las 3 interfaces se implementan con la misma clase, lo que hace que sea más fácil.
public class MyDbContext : DbContext, IWriteEntities
{
public IQueryable<TEntity> Query<TEntity>()
{
return Set<TEntity>().AsNoTracking(); // detach results from context
}
public IQueryable<TEntity> Load<TEntity>()
{
return Set<TEntity>();
}
public void Create<TEntity>(TEntity entity)
{
if (Entry(entity).State == EntityState.Detached)
Set<TEntity>().Add(entity);
}
...etc
}
Ahora solo necesita inyectar una sola interfaz en su dependencia, independientemente de la cantidad de entidades con las que necesite trabajar:
// NOTE: In reality I would never inject IWriteEntities into an MVC Controller.
// Instead I would inject my CQRS business layer, which consumes IWriteEntities.
// See @MikeSW''s answer for more info as to why you shouldn''t consume a
// generic repository like this directly by your web application layer.
// See http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91 and
// http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92 for more info
// on what a CQRS business layer that consumes IWriteEntities / IReadEntities
// (and is consumed by an MVC Controller) might look like.
public class RecipeController : Controller
{
private readonly IWriteEntities _entities;
//Using Dependency Injection
public RecipeController(IWriteEntities entities)
{
_entities = entities;
}
[HttpPost]
public ActionResult Create(CreateEditRecipeViewModel model)
{
Mapper.CreateMap<CreateEditRecipeViewModel, Recipe>()
.ForMember(r => r.IngredientAmounts, opt => opt.Ignore());
Recipe recipe = Mapper.Map<CreateEditRecipeViewModel, Recipe>(model);
_entities.Create(recipe);
foreach(Tag t in model.Tags) {
_entities.Create(tag);
}
_entities.SaveChanges();
return RedirectToAction("CreateRecipeSuccess");
}
}
Una de mis cosas favoritas sobre este diseño es que minimiza las dependencias de almacenamiento de la entidad en el consumidor . En este ejemplo, el RecipeController
es el consumidor, pero en una aplicación real, el consumidor sería un controlador de comandos. (Para un manejador de consultas, normalmente consumiría IReadEntities
solo porque solo desea devolver datos, no mutar ningún estado). Pero para este ejemplo, usemos RecipeController
como consumidor para examinar las implicaciones de dependencia:
Digamos que tiene un conjunto de pruebas unitarias escritas para la acción anterior. En cada una de estas pruebas unitarias, usted actualiza el Controlador, pasando un simulacro al constructor. Luego, supongamos que su cliente decide que quiere permitir que las personas creen un nuevo libro de cocina o lo agreguen a uno existente al crear una receta nueva.
Con un patrón de interfaz repositorio-por-entidad o repositorio-por-agregado, tendría que inyectar una nueva instancia de repositorio IRepository<Cookbook>
en el constructor de su controlador (o usando la respuesta de @Chris Hardie, escriba el código para adjuntar otro repositorio al Instancia UOW). Esto haría inmediatamente que todas las demás pruebas de tu unidad se rompan, y tendrías que volver a modificar el código de construcción en todas ellas, pasar otra instancia simulada y ampliar tu matriz de dependencias. Sin embargo, con lo anterior, todas las demás pruebas unitarias seguirán compilando al menos. Todo lo que tiene que hacer es escribir pruebas adicionales para cubrir la nueva funcionalidad del libro de recetas.
Buscando en Internet encontré este http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework/ y es un artículo de 2 partes sobre la utilidad del patrón de repositorio de Jon Smith. La segunda parte se enfoca en una solución. ¡Espero eso ayude!
Debería considerar "objetos de comando / consulta" como alternativa, puede encontrar un montón de artículos interesantes en esta área, pero esta es una buena:
https://rob.conery.io/2014/03/03/repositories-and-unitofwork-are-not-a-good-idea/
Cuando necesite una transacción sobre múltiples objetos DB, use un objeto de comando por comando para evitar la complejidad del patrón UOW.
Un objeto de consulta por consulta es probablemente innecesario para la mayoría de los proyectos. En su lugar, puede optar por comenzar con un objeto ''FooQueries'' ... con lo que quiero decir que puede comenzar con un patrón de Repositorio para READS, pero denomínelo como "Consultas" para que sea explícito que no lo hace y no debe hacer inserciones / actualizaciones .
Más adelante, es posible que valga la pena dividir objetos de consulta individuales si desea agregar cosas como autorización y registro, podría alimentar un objeto de consulta a una interconexión.
El repositorio con la implementación del patrón de unidad de trabajo es malo para responder a su pregunta.
DbContext of the entity framework es implementado por Microsoft de acuerdo con el patrón de unidad de trabajo. Eso significa que context.SaveChanges guarda de forma transaccional los cambios de una vez.
El DbSet también es una implementación del patrón Repositorio. No construyas repositorios que puedas hacer:
void Add(Customer c)
{
_context.Customers.Add(c);
}
¿Cree un método de una sola línea para lo que puede hacer dentro del servicio de todos modos?
No hay ningún beneficio y nadie está cambiando EF ORM a otro ORM hoy en día ...
No necesitas esa libertad ...
Chris Hardie está argumentando que podrían crearse instancias de objetos de contexto múltiple pero que ya lo están haciendo mal ...
Simplemente use una herramienta de IOC que le guste y configure MyContext por solicitud de Http y estará bien.
Tome ninject por ejemplo:
kernel.Bind<ITeststepService>().To<TeststepService>().InRequestScope().WithConstructorArgument("context", c => new ITMSContext());
El servicio que ejecuta la lógica comercial obtiene el contexto inyectado.
Solo mantenlo simple y estúpido :-)
No (lo siento) decir que codefizzle, el artículo de Dyksta y las respuestas anteriores son incorrectos . Por el simple hecho de que usan las entidades EF como objetos de dominio (de negocios), que es una gran WTF.
Actualización : para una explicación menos técnica (en palabras simples) lea el patrón de repositorio para Dummies
En pocas palabras, CUALQUIER interfaz de repositorio no debe acoplarse a CUALQUIER detalle de persistencia (ORM). La interfaz repo SOLO trata con objetos que tengan sentido para el resto de la aplicación (dominio, tal vez UI como en la presentación). MUCHAS personas (con MS liderando el paquete, con la intención que sospecho) cometen el error de creer que pueden reutilizar sus entidades EF o que pueden ser objetos comerciales encima de ellos.
Si bien puede suceder, es bastante raro. En la práctica, tendrá una gran cantidad de objetos de dominio ''diseñados'' después de las reglas de la base de datos, es decir, modelos defectuosos. El propósito del repositorio es desacoplar el resto de la aplicación (principalmente la capa de negocios) de su formulario de persistencia.
¿Cómo se desacopla cuando su repositorio trata con entidades EF (detalle de persistencia) o sus métodos devuelven IQueryable, una abstracción filtrada con semántica incorrecta para este propósito (IQueryable le permite generar una consulta, lo que implica que necesita conocer detalles de persistencia por lo tanto negando el propósito y la funcionalidad del repositorio)?
Un objeto domin nunca debe saber acerca de persistencia, EF, combinaciones, etc. No debería saber qué motor db está utilizando o si está utilizando uno. Lo mismo con el resto de la aplicación, si desea que se desacople de los detalles de persistencia.
La interfaz del repositorio solo sabe qué sabe la capa superior. Esto significa que una interfaz de repositorio de dominio genérico se parece a esto
public interface IStore<TDomainObject> //where TDomainObject != Ef (ORM) entity
{
void Save(TDomainObject entity);
TDomainObject Get(Guid id);
void Delete(Guid id);
}
La implementación residirá en el DAL y usará EF para trabajar con el db. Sin embargo, la implementación se ve así
public class UsersRepository:IStore<User>
{
public UsersRepository(DbContext db) {}
public void Save(User entity)
{
//map entity to one or more ORM entities
//use EF to save it
}
//.. other methods implementation ...
}
Realmente no tienes un repositorio genérico concreto . El único uso de un repositorio genérico concreto es cuando CUALQUIER objeto de dominio se almacena en forma serializada en una tabla clave-valor como. No es el caso con un ORM.
¿Qué hay de las consultas?
public interface IQueryUsers
{
PagedResult<UserData> GetAll(int skip, int take);
//or
PagedResult<UserData> Get(CriteriaObject criteria,int skip, int take);
}
UserData es el modelo de lectura / vista adecuado para el uso del contexto de la consulta.
Puede usar EF directamente para consultar en un manejador de consultas si no le importa que su DAL conozca los modelos de vista y en ese caso no necesitará ningún repositorio de consultas.
Conclusión
- Su objeto comercial no debe saber acerca de las entidades EF.
- El repositorio usará un ORM , pero nunca expone el ORM al resto de la aplicación, por lo que la interfaz repo utilizará solo objetos de dominio o modelos de visualización (o cualquier otro objeto de contexto de la aplicación que no sea un detalle de persistencia)
- Usted no le dice al repositorio cómo hacer su trabajo, es decir, NUNCA use IQueryable con una interfaz repo
- Si solo desea utilizar el archivo db de una manera más fácil o más sencilla, está tratando con una aplicación CRUD simple donde no necesita (asegúrese de hacerlo) para mantener la separación de las preocupaciones y omita el repositorio , use directamente EF para todos los datos. La aplicación estará estrechamente unida a EF, pero al menos cortarás al intermediario y será a propósito, no por error.
Tenga en cuenta que si utiliza el repositorio de forma incorrecta, se invalidará su uso y su aplicación seguirá estando estrechamente unida a la persistencia (ORM).
En caso de que crea que el ORM está ahí para almacenar mágicamente sus objetos de dominio, no lo es. El propósito de ORM es simular un almacenamiento de OOP en la parte superior de las tablas relacionales. Tiene todo que ver con la persistencia y nada que ver con el dominio, por lo que no utilice el ORM fuera de persistencia.
Siempre uso UoW con código EF primero. Me parece más eficaz y más fácil de manejar tus contextos, para evitar pérdidas de memoria y cosas así. Puede encontrar un ejemplo de mi solución en mi github: http://www.github.com/stefchri en el proyecto RADAR.
Si tiene alguna pregunta al respecto, siéntase libre de preguntar.
DbContext
se construye con el patrón Unidad de trabajo. Permite que todas sus entidades compartan el mismo contexto a medida que trabajamos con ellos. Esta implementación es interna al DbContext
.
Sin embargo, debe tenerse en cuenta que si DbContext
instancia de dos objetos DbContext
, ninguno de ellos verá las entidades del otro que cada uno de ellos realiza un seguimiento. Están aislados el uno del otro, lo que puede ser problemático.
Cuando construyo una aplicación MVC, quiero asegurarme de que, durante el curso de la solicitud, todo mi código de acceso a datos funcione a partir de un solo DbContext
. Para lograr eso, aplico la Unidad de trabajo como un patrón externo a DbContext
.
Aquí está mi objeto de Unidad de trabajo de una aplicación de recetas de barbacoa que estoy construyendo:
public class UnitOfWork : IUnitOfWork
{
private BarbecurianContext _context = new BarbecurianContext();
private IRepository<Recipe> _recipeRepository;
private IRepository<Category> _categoryRepository;
private IRepository<Tag> _tagRepository;
public IRepository<Recipe> RecipeRepository
{
get
{
if (_recipeRepository == null)
{
_recipeRepository = new RecipeRepository(_context);
}
return _recipeRepository;
}
}
public void Save()
{
_context.SaveChanges();
}
**SNIP**
Adjunto todos mis repositorios, todos inyectados con el mismo DbContext
, a mi objeto Unidad de trabajo. Siempre que se soliciten repositorios del objeto Unidad de trabajo, podemos estar seguros de que todos nuestros códigos de acceso a datos se administrarán con la misma salsa DbContext
.
Si tuviera que usar esto en una aplicación MVC, me aseguraría de que la Unidad de trabajo se utilizara en toda la solicitud al crear una instancia en el controlador y al usarla en todas sus acciones:
public class RecipeController : Controller
{
private IUnitOfWork _unitOfWork;
private IRepository<Recipe> _recipeService;
private IRepository<Category> _categoryService;
private IRepository<Tag> _tagService;
//Using Dependency Injection
public RecipeController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_categoryRepository = _unitOfWork.CategoryRepository;
_recipeRepository = _unitOfWork.RecipeRepository;
_tagRepository = _unitOfWork.TagRepository;
}
Ahora en nuestra acción, podemos estar seguros de que todos nuestros códigos de acceso a datos usarán el mismo DbContext
:
[HttpPost]
public ActionResult Create(CreateEditRecipeViewModel model)
{
Mapper.CreateMap<CreateEditRecipeViewModel, Recipe>().ForMember(r => r.IngredientAmounts, opt => opt.Ignore());
Recipe recipe = Mapper.Map<CreateEditRecipeViewModel, Recipe>(model);
_recipeRepository.Create(recipe);
foreach(Tag t in model.Tags){
_tagRepository.Create(tag); //I''m using the same DbContext as the recipe repo!
}
_unitOfWork.Save();