更新选择性字段时,EF5无法处理并发

时间:2016-03-23 06:36:06

标签: c# asp.net-mvc database entity-framework concurrency

我正在使用EF5和Data First方法来更新实体。 我正在使用其他问题建议的方法来有条件地更新实体中的已修改属性。 Oki所以这里的场景我的控制器用POCO对象调用Service并从Service获取POCO对象,Service层与Data层对话,Data层在内部使用EF5从DB检索实体并在DB中更新它们。 控制器从服务层检索的DTO对象加载View数据。 用户更改View和Posts将JSON数据发送回控制器,控制器映射到控制器中的DTO对象(礼貌MVC)。 控制器使用DTO对象(PO​​CO)对象调用Service层。 服务将POCO对象映射到EF实体对象,并调用传入EF实体的数据层(即存储库)更新方法。 在Repository中,我从DB获取现有实体并调用ApplyCurrentvaluesValues方法,然后检查是否修改了任何属性。 如果属性被修改,那么我将我的自定义逻辑应用于与当前实体无关的其他实体,并且还更新" UpdatedAdminId" &安培; " UpdationDate"当前实体 发布这个我打电话" SaveChanges" Centext上的方法。 我提到的每一件事都很好,除非我在" SaveChanges"中插入一个断点。调用并更新User修改的一些字段到不同的值然后" DbUpdateConcurrencyException"不是EF5抛出的。 即我可以获得有条件的更新&当我感兴趣的属性被修改为完美地工作时触发我的自定义逻辑。 但是在并发的情况下我没有得到错误,即EF没有提高" DbUpdateConcurrencyException"如果在我从DB获取记录,更新记录并保存记录之间更新记录。 在实际场景中,有一个离线cron运行,它检查新创建的广告系列并为它们创建投资组合,并将下面的IsPortfolioCreated属性标记为true,同时用户可以编辑广告系列,并且即使cron也可以将标志设置为false创建了投资组合。 为了复制并发场景,我在SaveChanges上设置了一个断点,然后从MS-Sql企业管理器中为同一个实体更新了IsPortfolioCreated feild,但是" DbUpdateConcurrencyException"即使已更新存储中的数据,也不会抛出。 这是我的代码供参考, 公共bool EditGeneralSettings(CampaignDefinition campaignDefinition) {     var success = false;     //campaignDefinition.UpdatedAdminId在控制器中通过从RquestContext中检索来更新,所以它不是来自客户端的comgin     var updatedAdminId = campaignDefinition.UpdatedAdminId;     var updationDate = DateTime.UtcNow;     CmsContext context = null;     GlobalMasterContext globalMasterContext = null;     尝试     {         context = new CmsContext(SaveTimeout);         var contextCampaign = context.CampaignDefinitions.Where(x => x.CampaignId == campaignDefinition.CampaignId).First();         //始终使用Server中的这些字段,无论客户端是什么         campaignDefinition.CreationDate = contextCampaign.CreationDate;         campaignDefinition.UpdatedAdminId = contextCampaign.UpdatedAdminId;         campaignDefinition.UpdationDate = contextCampaign.UpdationDate;         campaignDefinition.AdminId = contextCampaign.AdminId;         campaignDefinition.AutoDecision = contextCampaign.AutoDecision;         campaignDefinition.CampaignCode = contextCampaign.CampaignCode;         campaignDefinition.IsPortfolioCreated = contextCampaign.IsPortfolioCreated;         var campaignNameChanged = contextCampaign.CampaignName!= campaignDefinition.CampaignName;         //将在下面的条件中使用....         var originalSkeForwardingDomain = contextCampaign.skeForwardingDomain.ToLower();         var originalMgForwardingDomain = contextCampaign.mgForwardingDomain.ToLower();         //这也没有解决concurreny异常....         var key =((IObjectContextAdapter)context).ObjectContext.CreateEntityKey(" CampaignDefinitions",campaignDefinition);         ((IObjectContextAdapter)context).ObjectContext.AttachTo(" CampaignDefinitions",contextCampaign);         var updated =((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName,campaignDefinition);         ObjectStateEntry entry =((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(updated);         var modifiedProperties = entry.GetModifiedProperties();         //甚至试过这个,工作正常但没有并发异常         // var entry = context.Entry(contextCampaign);         //entry.CurrentValues.SetValues(campaignDefinition);         // var modifiedProperties = entry.CurrentValues.PropertyNames.Where(propertyName => entry.Property(propertyName).IsModified)。ToList();         //如果修改了任何字段,则只设置Updations字段         if(modifiedProperties.Count()> 0)         {             campaignDefinition.UpdatedAdminId = updatedAdminId;             campaignDefinition.UpdationDate = updationDate;             //entry.CurrentValues.SetValues(campaignDefinition);             updated =((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName,campaignDefinition);             //还在其他实体中执行一些自定义逻辑...然后调用保存更改             context.SaveChanges();             //如果广告系列名称更改,请在不同的数据库中调用SP             if(campaignNameChanged)             {                 globalMasterContext = new GlobalMasterContext(SaveTimeout);                 globalMasterContext.Rename_CMS_Campaign(campaignDefinition.CampaignId,updatedAdminId);                 globalMasterContext.SaveChanges();             }         }         success = true;     }     catch(DbUpdateConcurrencyException ex)     {         //代码永远不会进入此处,如果确实如此,我计划向用户显示来自DB的值并要求他重试         //简而言之,赢得了战略         //这个块中的代码不完整所以不要Stackies不要开始评论这个部分并且困扰问题......         //获取当前实体值和数据库中的值         var entry = ex.Entries.Single();         var currentValues = entry.CurrentValues;         var databaseValues = entry.GetDatabaseValues();         //选择一组初始解析值。在这种情况下我们         //使默认值为当前数据库中的值。         var resolvedValues = databaseValues.Clone();         //使用数据库值更新原始值和         //用户选择的当前值。         entry.OriginalValues.SetValues(databaseValues);         entry.CurrentValues.SetValues(resolvedValues);     }     catch(Exception ex)     {         if(ex.InnerException!= null)             抛出ex.InnerException;         扔;     }     最后     {         if(context!= null)context.Dispose();         if(globalMasterContext!= null)globalMasterContext.Dispose();     }     回归成功; }

1 个答案:

答案 0 :(得分:2)

实体框架在你(作为开发人员)将其配置为检查并发性问题之前,并没有对并发性做任何特殊处理。

您正在尝试捕获DbUpdateConcurrencyException,此异常的文档说:“当预期实体的SaveChanges将导致数据库更新但实际上没有行中的DbContext时抛出异常数据库受到影响。“,您可以阅读here

在数据库第一种方法中,您必须为'Fixed'上的列设置属性'Concurrency Mode'(默认为None)。请看这个屏幕截图:enter image description here

列版本是SQL SERVER TIMESTAMP类型,这是一种特殊类型,每次更改行时都会自动更新,请阅读here

使用此配置,如果所有操作都按预期工作,您可以尝试使用此简单测试:

try
{
    using (var outerContext = new testEntities())
    {
        var outerCust1 = outerContext.Customer.FirstOrDefault(x => x.Id == 1);
        outerCust1.Description += "modified by outer context";
        using (var innerContext = new testEntities())
        {
            var innerCust1 = innerContext.Customer.FirstOrDefault(x => x.Id == 1);
            innerCust1.Description += "modified by inner context";
            innerContext.SaveChanges();
        }
        outerContext.SaveChanges();
    }
}
catch (DbUpdateConcurrencyException ext)
{
    Console.WriteLine(ext.Message);
}

在上面的示例中,将提交内部上下文的更新,来自外部上下文的更新将抛出DbUpdateConcurrencyException,因为EF将尝试使用2列作为过滤器更新实体:Id和Version列。

希望这有帮助!