Linq to sql Linq到SQL和Rob Conry存储库模式的并发性

Linq to sql Linq到SQL和Rob Conry存储库模式的并发性,linq-to-sql,concurrency,repository-pattern,Linq To Sql,Concurrency,Repository Pattern,我使用Rob Conry的spin on the repository模式(来自MVC Storefront项目)实现了一个DAL,其中我使用Linq将数据库对象映射到域对象,并使用Linq到SQL来实际获取数据 这一切都非常好,让我能够完全控制我想要的域对象的形状,但是我遇到了一个并发问题,我想在这里问一下。我有并发工作,但解决方案感觉可能是错误的(只是其中一种愚蠢的感觉) 基本模式是: private MyDataContext _datacontext private Table _tas

我使用Rob Conry的spin on the repository模式(来自MVC Storefront项目)实现了一个DAL,其中我使用Linq将数据库对象映射到域对象,并使用Linq到SQL来实际获取数据

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

基本模式是:

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 to SQL生成的对象一直在我的堆栈中来避免所有这些,但我不喜欢这一点,就像我不喜欢处理并发性的方式一样。

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


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

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

我在datacontext上使用
.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;
        }

    }
private Dictionary\u attachedPayments;
公共作废储蓄付款(IList付款)
{
字典保存付款=
新字典();
//id为零的项目是新的
foreach(payments.Where中的Domain.paymentp(p=>p.PaymentId!=0))
{
//围绕linq datacontext工作的附加付款列表
//重复密钥异常
if(_attachedPayments.ContainsKey(p.PaymentId))//已附加
{
Payment dbPayment=_attachedPayments[p.PaymentId];
//只是一个将域映射到datacontext类型的方法
MapDomainPaymentToDBPayment(p,dbPayment,false);
savedPayments.Add(dbPayment,p);
}
else//将此付款附加到datacontext
{
Payment dbPayment=新付款();
MapDomainPaymentToDBPayment(p,dbPayment,true);
_dataContext.Payments.Attach(dbPayment,true);
savedPayments.Add(dbPayment,p);
}
}
//有一些代码被剪掉了,但这只是全新的支付方式
foreach(新付款中的var付款)
{
Domain.PaymentPayment1=付款;
Payment newPayment=新付款();
MapDomainPaymentToDBPayment(payment1,newPayment,false);
_dataContext.Payments.InsertOnSubmit(新支付);
savedPayments.Add(newPayment,payment);
}
尝试
{
_dataContext.SubmitChanges();
//将时间戳抓取到域对象中
foreach(savedPayments.key中的付款p)
{
savedPayments[p].PaymentId=p.PaymentId;
savedPayments[p]。时间戳=p。时间戳;
_attachedPayments[savedPayments[p].PaymentId]=p;
}
}
捕获(ChangeConflictException ex)
{