0

我的问题

我有一个用于测试并发性的简单 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 文章,我就是无法解决这个问题。

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

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

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

  • 我尝试使用 DBContext 和 ObjectContext 进行刷新。都没有奏效。(objContext.Refresh(RefreshMode.StoreWins,实体)。我要么得到一个异常,如前所述]实体没有被跟踪,或者如果我告诉它只刷新未修改的实体,那么什么都不会发生(没有刷新,没有例外)

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

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

建议

你们中有人对如何进行这项工作有任何建议吗?

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

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

任何帮助表示赞赏。

谢谢彼得梅斯

4

1 回答 1

1

我设法解决了我的问题。

它可能不是最正确的解决方案,但它正在工作,并且非常感谢此时的任何进展。

方法

  • 在 ASPXGridView 中发布数据后,我尝试刷新实体框架。许多尝试。没有工作。
  • 我尝试在我的 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 可能不会检索 OldValues?


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

当用户打开编辑器时,我得到当前值并存储它们。

然后,当 ASPXGridView.RowUpdating 触发时,我将存储的旧值推回到 OldValues 数据中。(我也在 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;
    }
    
于 2012-09-07T07:27:42.017 回答