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