2012-08-16 14 views
7

He intentado entender esto durante un par de días y hay muchos tutoriales en torno a la unidad de trabajo y en torno a TransactionScope, pero no encuentro nada que se refiera a la dos juntos. Cualquier ayuda muy apreciada!Patrón de unidad de trabajo con transacciones de base de datos

Estoy utilizando Entity Framework con el patrón Unit Of Work y un repositorio por tipo. Según el código simple a continuación, tengo una entidad Member and MembershipDefinition. Deseo crear una entidad de Membresía que vincule los dos pero cuando creo el objeto de Membresía Deseo consultar el DB para un valor máximo basado en alguna lógica comercial. Por lo tanto, necesito usar algún tipo de transacción DB para evitar que otro subproceso incremente el valor en el db antes de que mi hilo haya escrito el objeto Membership nuevamente en el DB.

Si yo estaba usando procedimientos almacenados, esto sería bastante sencillo, pero no puedo encontrar la manera de hacerlo utilizando C# puro ...

El código siguiente crea 100 entidades de afiliación en la base de datos con MembershipNumbers duplicados . Necesito usar este uso para usar las transacciones para asegurar que todos los números de membresía generados en el código C# sean únicos.

class Program 
{ 
    static void Main(string[] args) 
    { 
     var p = new Program(); 
     p.Go();; 
    } 

    public void Go() 
    { 
     long memberId; 
     long membershipDefId; 

     using(var unitOfWork = new UnitOfWork()) 
     { 
      // Setup - create test club and member entities 

      var testUsername = ("TestUserName" + Guid.NewGuid()).Substring(0, 29); 
      var member = new Member() 
          { 
           UserName = testUsername 
          }; 

      var testmemebrshpDefName = ("TestMembershipDef" + Guid.NewGuid()).Substring(0, 29); 
      var membershipDefinition = new ClubMembershipDefinition() 
      { 
       ClubId = 1, 
       Name = testmemebrshpDefName 
      }; 

      unitOfWork.MemberRepository.Add(member); 
      unitOfWork.MembershipDefinitionRepository.Add(membershipDefinition); 

      unitOfWork.Save(); 

      memberId = member.Id; 
      membershipDefId = membershipDefinition.Id; 
     } 

     Task[] tasks = new Task[100]; 

     // Now try to add a membership to the Member object, linking it to the test Club's single Club Definition 
     for (int i = 0; i < 100; i++) 
     { 
      var task = new Task(() => CreateMembership(memberId, membershipDefId)); 
      tasks[i] = task; 
      task.Start(); 
     } 
     Task.WaitAll(tasks); 
    } 

    private void CreateMembership(long memberId, long membershipDefId) 
    { 
     using (var unitOfWork = new UnitOfWork()) 
     { 
      var member = unitOfWork.MemberRepository.GetById(memberId); 
      var membershipDef = unitOfWork.MembershipDefinitionRepository.GetById(membershipDefId); 

      var membership = new ClubMembership() 
            { 
             ClubMembershipDefinition = membershipDef 
            }; 

      membership.MembershipNumber = (unitOfWork.MembershipRepository.GetMaxMembershipNumberForClub(membershipDef.ClubId) ?? 0) + 1; 

      member.ClubMemberships.Add(membership); 
      unitOfWork.Save(); 
     } 
    } 

} 

public class UnitOfWork : IUnitOfWork, IDisposable 
{ 
    internal ClubSpotEntities _dbContext = new ClubSpotEntities(); 
    internal MemberRepository _memberRepository; 
    internal MembershipRepository _membershipRepository; 
    internal MembershipDefinitionRepository _membershiDefinitionpRepository; 

    public MemberRepository MemberRepository 
    { 
     get 
     { 
      if (_memberRepository == null) 
       _memberRepository = new MemberRepository(_dbContext); 

      return _memberRepository; ; 
     } 
    } 

    public MembershipRepository MembershipRepository 
    { 
     get 
     { 
      if (_membershipRepository == null) 
       _membershipRepository = new MembershipRepository(_dbContext); 

      return _membershipRepository; ; 
     } 
    } 

    public MembershipDefinitionRepository MembershipDefinitionRepository 
    { 
     get 
     { 
      if (_membershiDefinitionpRepository == null) 
       _membershiDefinitionpRepository = new MembershipDefinitionRepository(_dbContext); 

      return _membershiDefinitionpRepository; ; 
     } 
    } 

    public virtual int Save() 
    { 
     return _dbContext.SaveChanges(); 

    } 

    private bool _disposed = false; 

    protected virtual void Dispose(bool disposing) 
    { 
     if (!this._disposed) 
     { 
      if (disposing) 
      { 
       _dbContext.Dispose(); 
      } 
     } 
     this._disposed = true; 
    } 

