Devexpress ASPXGridView和EntityFramework 4.3.1的并发异常

时间:2012-09-05 04:54:24

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

我的问题

我有一个简单的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]属性,正在测试concurrecny检查。

在我的网页上使用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 
}

我也有一些按钮事件被挂钩来测​​试直接的concurrecny测试。

例如

- 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控件,只显示一个 我的数据库表/实体上的单个网格视图。

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

  • 我尝试通过销毁和重新创建对象context / db来刷新实体框架 上下文,但不知怎的,我仍然得到并发问题。

  • 我尝试使用DBContexct和ObjectContext进行刷新。都没有奏效。 (objContext.Refresh(RefreshMode.StoreWins,entity)。我要么得到一个例外,如所述] 较早说,该实体未被跟踪,或者我告诉它只刷新非修改 然后实体根本没有任何事情发生(没有刷新,没有激发)

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

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

建议

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

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

这似乎是一个非常简单的设置。也许我错过了一些非常明显的东西。我还没有使用WebForms或EntityFramework,所以我可能会找到简单(也许是显而易见的)解决方案吗?

任何帮助表示感谢。

由于 彼得梅斯

1 个答案:

答案 0 :(得分:1)

我设法解决了我的问题。

它可能不是最正确的解决方案,但它正在发挥作用,此时我们非常感谢任何进展。

方法

  • 我在ASPXGridView中发布数据后尝试刷新Entity Framework。   许多尝试。没有工作。
  • 我尝试在我的国家/地区实体上使用TimeStamp属性,但确实如此   似乎没有很好地映射到MySQL。 (但我现在可能会再试一次  我已经解决了这个问题)
  • 然后我想也许我的DevArt MySQL点连接器和MySQL出错了。   所以我切换到MSSQL及其标准连接器。这显示了相同的   问题与MySQL&有限公司

最后,我正在进行各种尝试,并注意到如果我去了另一个 在我的网站上的页面,然后再回来问题不会发生。

E.g:

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

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

随着与同事的一些更多测试,我得到了一个预感,也许是观众 在ASPGridView上发布/更新后,实体数据源未刷新。

所以我做的是:

> Set EntityDataSource.StoreOriginalValuesInViewState = FALSE

由于没有存储旧的/预编辑值,因此停止了所有并发工作 没有可用于concurrecny检查。

然后我想我会在编辑之前强制旧值在编辑器中。 我使用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。我不知道为什么会这样。很奇怪。

也许OldValues不会被DevExpress ASPXGridView检索到 编辑表格正在关闭?


无论如何,我想。好吧,我会解决这个奇怪的问题。所以我这样做了 网页上的静态成员,用于存储我的国家/地区实体的副本。

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

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

通过这种方法,我的并发现在可以正常工作。 Hurah!

我可以根据需要在本地编辑,不会发生冲突。如果我一次在两个浏览器中编辑,则第二个浏览器会引发并发异常。

我也可以在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;
    }