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を使用して同じレコードを2回編集すると、同時実行例外が発生します。

私が得る例外は次のとおりです: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    
}

[ConcurrencyCheck]属性を設定したために、concurrecnyチェックがテストされているLastUpdateという単一のフィールドを追加したことがわかります。

DevExpress ASPXGridViewを使用するWebページでは、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の記事をチェックしましたが、これを解決することはできません。

  • 私の投稿ほど「シンプル」な投稿はありませんでした。それらはすべて他のデータが関係していた。例:マスター/詳細の関係。カスタムエディタ。など。すべてのinbuildDevExpressコントロールを使用しており、dbテーブル/エンティティに単一のグリッドビューを表示するだけです。

  • 一部の投稿では、エンティティを更新することを提案しています。これを試しましたが、エンティティがオブジェクト状態マネージャーで追跡されていないという例外が発生します。

  • オブジェクトコンテキスト/データベースコンテキストを破棄して再作成することでエンティティフレームワークを更新しようとしましたが、どういうわけか、並行性の問題が発生します。

  • DBContexctとObjectContextを使用して更新してみました。どちらも機能しませんでした。(objContext.Refresh(RefreshMode.StoreWins、entity)。前述のように例外が発生します]エンティティが追跡されていないと言った場合、または変更されていないエンティティのみを更新するように指示した場合、何も起こりません(更新なし、例外)

  • DBContextをグローバルにしようとしましたが、WebFormsは、Webを更新するたびに、状態全体を再作成し、グリッドデータコンテキストなどを再フックしたいと考えているため、これは適切ではありません。(ページの読み込み、ユーザーのクリックによる編集、ユーザーのクリックによる更新)

現在、これらのソリューションはすべて、並行性の例外の後に何をすべきかを悩ませているようです。そもそも例外を取得するべきではないことを考えると、彼らは役に立たないと思います。

提案

これを機能させる方法について何か提案はありますか?

グリッドからデータを投稿した後、エンティティフレームワークを手動で更新する必要がありますか?(私は今これだけを考えました)

それは私が持っている非常に簡単なセットアップのようです。多分私は非常に明白な何かを逃しています。私はまだWebFormsやEntityFrameworkを使ったことがないので、私が見逃している単純な(そしておそらく明白な)解決策があるかもしれませんか?

助けていただければ幸いです。

ありがとうピーターメイズ

4

1 に答える 1

1

私は自分の問題をなんとか解決しました。

これは最も正しい解決策ではないかもしれませんが、機能しており、この時点での進歩は大いにありがたいです。

アプローチ

  • ASPXGridViewにデータを投稿した後、EntityFrameworkを更新してみました。多くの試み。何も機能しませんでした。
  • 国エンティティでTimeStamp属性を使用しようとしましたが、これはMySQLにうまくマッピングされていないようです。(ただし、問題を解決したので、これを再試行する可能性があります)
  • 次に、DevArtMySQLドットコネクタとMySQLに問題があるのではないかと思いました。そこで、MSSQLとその標準コネクタに切り替えました。これは、MySQL&coで発生しているのと同じ問題を示しています。

最後に、私はさまざまな試みをいじくり回していて、自分のWebサイトの別のページに移動しても、再び問題が発生しないことに気づきました。

例えば:

  • 国を編集して保存します。問題ない。
  • 他のサイトページに切り替えます。
  • 国に戻ります。
  • 国を編集して保存します。問題ない。

違いは、ページを切り替えない場合、2回目の編集で同時実行例外が発生することです。

同僚とのテストをさらに行ったところ、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を更新してはいけないことを覚えておいてください。なぜこれが起こるのか分かりません。非常に奇妙な。

たぶん、編集フォームが閉じるまで、OldValuesはDevExpress ASPXGridViewによって取得されませんか?


とにかく、それから私は思った。いいでしょう、私はその奇妙なことを回避します。そのために、Countryエンティティのコピーを保存する静的メンバーをWebページに作成しました。

ユーザーがエディターを開くと、現在の値を取得して保存します。

次に、ASPXGridView.RowUpdatingが起動すると、保存されている古い値をOldValuesデータにプッシュして戻します。(ここでも、NewValuesデータのtimstamp / concurrencyフィールドを更新します)

このアプローチにより、私の並行性が機能するようになりました。フラ!

ローカルで好きなだけ編集でき、競合は発生しません。2つのブラウザーで同時に編集すると、2番目に投稿するブラウザーで同時実行例外が発生します。

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 に答える