我使用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生成的对象一直到我的堆栈来避免这一切 - 但我不喜欢我不喜欢我处理的方式并发性。
答案 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功能,但抽象掉数据层的想法是如此的不依赖于特定的实现的功能等,所以具有乐观并发在服务层上你的域对象检查的完全控制(例如)似乎对我来说是个好方法。