【问题标题】:Concurrency exception with Devexpress ASPXGridView and EntityFramework 4.3.1Devexpress ASPXGridView 和 EntityFramework 4.3.1 的并发异常
【发布时间】:2012-09-05 04:54:24
【问题描述】:

我的问题

我有一个用于测试并发性的简单 WebForms 项目。

我正在使用:

1. Entity Framework 4.3.1 code first approach.
2. DevExpress ASP.net controls to visualize my data. Specifically an ASPXGridView control.
3. MySQL as database backend.

现在我遇到了并发检查问题。

即使我是唯一编辑数据的用户,如果我使用 DevExpress ASPXGridView 两次编辑同一记录,我也会遇到并发异常!

我得到的例外是: System.Data.OptimisticConcurrencyException

我的设置

** 为简洁起见,此处简化

我的代码优先实体是这样定义的:

public class Country
{
    //Some constructors here

    [Required, ConcurrencyCheck]       
    public virtual string LastUpdate { get; set; }

    [Required, Key, DatabaseGenerated(DatabaseGeneratedOption.None)]        
    public virtual int CountryID { get; set; }

    //Various other data fields here    
}

您可以看到我添加了一个名为 LastUpdate 的字段,由于设置了 [ConcurrencyCheck] 属性,正在测试并发检查。

在我的带有 DevExpress ASPXGridView 的网页上,我使用 EntityDataSource 在网格视图和实体框架之间进行绑定。网格视图正在使用弹出编辑器。我有以下事件:

protected void Page_Load(object sender, EventArgs e)
{            
    //Hook entity datasource to grid view
    dbgCountries.DataSource = CountriesEntityDataSource;
    dbgCountries.DataBind(); 
}          

protected void CountriesEntityDataSource_ContextCreating(object sender, EntityDataSourceContextCreatingEventArgs e)
{
    //Create and hook my DBContext class to the entity
    //datasources ObjectContext property.
    var context = new MyDBContextClass();          
    e.Context = ((IObjectContextAdapter)context ).ObjectContext;       
}

protected void dbgCountries_InitNewRow(object sender, DevExpress.Web.Data.ASPxDataInitNewRowEventArgs e)
{
    //I create a new MyDBContextClass here and use it
    //to get the next free id for the new record 
}

protected void dbgCountries_CustomErrorText(object sender, DevExpress.Web.ASPxGridView.ASPxGridViewCustomErrorTextEventArgs e)
{ 
    //My code to catch the System.Data.OptimisticConcurrencyException
    //excpetion is in here. 

    //I try to rtefresh the entity here to get the latest data from
    //database but I get an exception saying the entity is not being
    //tracked
}

protected void dbgCountries_RowValidating(object sender, DevExpress.Web.Data.ASPxDataValidationEventArgs e)
{
    //Basic validation of record update in here
}

protected void dbgCountries_RowUpdating(object sender, DevExpress.Web.Data.ASPxDataUpdatingEventArgs e)
{
    //I set the LastUpdate field (my concurrency field)
    //to the current time here 
}

我还连接了一些按钮事件来测试直接并发测试。

例如

- Get Entity
- Update Entity
- Update DB directly with sql
- Save Entity
- Get concurrency exception as expected

例如

- Get Entity
- Update Entity      
- Save Entity
- No issue.
- Get Entity again.
- Update Entity again.      
- Save Entity again.
- No issue.

这些按钮按预期工作。只有网格更新似乎有问题。

可能是因为网格需要使用ObjectContect,而我的实体框架类使用的是DBContext?

我尝试的修复

我搜索了互联网试图找到解决方案。检查了 DevExpress 论坛,检查了 StackOverflow 上的其他帖子,互联网上的各种帖子,Microsoft MSDN 关于并发的文章,我就是无法解决这个问题。

  • 没有一个帖子像我的那样“简单”。他们都涉及其他数据。例如主/详细信息 关系。自定义编辑器。等等我正在使用所有内置的 DevExpress 控件,只显示一个 我的数据库表/实体上的单个网格视图。

  • 一些帖子建议刷新实体。我试过这个但得到一个异常说实体是 在对象状态管理器中没有被跟踪。

  • 我尝试通过销毁和重新创建我的对象上下文/数据库来刷新实体框架 上下文,但不知何故我仍然遇到并发问题。

  • 我尝试使用 DBContext 和 ObjectContext 进行刷新。都没有奏效。 (objContext.Refresh(RefreshMode.StoreWins,实体)。我要么得到一个异常,如所述] 早些时候说实体没有被跟踪,或者如果我告诉它只刷新非修改 实体然后什么都没有发生(没有刷新,没有异常)

  • 我尝试将我的 DBContext 设为全局,但这并不好,因为 WebForms 似乎想要重新创建它 整个状态并在每次网络刷新后重新挂钩其网格数据上下文等。 (页面加载,用户 点击编辑,用户点击确定更新)

现在所有这些解决方案似乎都考虑了在并发异常之后要做什么。看到我什至不应该首先得到异常,我想他们不会有帮助。

建议

你们有没有关于如何完成这项工作的建议?

