Linq to SQL和Rob Conery存储库模式的并发性

时间:2009-05-11 05:42:27

标签: linq-to-sql concurrency repository-pattern

我使用Rob Conery在存储库模式(来自MVC Storefront项目)上实现了DAL,我使用Linq将数据库对象映射到域对象,并使用Linq to SQL实际获取数据。

这一切都非常有效地让我完全控制了我想要的域对象的形状,但是我遇到了并发问题,我想我会在这里问一下。我有并发工作,但解决方案感觉它可能是错误的(只是其中一种gitchy感觉)。

基本模式是:

private MyDataContext _datacontext
private Table _tasks;

public Repository(MyDataContext datacontext)
{
    _dataContext = datacontext;
}

public void GetTasks()
{
    _tasks = from t in _dataContext.Tasks;

    return from t in _tasks
        select new Domain.Task
        {
            Name = t.Name,
            Id = t.TaskId,
            Description = t.Description                              
        };
}

public void SaveTask(Domain.Task task)
{
    Task dbTask = null;

    // Logic for new tasks omitted...

    dbTask = (from t in _tasks
        where t.TaskId == task.Id
        select t).SingleOrDefault();

    dbTask.Description = task.Description,
        dbTask.Name = task.Name,

    _dataContext.SubmitChanges();
} 

因此,由于映射到域任务,我失去了并发跟踪。我通过存储私有表来获取它,这是在获取原始任务时我的datacontext任务列表。

然后我从这个存储的表中更新任务并保存我已更新的内容

这是有效的 - 当出现并发冲突时,我会得到更改冲突异常,就像我想要的那样。

然而,我只是尖叫着我错过了一个技巧。

有更好的方法吗?

我看过datacontext上的.Attach方法,但似乎要求以与我现在正在做的相似的方式存储原始版本。

我也知道我可以通过取消域对象并让Linq到SQL生成的对象一直到我的堆栈来避免这一切 - 但我不喜欢我不喜欢我处理的方式并发性。

2 个答案:

答案 0 :(得分:1)

我完成了这个并找到了以下解决方案。它适用于所有测试用例(更重要的是,我的测试人员!)可以想到。

我在datacontext和TimeStamp列上使用.Attach()方法。这在第一次将特定主键保存回数据库时工作正常但我发现datacontext抛出System.Data.Linq.DuplicateKeyException“无法添加一个已经在使用的密钥的实体。”

我创建的这个工作是添加一个字典,存储我第一次附加的项目,然后每次保存我重复使用该项目。

示例代码如下,我想知道我是否错过了任何技巧 - 并发性非常重要,所以我跳过的箍似乎有点过分。

希望以下证明有用,或者有人可以指出我更好的实施!

private Dictionary<int, Payment> _attachedPayments;

public void SavePayments(IList<Domain.Payment> payments)
    {
        Dictionary<Payment, Domain.Payment> savedPayments =
            new Dictionary<Payment, Domain.Payment>();

        // Items with a zero id are new
        foreach (Domain.Payment p in payments.Where(p => p.PaymentId != 0))
        {
            // The list of attached payments that works around the linq datacontext  
            // duplicatekey exception
            if (_attachedPayments.ContainsKey(p.PaymentId)) // Already attached
            {
                Payment dbPayment = _attachedPayments[p.PaymentId];                    
                // Just a method that maps domain to datacontext types
                MapDomainPaymentToDBPayment(p, dbPayment, false);
                savedPayments.Add(dbPayment, p);
            }
            else // Attach this payment to the datacontext
            {
                Payment dbPayment = new Payment();
                MapDomainPaymentToDBPayment(p, dbPayment, true);
                _dataContext.Payments.Attach(dbPayment, true);
                savedPayments.Add(dbPayment, p);
            }
        }

        // There is some code snipped but this is just brand new payments
        foreach (var payment in newPayments)
        {
            Domain.Payment payment1 = payment;
            Payment newPayment = new Payment();
            MapDomainPaymentToDBPayment(payment1, newPayment, false);
            _dataContext.Payments.InsertOnSubmit(newPayment);
            savedPayments.Add(newPayment, payment);
        }

        try
        {
            _dataContext.SubmitChanges();
            // Grab the Timestamp into the domain object
            foreach (Payment p in savedPayments.Keys)
            {
                savedPayments[p].PaymentId = p.PaymentId;
                savedPayments[p].Timestamp = p.Timestamp;
                _attachedPayments[savedPayments[p].PaymentId] = p;
            }
        }
        catch (ChangeConflictException ex)
        {
            foreach (ObjectChangeConflict occ in _dataContext.ChangeConflicts)
            {
                Payment entityInConflict = (Payment) occ.Object;

                // Use the datacontext refresh so that I can display the new values
                _dataContext.Refresh(RefreshMode.OverwriteCurrentValues, entityInConflict);
                _attachedPayments[entityInConflict.PaymentId] = entityInConflict;

            }
            throw;
        }

    }

答案 1 :(得分:0)

我会尝试通过传递'原始'和'更新'对象来尝试利用.Attach方法,从而实现LINQ2SQL的真正乐观并发检查。此IMO将优先使用DBML对象或Domain对象中的版本或日期时间戳。我不确定MVC如何允许这种持久化“原始”数据的想法。但我一直试图调查验证脚手架,希望它存储'原始'数据..但我怀疑它是与最近的帖子一样好(和/或验证失败)。所以这个想法可能不起作用。

我的另一个疯狂想法是:覆盖所有域对象的GetHashCode(),其中哈希表示该对象的唯一数据集(减去当然的ID)。然后,手动或与助手埋葬散列在HTML POST形式的隐藏字段,并与更新后的域对象发回给你的服务层 - 执行并发在服务层和数据层检查(通过比较原始使用新提取的域对象的哈希进行哈希)但要注意您需要自己检查并引发并发异常。这是很好的使用DMBL功能,但抽象掉数据层的想法是如此的不依赖于特定的实现的功能等,所以具有乐观并发在服务层上你的域对象检查的完全控制(例如)似乎对我来说是个好方法。