    public void Dispose() 
    { 
     Dispose(true); 
     GC.SuppressFinalize(this); 
    } 
} 

public class MembershipRepository 
{ 
    ClubSpotEntities _dbContext = new ClubSpotEntities(); 

    public MembershipRepository(){} 

    public MembershipRepository(ClubSpotEntities dbContext) 
    { 
     _dbContext = dbContext; 
    } 

    public IEnumerable<ClubMembership> GetAll() 
    { 
     return _dbContext.Set<ClubMembership>().ToList<ClubMembership>(); 
    } 

    public ClubMembership GetById(long id) 
    { 
     return _dbContext.ClubMemberships.First(x => x.Id == id); 
    } 

    public long? GetMaxMembershipNumberForClub(long clubId) 
    { 
     return _dbContext.ClubMemberships.Where(x => x.ClubMembershipDefinition.ClubId == clubId).Max(x => x.MembershipNumber); 
    } 

    public ClubMembership Add(ClubMembership entity) 
    { 
     return _dbContext.Set<ClubMembership>().Add(entity); 
    } 

    public void Delete(ClubMembership membership) 
    { 
     _dbContext.Set<ClubMembership>().Remove(membership); 
    } 

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


public partial class ClubMembership 
{ 
    public long Id { get; set; } 
    public long MembershipDefId { get; set; } 
    public Nullable<long> MemberId { get; set; } 
    public Nullable<long> MembershipNumber { get; set; } 

    public virtual ClubMembershipDefinition ClubMembershipDefinition { get; set; } 
    public virtual Member Member { get; set; } 
} 

public partial class ClubMembershipDefinition 
{ 
    public ClubMembershipDefinition() 
    { 
     this.ClubMemberships = new HashSet<ClubMembership>(); 
    } 

    public long Id { get; set; } 
    public long ClubId { get; set; } 
    public string Name { get; set; } 

    public virtual ICollection<ClubMembership> ClubMemberships { get; set; } 
} 

public partial class Member 
{ 
    public Member() 
    { 
     this.ClubMemberships = new HashSet<ClubMembership>(); 
    } 

    public long Id { get; set; } 
    public string UserName { get; set; } 

    public virtual ICollection<ClubMembership> ClubMemberships { get; set; } 
} 
+0

¿Estás seguro de que una transacción es suficiente? –

+0

Puede llamar a Procedimientos almacenados a través de Entity Framework, entonces ¿por qué complicar demasiado el trabajo que tiene que – podiluska

+0

Veo la pregunta como una más general: cómo reducir la complejidad del sistema de tal manera que podemos razonar acerca de la corrección transaccional del sistema . En este caso, podríamos resolver el problema del MAX(), pero ¿cómo podemos estar seguros de que el sistema funciona de forma esperada, no se bloquea y no se ejecuta lentamente? Incluso con una transacción serializable, no puede estar seguro de que su sistema se comporta bien cuando ejecuta operaciones en paralelo. – Steven

Respuesta

2

Puede crear ámbito de transacción cuando instantiate nueva UnitOfWork, y comprometerse en su finalización. Esto no llena exmaple:

class UnitOfWork 
{ 
    ClubSpotEntities _dbContext; 
    TransactionScope _transaction; 

    public UnitOfWork() 
    { 
     _dbContext = new ClubSpotEntities(); 
     _transaction = new TransactionScope(); 
    } 

    public void Complete() 
    { 
     _dbContext.SaveChanges(); 
     _transaction.Complete(); 
    } 

    ... 
} 

UPD: Como Steven dijo que esto no es la solución del problema que. Y UnitOfWork no puede ayudarlo, TransactionScope no es una solución en este caso. EF no admite bloqueos pesimistas que desee utilizar, pero puede probar este solution.

+1

Al ejecutar una transacción no se resuelve Problema de OP, ya que dos operaciones paralelas aún podrían insertar dos miembros con el mismo 'MembershipNumber' (donde una operación probablemente fallará). – Steven

+0

Puede usar el nivel de aislamiento serializable. Por supuesto, puede provocar interbloqueos, pero esta es una cuestión de implementación y no depende de elegir EF o procedimientos almacenados. Además, si los interbloqueos son raros, puede intentar repetir la operación. –

+1

Cuando se ejecuta en una transacción 'SERIALIZABLE', el servidor SQL también bloqueará las filas que se están leyendo. Sin embargo, cuando su consulta solo se ejecuta sobre un índice (como la consulta 'MAX()' del OP podría hacerlo), solo ese índice se bloqueará, no la tabla en sí. Entonces, aunque serializable en este caso ayudará a serializar las operaciones 'CreateMembership', aún podría fallar junto con otras operaciones. – Steven

Cuestiones relacionadas