una transacciones transaccion tipos registro realizar ley hacer ejemplos contabilidad como comerciales comercial agropecuarias c# java architecture repository-pattern unit-of-work

c# - transacciones - Unidad de trabajo+Patrón de repositorio: La caída del concepto de transacción comercial



transacciones comerciales ejemplos (7)

En .NET, los componentes de acceso a datos generalmente se enlistan automáticamente en transacciones ambientales. Por lo tanto, guardar los cambios dentro de la transacción se separa de la transacción para continuar con los cambios .

En otras palabras, si crea un ámbito de transacción, puede dejar que los desarrolladores ahorren todo lo que quieran. No hasta que se comprometa la transacción, el estado observable de la base de datos se actualizará (bueno, lo que es observable depende del nivel de aislamiento de la transacción).

Esto muestra cómo crear un ámbito de transacción en c #:

using (TransactionScope scope = new TransactionScope()) { // Your logic here. Save inside the transaction as much as you want. scope.Complete(); // <-- This will complete the transaction and make the changes permanent. }

La combinación de Unit of Work y Repository Pattern es algo que se usa bastante en la actualidad. Como says Martin Fowler says propósito de usar UoW es formar una Transacción comercial mientras se ignora cómo funcionan realmente los repositorios (siendo ignorante persistente). He revisado muchas implementaciones; e ignorando detalles específicos (clase concreta / abstracta, interfaz, ...) son más o menos similares a lo que sigue:

public class RepositoryBase<T> { private UoW _uow; public RepositoryBase(UoW uow) // injecting UoW instance via constructor { _uow = uow; } public void Add(T entity) { // Add logic here } // +other CRUD methods } public class UoW { // Holding one repository per domain entity public RepositoryBase<Order> OrderRep { get; set; } public RepositoryBase<Customer> CustomerRep { get; set; } // +other repositories public void Commit() { // Psedudo code: For all the contained repositories do: store repository changes. } }

Ahora mi problema:

UoW expone el método público Commit a almacenar los cambios. Además, como cada repositorio tiene una instancia compartida de UoW , cada Repository puede acceder al método Commit en UoW. Llamarlo por un repositorio hace que todos los otros repositorios almacenen sus cambios también; De ahí el resultado de que todo el concepto de transacción se derrumba:

class Repository<T> : RepositoryBase<T> { private UoW _uow; public void SomeMethod() { // some processing or data manipulations here _uow.Commit(); // makes other repositories also save their changes } }

Creo que esto no debe estar permitido. Teniendo en cuenta el propósito de la UoW (transacción comercial), el método Commit debe estar expuesto solo al que inició una transacción comercial, por ejemplo, Business Layer. Lo que me sorprendió es que no pude encontrar ningún artículo que abordara este problema. En todos ellos se puede llamar a Commit por cualquier repo que se inyecte.

PD: Sé que puedo decirle a mis desarrolladores que no llamen a Commit en un Repository pero una Arquitectura confiable es más confiable que los desarrolladores confiables!


Estoy de acuerdo con sus preocupaciones. Prefiero tener una unidad de trabajo ambiental, donde la función más externa que abre una unidad de trabajo es la que decide si comprometer o abortar. Las funciones llamadas pueden abrir una unidad de alcance de trabajo que automáticamente se inscribe en la UOW ambiente si la hay, o crea una nueva si no hay ninguna.

La implementación del UnitOfWorkScope que utilicé está muy inspirada en cómo funciona TransactionScope . El uso de un enfoque de ambiente / ámbito también elimina la necesidad de la inyección de dependencia.

Un método que realiza una consulta se ve así:

public static Entities.Car GetCar(int id) { using (var uow = new UnitOfWorkScope<CarsContext>(UnitOfWorkScopePurpose.Reading)) { return uow.DbContext.Cars.Single(c => c.CarId == id); } }

Un método que escribe se ve así:

using (var uow = new UnitOfWorkScope<CarsContext>(UnitOfWorkScopePurpose.Writing)) { Car c = SharedQueries.GetCar(carId); c.Color = "White"; uow.SaveChanges(); }

Tenga en cuenta que la llamada a uow.SaveChanges() solo realizará un guardado real en la base de datos si este es el alcance de la raíz (más lejos). De lo contrario, se interpreta como un "voto correcto" de que se permitirá al alcance raíz guardar los cambios.

La implementación completa del UnitOfWorkScope está disponible en: http://coding.abel.nu/2012/10/make-the-dbcontext-ambient-with-unitofworkscope/


Haz tus repositorios miembros de tu UoW. No dejes que tus repositorios ''vean'' tu UoW. Deje que UoW maneje la transacción.


No pase a UnitOfWork , pase a una interfaz que tenga los métodos que necesita. Aún puede implementar esa interfaz en la implementación concreta original de UnitOfWork si desea:

public interface IDbContext { void Add<T>(T entity); } public interface IUnitOfWork { void Commit(); } public class UnitOfWork : IDbContext, IUnitOfWork { public void Add<T>(T entity); public void Commit(); } public class RepositoryBase<T> { private IDbContext _c; public RepositoryBase(IDbContext c) { _c = c; } public void Add(T entity) { _c.Add(entity) } }

EDITAR

Después de publicar esto tuve un replanteamiento. Exponer el método Add en la implementación de UnitOfWork significa que es una combinación de los dos patrones.

Utilizo Entity Framework en mi propio código y el DbContext utilizado allí se describe como "una combinación del patrón de Unidad de trabajo y Repositorio".

Creo que es mejor dividir los dos, y eso significa que necesito dos envoltorios alrededor de DbContext uno para el bit de Unidad de Trabajo y uno para el bit de Repositorio. Y hago el ajuste del repositorio en RepositoryBase .

La diferencia clave es que no paso el UnitOfWork a los repositorios, paso el DbContext . Eso significa que el BaseRepository tiene acceso a un SaveChanges en el DbContext . Y dado que la intención es que los repositorios personalizados deban heredar BaseRepository , también obtienen acceso a un DbContext . Por lo tanto, es posible que un desarrollador pueda agregar código en un repositorio personalizado que use ese DbContext . Así que supongo que mi "envoltorio" es un poco escurrido ...

Entonces, ¿vale la pena crear otro contenedor para el DbContext que pueda pasarse a los constructores del repositorio para cerrar eso? No estoy seguro de que sea ...

Ejemplos de pasar el DbContext:

Implementación del repositorio y unidad de trabajo.

Repositorio y Unidad de Trabajo en Entity Framework.

Código fuente original de John Papa


Sí, esta pregunta me preocupa, y así es como lo manejo.

En primer lugar, a mi entender, el Modelo de Dominio no debe saber acerca de la Unidad de Trabajo. El Modelo de dominio consiste en interfaces (o clases abstractas) que no implican la existencia del almacenamiento transaccional. De hecho, no conoce la existencia de ningún almacenamiento. De ahí el término Modelo de Dominio.

La unidad de trabajo está presente en la capa de implementación del modelo de dominio . Supongo que este es mi término, y con eso me refiero a una capa que implementa interfaces de Modelo de Dominio al incorporar la Capa de Acceso de Datos. Por lo general, uso ORM como DAL y, por lo tanto, viene con UOW incorporado (Entity Framework SaveChanges o SubmitChanges para confirmar los cambios pendientes). Sin embargo, ese pertenece a DAL y no necesita la magia de ningún inventor.

Por otro lado, se está refiriendo a la UoW que necesita tener en la capa de Implementación del Modelo de Dominio porque necesita abstraer la parte de "confirmar los cambios en DAL". Para eso, me gustaría ir con la solución de Anders Abel (imágenes recursivas), porque eso aborda dos cosas que necesitas resolver de una sola vez :

  • Debe admitir el almacenamiento de agregados como una transacción, si el agregado es un iniciador del alcance.
  • Debe admitir el almacenamiento de agregados como parte de la transacción principal , si el agregado no es el iniciador del alcance, sino que forma parte de él.

Tenga en cuenta que ha pasado un tiempo desde que se solicitó esto, y las personas pueden haber muerto de vejez, haber sido transferidas a la gerencia, etc., pero aquí va.

Inspirándose en las bases de datos, los controladores de transacciones y el protocolo de confirmación de dos fases, los siguientes cambios en los patrones deberían funcionar para usted.

  1. Implemente la interfaz de la unidad de trabajo descrita en el libro P de EAA de Fowler, pero inyecte el repositorio en cada método UoW.
  2. Inyecte la unidad de trabajo en cada operación de repositorio.
  3. Cada operación de repositorio llama a la operación de UoW apropiada y se inyecta.
  4. Implemente los dos métodos de confirmación de fase CanCommit (), Commit () y Rollback () en los repositorios.
  5. Si es necesario, commit en el UoW puede ejecutar Commit en cada repositorio o puede comprometerse con el almacén de datos. También puede implementar una confirmación de 2 fases si eso es lo que desea.

Una vez hecho esto, puede admitir varias configuraciones diferentes dependiendo de cómo implemente los repositorios y la UoW. por ejemplo, desde un simple almacén de datos sin transacciones, RDBM individuales, múltiples almacenes de datos heterogéneos, etc. Los almacenes de datos y sus interacciones pueden estar en los repositorios o en el UoW, según lo requiera la situación.

interface IEntity { int Id {get;set;} } interface IUnitOfWork() { void RegisterNew(IRepsitory repository, IEntity entity); void RegisterDirty(IRepository respository, IEntity entity); //etc. bool Commit(); bool Rollback(); } interface IRepository<T>() : where T : IEntity; { void Add(IEntity entity, IUnitOfWork uow); //etc. bool CanCommit(IUnitOfWork uow); void Commit(IUnitOfWork uow); void Rollback(IUnitOfWork uow); }

El código de usuario es siempre el mismo independientemente de las implementaciones de la base de datos y se ve así:

// ... var uow = new MyUnitOfWork(); repo1.Add(entity1, uow); repo2.Add(entity2, uow); uow.Commit();

Volver a la publicación original. Debido a que somos un método para inyectar el UoW en cada operación de repositorio, el UoW no necesita ser almacenado por cada repositorio, lo que significa que Commit () en el Repositorio se puede apagar, con Commit en el UoW haciendo la confirmación de DB real.


Yo también he estado investigando recientemente este patrón de diseño y al utilizar la Unidad de trabajo y el Patrón genérico de repositorio, pude extraer la Unidad de trabajo "Guardar cambios" para la implementación del repositorio. Mi código es el siguiente:

public class GenericRepository<T> where T : class { private MyDatabase _Context; private DbSet<T> dbset; public GenericRepository(MyDatabase context) { _Context = context; dbSet = context.Set<T>(); } public T Get(int id) { return dbSet.Find(id); } public IEnumerable<T> GetAll() { return dbSet<T>.ToList(); } public IEnumerable<T> Where(Expression<Func<T>, bool>> predicate) { return dbSet.Where(predicate); } ... ... }

Esencialmente, todo lo que estamos haciendo es pasar el contexto de datos y utilizar los métodos dbSet del marco de la entidad para obtener, obtener, obtener, agregar, agregar, agregar, eliminar, eliminar y dónde.

Ahora crearemos una interfaz genérica para exponer estos métodos.

public interface <IGenericRepository<T> where T : class { T Get(int id); IEnumerable<T> GetAll(); IEnumerabel<T> Where(Expression<Func<T, bool>> predicate); ... ... }

Ahora nos gustaría crear una interfaz para cada entidad en el marco de la entidad y heredarla de IGenericRepository para que la interfaz cuente con las firmas del método implementadas en los repositorios heredados.

Ejemplo:

public interface ITable1 : IGenericRepository<table1> { }

Seguirás este mismo patrón con todas tus entidades. También agregará firmas de funciones en estas interfaces que sean específicas de las entidades. Esto daría lugar a que los repositorios necesiten implementar los métodos GenericRepository y cualquier método personalizado definido en las interfaces.

Para los Repositorios los implementaremos así.

public class Table1Repository : GenericRepository<table1>, ITable1 { private MyDatabase _context; public Table1Repository(MyDatabase context) : base(context) { _context = context; } }

En el repositorio de ejemplo anterior, estoy creando el repositorio de table1 y heredando el GenericRepository con un tipo de "table1", luego heredé de la interfaz de ITable1. Esto implementará automáticamente los métodos dbSet genéricos para mí, lo que me permitirá centrarme solo en mis métodos de repositorio personalizados, si los hay. Al pasar el dbContext al constructor, también debo pasar el dbContext al Repositorio genérico base.

Ahora desde aquí iré y crearé el repositorio y la interfaz de la Unidad de Trabajo.

public interface IUnitOfWork { ITable1 table1 {get;} ... ... list all other repository interfaces here. void SaveChanges(); } public class UnitOfWork : IUnitOfWork { private readonly MyDatabase _context; public ITable1 Table1 {get; private set;} public UnitOfWork(MyDatabase context) { _context = context; // Initialize all of your repositories here Table1 = new Table1Repository(_context); ... ... } public void SaveChanges() { _context.SaveChanges(); } }

Manejo el alcance de mi transacción en un controlador personalizado del que heredan todos los demás controladores de mi sistema. Este controlador hereda del controlador MVC predeterminado.

public class DefaultController : Controller { protected IUnitOfWork UoW; protected override void OnActionExecuting(ActionExecutingContext filterContext) { UoW = new UnitOfWork(new MyDatabase()); } protected override void OnActionExecuted(ActionExecutedContext filterContext) { UoW.SaveChanges(); } }

Al implementar su código de esta manera. Cada vez que se realiza una solicitud al servidor al comienzo de una acción, se creará un nuevo UnitOfWork que creará automáticamente todos los repositorios y los hará accesibles a la variable UoW en su controlador o clases. Esto también eliminará su SaveChanges () de sus repositorios y lo colocará dentro del repositorio de UnitOfWork. Y por último, este patrón es capaz de utilizar solo un único dbContext en todo el sistema a través de la inyección de dependencia.

Si le preocupan las actualizaciones padre / hijo con un contexto singular, puede utilizar procedimientos almacenados para las funciones de actualización, inserción y eliminación y utilizar el marco de entidades para sus métodos de acceso.