2012-08-16 9 views
7

Tôi đã cố gắng để có được đầu của tôi xung quanh này cho một vài ngày và có rất nhiều hướng dẫn xung quanh đơn vị công việc và xung quanh TransactionScope nhưng tôi không thể tìm thấy bất cứ điều gì mà nói về cả hai cùng nhau. Bất kỳ giúp đỡ nhiều đánh giá cao!Đơn vị công việc mẫu với các giao dịch cơ sở dữ liệu

Tôi đang sử dụng Khung thực thể với mẫu Đơn vị công việc và một kho lưu trữ cho mỗi loại. Theo mã đơn giản dưới đây tôi có một thực thể Member và MembershipDefinition. Tôi muốn tạo một thực thể thành viên liên kết hai nhưng khi tôi tạo đối tượng thành viên, tôi muốn truy vấn DB cho một giá trị tối đa dựa trên một số logic nghiệp vụ. Vì vậy, tôi cần phải sử dụng một số loại giao dịch DB để ngăn chặn một thread khác tăng giá trị trong db trước khi thread của tôi đã viết đối tượng Membership trở lại DB.

Nếu tôi đã sử dụng được lưu trữ procs, đây sẽ là khá đơn giản nhưng tôi không thể tìm ra cách để làm điều đó bằng tinh khiết C# ...

Đoạn code dưới đây tạo ra 100 đơn vị thành viên trong cơ sở dữ liệu với MembershipNumbers trùng lặp . Tôi cần sử dụng giao dịch này để đảm bảo rằng tất cả các số thành viên được tạo trong mã C# là duy nhất.

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

bạn có chắc chắn giao dịch là đủ? –

+0

Bạn có thể gọi Thủ tục lưu trữ qua Entity Framework, vì vậy tại sao làm phức tạp công việc bạn phải – podiluska

+0

Tôi thấy câu hỏi là một câu hỏi tổng quát hơn: làm thế nào để giảm độ phức tạp của hệ thống theo cách mà chúng ta có thể giải thích về tính chính xác của hệ thống . Trong trường hợp này, chúng tôi có thể giải quyết vấn đề cho MAX(), nhưng làm thế nào chúng ta có thể chắc chắn rằng hệ thống hoạt động dự kiến, không bế tắc, và không chạy chậm. Ngay cả với một giao dịch serializable, bạn không thể chắc chắn rằng hệ thống của bạn hoạt động tốt khi chạy các hoạt động song song. – Steven

Trả lời

2

Bạn có thể tạo phạm vi giao dịch khi khởi tạo UnitOfWork mới và cam kết hoàn thành. Đây exmaple không đầy đủ:

class UnitOfWork 
{ 
    ClubSpotEntities _dbContext; 
    TransactionScope _transaction; 

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

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

    ... 
} 

UPD: Như Steven cho biết đây không phải là giải pháp của bạn có vấn đề. Và UnitOfWork không thể giúp bạn, TransactionScope không phải là một giải pháp trong trường hợp này. EF không hỗ trợ khóa bi quan mà bạn muốn sử dụng, nhưng bạn có thể thử này solution.

+1

Chạy bên trong một giao dịch không giải quyết được Vấn đề về OP, vì hai hoạt động song song vẫn có thể chèn hai thành viên với cùng 'MembershipNumber' (một hoạt động có thể sẽ thất bại). – Steven

+0

Bạn có thể sử dụng mức cô lập nối tiếp. Tất nhiên nó có thể kích động deadlocks, nhưng đây là vấn đề về việc thực hiện và không phụ thuộc vào lựa chọn EF hoặc Stored Procedures. Ngoài ra nếu deadlocks là hiếm, bạn có thể cố gắng để lặp lại hoạt động. –

+1

Khi chạy trong một giao dịch 'SERIALIZABLE', máy chủ SQL cũng sẽ khóa các hàng đang được đọc. Tuy nhiên, khi truy vấn của bạn chỉ chạy trên một chỉ mục (chẳng hạn như truy vấn 'MAX()' của OP có thể làm), chỉ có chỉ mục đó sẽ bị khóa, không phải là chính bảng đó. Vì vậy, mặc dù serializable sẽ trong trường hợp này giúp serialize 'CreateMembership' hoạt động, nó vẫn có thể thất bại trong kết hợp với các hoạt động khác. – Steven