从网格发布数据后,我是否必须强制实体框架手动刷新? (我现在才想到这个)

我的设置似乎很简单。也许我错过了一些非常明显的东西。我还没有过多地使用 WebForms 或 EntityFramework,所以可能缺少一些简单(而且可能很明显)的解决方案?

任何帮助表示赞赏。

谢谢 彼得·梅斯

【问题讨论】:

    标签: asp.net entity-framework devexpress optimistic-concurrency


    【解决方案1】:

    我已经设法解决了我的问题。

    这可能不是最正确的解决方案,但它正在发挥作用,非常感谢您在这一点上取得的任何进展。

    方法

    • 在 ASPXGridView 中发布数据后,我尝试刷新 Entity Framework。 许多尝试。没有任何效果。
    • 我尝试在我的 Country 实体上使用 TimeStamp 属性,但确实如此 似乎不能很好地映射到 MySQL。 (但是我现在可能会再试一次 我已经解决了这个问题)
    • 然后我想也许我的 DevArt MySQL 点连接器和 MySQL 有问题。 所以我切换到 MSSQL 及其标准连接器。这表明相同 MySQL & co 遇到的问题。

    最后,我尝试了各种尝试,发现如果我去不同的地方 我的网站上的页面,然后再返回问题不会发生。

    例如:

    • 编辑国家并保存。没有问题。
    • 切换到其他网站页面。
    • 切换回国家/地区。
    • 编辑国家并保存。没有问题。

    不同之处在于,如果我切换页面,则第二次编辑会创建并发异常。

    在与同事进行更多测试后,我有一种预感,可能是 在 ASPGridView 上发布/更新后,实体数据源没有被刷新。

    所以我做的是:

    > Set EntityDataSource.StoreOriginalValuesInViewState = FALSE
    

    这停止了所有并发工作,因为没有存储旧的/预编辑值,所以
    不能用于并发检查。

    然后我想我会强制旧值成为我编辑之前在编辑器中的内容。 我正在使用 ASPXGridView.RowUpdating 来执行此操作。

    我认为没关系,我可以使用传递给 ASPXGridView.RowUpdating 的 OldValues 确保实体框架顺利运行。

    这样做我发现了一些非常奇怪的行为......

    如果我:

    - open edit form in browser A
    - open edit form in browser B
    - save changes in browser B (DB updates with new values here)
    - save changes in browser A (DB updated here too. but should have been a
                                concurrency exception!)
    

    来自 A 的帖子成功的原因是 A 上的 OldValues 已经神奇地更新了 到 B 发布的新值!

    请记住,A 上的编辑表单一直处于打开状态,因此它不应该更新其下方的 OldValues。我不知道为什么会这样。很奇怪。

    DevExpress ASPXGridView 可能直到 编辑表单正在关闭?


    无论如何,我想。好吧,我会解决这个奇怪的问题。为此,我创建了 网页上的静态成员,用于存储我的 Country 实体的副本。

    当用户打开编辑器时,我会获取当前值并存储它们。

    然后,当 ASPXGridView.RowUpdating 触发时,我将存储的旧值推回 旧值数据。 (我也在 NewValues 中更新了我的 timstamp/concurrency 字段 数据)

    通过这种方法,我的并发现在可以工作了。万岁!

    我可以随心所欲地在本地进行编辑,并且不会发生冲突。如果我同时在两个浏览器中编辑,第二个要发布的浏览器会引发并发异常。

    我还可以在 MySQL 和 MSSQL 之间切换,现在都可以正常工作了。


    解决方案总结

    1. 设置 EntityDataSource.StoreOriginalValuesInViewState = FALSE。 (我在设计器中做到了这一点。)
    2. 创建私有成员以保存预编辑的国家/地区值

       private static Country editCountry = null;  
      
    3. 在 ASPXGridView 上挂钩 StartRowEditing。在这里,我获取当前的国家/地区值并将它们存储为“预编辑”值。请注意,CopyFrom 只是我实体上的一个辅助方法。

       protected void dbgCountries_StartRowEditing(object sender, DevExpress.Web.Data.ASPxStartRowEditingEventArgs e)
       {
       if (editCountry == null)
       {
           editCountry = new Country();                               
       }
      
      var context = new MyDBContext();
      var currCountry = context.Countries.Where(c => c.CountryID == (int)(e.EditingKeyValue)).Single();
      editCountry.CopyFrom(currCountry);
      }
      
    4. 在 ASPXGridView 上挂钩 RowUpdating。这是我在更新继续之前确保旧值正确的地方。这可确保并发按预期工作。

      protected void dbgCountries_RowUpdating(object sender, DevExpress.Web.Data.ASPxDataUpdatingEventArgs e)
      {
      //Ensure old values are populated
      e.OldValues["RowVersion"] = editCountry.RowVersion;
      
      //No need to set other old values as I am only testing against
      //the one field for concurrency. 
      
      //On row updating ensure RowVersion is set. 
      //This is the field being used for the concurrency check.
      DateTime date = DateTime.Now;         
      var s = date.ToString("yyyy-MM-dd HH:mm:ss");                        
      e.NewValues["RowVersion"] = s;
      }
      

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多