处理ChangeConflictException时,如何防止重复插入?

时间:2015-12-29 15:21:06

标签: c# wcf linq-to-sql

我们有一个WCF服务,如下所述,带有相关的扩展方法。我们在数据库中获取重复插入以获取客户详细信息。我知道为什么,但我不确定如何预防它。

header.shrink .nav > li > a > span.icon {
    display: block; (or inline)
}

我认为问题非常简单。发生的事情是客户正在呼叫一次插入3个细节。出于本讨论范围之外的原因,我们必须在更新或添加客户的新详细信息时更新最后修改的客户(此字段的更改对于使用此数据库的其他系统非常重要。)

客户详细信息的第一个插入完成得很好。出现问题的地方是插入其他细节。这些都是线程调用,因此它试图同时执行所有这些操作。调用public interface ICustomerService { [OperationContract(IsTerminating = false, IsInitiating = true, IsOneWay = false, AsyncPattern = false)] [FaultContract(typeof (ServiceFaultContract))] [FaultContract(typeof (ServiceTimeoutFaultContract))] DetailDataContract UpdateDetail(int entityId, RecordType recordType, string detailKey, string details); } [ServiceBehavior(TransactionAutoCompleteOnSessionClose = false, TransactionIsolationLevel = IsolationLevel.ReadUncommitted)] [ExceptionShielding("ServiceLayerPolicy")] public abstract class CustomerServiceBase : ServiceBase, ICustomerService { [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)] public virtual DetailDataContract UpdateDetail(int entityId, RecordType recordType, string detailKey, string details) { throw new NotImplementedException(); } } public class CustomerService : CustomerServiceBase { public override DetailDataContract UpdateDetail(int entityId, string detailType, string detailData) { try { using (var db = new DataContext()) { var customer = db.Customers.FirstOrDefault(c => c.CustomerId == entityId); if(customer == null) throw new Exception("Customer not found."); return customer.UpdateOrAddDetail(detailType, detailData).ToDataContract(); } } catch { //logging code removed throw; } } } public static class CustomerExtensions { public static Detail UpdateOrAddDetail(this Customer customer, DataContext db, string type, string data) { //see if the customer has this detail var detail = customer.Details.FirstOrDefault(x => x.Type == type); //if the customer does, update it (code removed) //if the customer doesn't, insert it (code removed) customer.LastModified = DateTime.Now; db.SaveChanges(); return detail; } } public static class DataContextExtensions { public static void SaveChanges(this DataContext db, RefreshMode mode = RefreshMode.KeepChanges) { try { db.SubmitChanges(ConflictMode.ContinueOnConflict); } catch (ChangeConflictException) { //logging code removed db.ChangeConflicts.ResolveAll(mode); //Resubmit try { db.SubmitChanges(ConflictMode.ContinueOnConflict); } catch (Exception ex) { //more logging code removed throw; } } } } 时第二个(及后续)详细信息的插入工作正常,但在更新db.SubmitChanges时会抛出ChangeConflictException,因为第一个细节更改了Customer插入另一个线程。我很酷,所以我只是解决它并尝试再次提交更改。

这就是问题所在。当第二个LastModified被调用时,行会再次插入。

交易如上,由WCF处理。我不能只回滚SubmitChanges,因为我什么也做不了。我已经尝试在提交更改之前刷新datacontext但我仍然得到Transaction.Current,从而得到重复的行。

我需要能够撤消插入或者不进行第二次插入或者有某种方法来回滚当前事务但仍然能够继续插入和解决冲突。

我最终得到了:

ChangeConflictException

1 个答案:

答案 0 :(得分:0)

UpdateOrAddDetail方法中,您首先阅读一个值,然后根据该值做出决定,最后保存更改。如果该方法由并行中的两个线程运行,则将存在重复值。您需要确保在检查完成后,没有其他实例可以在保存数据之前更新数据。

为此,您需要进行两项更改:

  1. 将事务的隔离级别更改为Serializable,这将确保一旦进行一次更新,其他任何内容都无法完成。
  2. 不幸的是只做那件事就会给你一个僵局。原因是使用FirstOrDefault读取记录只会给出一个共享的读锁定,在这种情况下它太弱了。这带来了第二次改变。

    1. 更改读取以使用显式SQL,它允许指定更新锁定,这会使读取上的任何其他并发进程块。
    2. var detail = db.ExecuteQuery<CustomerDetail>(
        "SELECT * FROM CustomerDetail WITH (UPDLOCK) WHERE CustomerId = {0} AND Type = {1}",
        customer.CustomerId, type);
        customer.Details.FirstOrDefault(x => x.Type == type);