SQL Server Compact Editionデータベース上のLINQ to SQLで「行が見つからないか変更されていません」という例外を解決するにはどうすればよいですか?


96

(SQL Server Compact Editionに対して)LINQ to SQL接続でいくつかのプロパティを更新した後、DataContextにSubmitChangesを実行すると、「行が見つからないか、変更されていません」というメッセージが表示されます。ChangeConflictException。

var ctx = new Data.MobileServerDataDataContext(Common.DatabasePath);
var deviceSessionRecord = ctx.Sessions.First(sess => sess.SessionRecId == args.DeviceSessionId);

deviceSessionRecord.IsActive = false;
deviceSessionRecord.Disconnected = DateTime.Now;

ctx.SubmitChanges();

クエリは次のSQLを生成します。

UPDATE [Sessions]
SET [Is_Active] = @p0, [Disconnected] = @p1
WHERE 0 = 1
-- @p0: Input Boolean (Size = 0; Prec = 0; Scale = 0) [False]
-- @p1: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:12:02 PM]
-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.21022.8

明らかな問題はWHERE 0 = 1です。レコードがロードされた後、「deviceSessionRecord」のすべてのプロパティが主キーを含むように正しいことを確認しました。また、「ChangeConflictException」をキャッチするとき、これが失敗した理由に関する追加情報はありません。また、この例外がデータベース内の1つのレコード(更新しようとしているレコード)でスローされることも確認しました

奇妙なのは、コードの別のセクションに非常によく似たupdateステートメントがあり、次のSQLが生成され、実際にSQL Server Compact Editionデータベースが更新されることです。

UPDATE [Sessions]
SET [Is_Active] = @p4, [Disconnected] = @p5
WHERE ([Session_RecId] = @p0) AND ([App_RecId] = @p1) AND ([Is_Active] = 1) AND ([Established] = @p2) AND ([Disconnected] IS NULL) AND ([Member_Id] IS NULL) AND ([Company_Id] IS NULL) AND ([Site] IS NULL) AND (NOT ([Is_Device] = 1)) AND ([Machine_Name] = @p3)
-- @p0: Input Guid (Size = 0; Prec = 0; Scale = 0) [0fbbee53-cf4c-4643-9045-e0a284ad131b]
-- @p1: Input Guid (Size = 0; Prec = 0; Scale = 0) [7a174954-dd18-406e-833d-8da650207d3d]
-- @p2: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:20:50 PM]
-- @p3: Input String (Size = 0; Prec = 0; Scale = 0) [CWMOBILEDEV]
-- @p4: Input Boolean (Size = 0; Prec = 0; Scale = 0) [False]
-- @p5: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:20:52 PM]
-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.21022.8

データベーススキーマとLINQクラスを生成するDBMLの両方で適切なプライマリフィールド値が識別されていることを確認しました。

これはほぼ2つの部分からなる質問だと思います。

  1. 例外がスローされるのはなぜですか?
  2. 生成されたSQLの2番目のセットを確認した後、競合を検出するためにすべてのフィールドをチェックすることは良いようですが、これはかなり非効率的だと思います。これは常にこれが機能する方法ですか?主キーだけをチェックする設定はありますか?

私は過去2時間これと戦ってきたので、どんな助けでもありがたいです。


FWIW:誤ってメソッドを2回呼び出すと、このエラーが発生しました。2番目の呼び出しで発生します。
Kris

優れた背景情報は、c-sharpcorner.com
article /

回答:


189

それは厄介ですが、単純です:

O / R-Designerのすべてのフィールドのデータ型がSQLテーブルのデータ型と一致するかどうかを確認します。 null可能かどうか再確認してください!列は、O / R-DesignerとSQLの両方でNULL可能であるか、両方でNULL可能であってはなりません。

たとえば、NVARCHAR列「title」はデータベースでNULL可能としてマークされ、値NULLが含まれています。列がO / RマッピングでNOT NULLableとしてマークされていても、LINQはそれを正常にロードし、column-Stringをnullに設定します。

  • 次に、何かを変更して、SubmitChanges()を呼び出します。
  • LINQは、「WHERE [title] IS NULL」を含むSQLクエリを生成して、タイトルが他のユーザーによって変更されていないことを確認します。
  • LINQは[title]のプロパティをマッピングで検索します。
  • LINQは[title] NOT NULLableを検出します。
  • [title]はNULL可能ではないため、ロジックによっては決してNULLになることはありません!
  • そのため、クエリを最適化すると、LINQはSQLで「never」に相当する「where 0 = 1」に置き換えます。

LINQはデータを読み取ってからSQLデータが変更されていないことを確認できないため、フィールドのデータ型がSQLのデータ型と一致しない場合、またはフィールドが欠落している場合にも、同じ症状が発生します。


4
私には似たような問題がありました-少し異なりますが-nullableを再確認するためのアドバイスは私の日を救いました!私はもうハゲでしたが、この問題があったなら、髪の毛をもう1つ持っていれば、きっと私には別の頭髪がかかるでしょう。
Rune Jacobsen、

7
プロパティウィンドウの「Nullable」プロパティをTrueに設定していることを確認してください。'Server Data Type'プロパティを編集して、からVARCHAR(MAX) NOT NULLに変更し、機能することVARCHAR(MAX) NULLを期待していました。非常に単純な間違い。

これを賛成しなければなりませんでした。時間を大幅に節約できました。同時実行の問題だと思っていたので、分離レベルを調べていました
エイドリアン

3
プロパティにNUMERIC(12,8)マップされた列がありDecimalました。列属性のDbTypeを正確に指定する必要があった [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
Costo

3
問題のあるフィールド/列を特定する1つの方法は、.dbmlファイルにある現在のLinq-to-SQLエンティティクラスを別のファイルに保存することです。次に、現在のモデルを削除してデータベースから再生成し(VSを使用)、新しい.dbmlファイルを生成します。次に、2つの.dbmlファイルでWinMergeやWinDiffなどのコンパレータを実行して、問題の違いを見つけます。
david.barkhuizen 2012年

24

まず、何が問題を引き起こしているのかを知ることは有用です。グーグルソリューションが役立つはずです。競合についての詳細(テーブル、列、古い値、新しい値)をログに記録して、後で競合を解決するためのより良いソリューションを見つけることができます。

public class ChangeConflictExceptionWithDetails : ChangeConflictException
{
    public ChangeConflictExceptionWithDetails(ChangeConflictException inner, DataContext context)
        : base(inner.Message + " " + GetChangeConflictExceptionDetailString(context))
    {
    }

    /// <summary>
    /// Code from following link
    /// https://ittecture.wordpress.com/2008/10/17/tip-of-the-day-3/
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    static string GetChangeConflictExceptionDetailString(DataContext context)
    {
        StringBuilder sb = new StringBuilder();

        foreach (ObjectChangeConflict changeConflict in context.ChangeConflicts)
        {
            System.Data.Linq.Mapping.MetaTable metatable = context.Mapping.GetTable(changeConflict.Object.GetType());

            sb.AppendFormat("Table name: {0}", metatable.TableName);
            sb.AppendLine();

            foreach (MemberChangeConflict col in changeConflict.MemberConflicts)
            {
                sb.AppendFormat("Column name : {0}", col.Member.Name);
                sb.AppendLine();
                sb.AppendFormat("Original value : {0}", col.OriginalValue.ToString());
                sb.AppendLine();
                sb.AppendFormat("Current value : {0}", col.CurrentValue.ToString());
                sb.AppendLine();
                sb.AppendFormat("Database value : {0}", col.DatabaseValue.ToString());
                sb.AppendLine();
                sb.AppendLine();
            }
        }

        return sb.ToString();
    }
}

sumbitChangesをラップするヘルパーを作成します。

public static class DataContextExtensions
{
    public static void SubmitChangesWithDetailException(this DataContext dataContext)
    {   
        try
        {         
            dataContext.SubmitChanges();
        }
        catch (ChangeConflictException ex)
        {
            throw new ChangeConflictExceptionWithDetails(ex, dataContext);
        }           
    }
}

次に、送信変更コードを呼び出します。

Datamodel.SubmitChangesWithDetailException();

最後に、グローバル例外ハンドラに例外を記録します。

protected void Application_Error(object sender, EventArgs e)
{         
    Exception ex = Server.GetLastError();
    //TODO
}

3
素晴らしいソリューション!約80のフィールドを持つテーブルがあり、挿入および更新中にさまざまなフィールドを更新しているテーブルに多数のトリガーがあります。L2Sを使用してデータコンテキストを更新するときにこのエラーが発生しましたが、トリガーの1つがフィールドを更新しているため、データコンテキストがテーブル内のデータと異なることが原因であることがかなりわかりました。あなたのコードは、データコンテキストがテーブルと同期しなくなっている原因となっているフィールドを正確に特定するのに役立ちました。トンありがとう!
ヤークト

1
これは大きなテーブルに最適なソリューションです。nullを処理するには、 'col.XValue.ToString()'を 'col.XValue == null?"null":3つの値フィールドのそれぞれに対するcol.XValue.ToString() '。
humbads

OriginalValue、CurrentValue、およびDatabaseValueを文字列化するときにnull参照から保護することについても同様です。
フロイドコッシュ

16

ここで役立つリフレッシュと呼ばれるメソッドがDataContextにあります。変更を送信する前にデータベースレコードを再ロードでき、保持する値を決定するためのさまざまなモードを提供します。「KeepChanges」は私の目的にとって最も賢く見えます。これは、私の変更を、その間にデータベースで発生した競合しない変更とマージすることを目的としています。

正しく理解できれば。:)


5
この回答は私の場合の問題を修正しました:dc.Refresh(RefreshMode.KeepChanges,changedObject);dc.SubmitChangesの前
HugoRune

動的データWebサイトのプロパティにReadOnlyAttributeを適用すると、この問題が発生しました。更新が機能しなくなり、「行が見つからないか変更されました」というエラーが表示されました(挿入は問題ありませんでした)。上記の修正により、多くの労力と時間を節約できました。
Chris Cannon

たとえば、RefreshMode値について説明してください。たとえば、KeepCurrentValuesの意味を教えてください。それは何をするためのものか?どうもありがとう。私は質問を作成できます...
Chris Cannon

別のトランザクションが同じ行で開始するのに間に合うように同時トランザクションが完了しないという問題がありました。KeepChangesはここで私を助けたので、おそらくそれは(保存された値を維持しながら)現在のトランザクションを中止し、新しいトランザクションを開始します(正直に私は
知り

11

これは、複数のDbContextを使用することによっても発生します。

だから例えば:

protected async Task loginUser(string username)
{
    using(var db = new Db())
    {
        var user = await db.Users
            .SingleAsync(u => u.Username == username);
        user.LastLogin = DateTime.UtcNow;
        await db.SaveChangesAsync();
    }
}

protected async Task doSomething(object obj)
{
    string username = "joe";
    using(var db = new Db())
    {
        var user = await db.Users
            .SingleAsync(u => u.Username == username);

        if (DateTime.UtcNow - user.LastLogin >
            new TimeSpan(0, 30, 0)
        )
            loginUser(username);

        user.Something = obj;
        await db.SaveChangesAsync();
    }
}

ユーザーは両方のコンテキストで使用され、変更されて一方に保存され、次にもう一方に保存されるため、このコードは時々失敗します。「Something」を所有するユーザーのメモリ内表現は、データベース内のものと一致しないため、この潜んでいるバグが発生します。

これを防ぐ1つの方法は、オプションのDbContextをとるように、ライブラリメソッドとして呼び出される可能性のあるコードを記述することです。

protected async Task loginUser(string username, Db _db = null)
{
    await EFHelper.Using(_db, async db =>
    {
        var user = await db.Users...
        ... // Rest of loginUser code goes here
    });
}

public class EFHelper
{
    public static async Task Using<T>(T db, Func<T, Task> action)
        where T : DbContext, new()
    {
        if (db == null)
        {
            using (db = new T())
            {
                await action(db);
            }
        }
        else
        {
            await action(db);
        }
    }
}

したがって、メソッドはオプションのデータベースを取得し、データベースがない場合は、それ自体を作成します。存在する場合は、渡されたものを再利用します。ヘルパーメソッドを使用すると、このパターンをアプリ全体で簡単に再利用できます。


10

サーバーエクスプローラーからデザイナーにテーブルをドラッグし直して再構築することで、このエラーを解決しました。


問題のテーブルをサーバーエクスプローラーからデザイナーに再ドラッグして再構築すると、これも修正されました。
rstackhouse 2013

4

これは、C#コードでこのエラーをオーバーライドするために必要なものです。

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }

アプリケーションのフロントエンドからデータベースに送信されるアイテムをスケジュールしました。これらは、それぞれ異なるスレッドでサービスの実行をトリガーします。ユーザーは、未処理のコマンドのステータスをすべて変更する「キャンセル」ボタンを押すことができます。サービスはそれぞれを終了しますが、「保留」が「キャンセル」に変更され、「完了」に変更できないことがわかります。これで問題が解決しました。
pwrgreg007 2018年

2
また、KeepCurrentValuesなど、RefreshModeの他の列挙も確認してください。このロジックを使用した後は、SubmitChangesを再度呼び出す必要があることに注意してください。msdn.microsoft.com/en-us/library/…を参照してください
pwrgreg007

3

あなたの質問に満足のいく答えが見つかるかどうかはわかりませんが、同様の質問を投稿し、最終的に自分で回答しました。データベースに対してNOCOUNTデフォルト接続オプションがオンになっていることがわかりました。これにより、Linq to Sqlで行われたすべての更新に対してChangeConflictExceptionが発生しました。ここで私の投稿を参照できます


3

(UpdateCheck = UpdateCheck.Never)すべての[Column]定義に追加することでこれを修正しました。

しかし、適切な解決策のようには感じません。私の場合、このテーブルが、行が削除された別のテーブルに関連付けられているという事実に関連しているようです。

これはWindows Phone 7.5です。


1

私の場合、異なるLINQ-to-SQLデータコンテキストを持つ2人のユーザーが同じエンティティを同じ方法で更新したときにエラーが発生しました。2番目のユーザーが更新を試みたとき、最初の更新が完了した後に読み取られたにもかかわらず、データコンテキストにあったコピーは古くなっていました。

この記事でAkshay Phadkeが説明と解決策を見つけました:https ://www.c-sharpcorner.com/article/overview-of-concurrency-in-linq-to-sql/

私が主に持ち上げたコードは次のとおりです。

try
{
    this.DC.SubmitChanges();
}
catch (ChangeConflictException)
{
     this.DC.ChangeConflicts.ResolveAll(RefreshMode.OverwriteCurrentValues);

     foreach (ObjectChangeConflict objectChangeConflict in this.DC.ChangeConflicts)
     {
         foreach (MemberChangeConflict memberChangeConflict in objectChangeConflict.MemberConflicts)
         {
             Debug.WriteLine("Property Name = " + memberChangeConflict.Member.Name);
             Debug.WriteLine("Current Value = " + memberChangeConflict.CurrentValue.ToString());
             Debug.WriteLine("Original Value = " + memberChangeConflict.OriginalValue.ToString());
             Debug.WriteLine("Database Value = " + memberChangeConflict.DatabaseValue.ToString());
         }
     }
     this.DC.SubmitChanges();
     this.DC.Refresh(RefreshMode.OverwriteCurrentValues, att);
 }

デバッグ中に出力ウィンドウを見ると、現在の値がデータベースの値と一致していることがわかりました。「元の価値」は常に犯人でした。これは、更新を適用する前にデータコンテキストによって読み取られた値です。

インスピレーションを与えてくれたMarceloBarbosaに感謝します。


0

私はこの質問が答えられて久しいことを知っていますが、ここ数時間は壁に頭をぶつけて、このスレッドの項目のどれにも関係がないことが判明した私の解決策を共有したかっただけです:

キャッシング!

データオブジェクトのselect()部分はキャッシュを使用していました。オブジェクトの更新に関しては、行が見つからないか変更されたというエラーが発生していました。

いくつかの回答は、異なるDataContextの使用について言及していましたが、振り返ってみると、これはおそらく何が起こっていたのでしょうか。


0

最近このエラーが発生しましたが、問題はデータコンテキストではなく、コンテキストでCommitが呼び出された後にトリガー内で更新ステートメントが起動することでした。トリガーは、null可能ではないフィールドをnull値で更新しようとしていたため、コンテキストが上記のメッセージでエラーになっていました。

私は、このエラーを処理する他の人を助けるためだけにこの回答を追加し、上記の回答で解決策を見つけません。


0

2つの異なるコンテキストを使用しているため、このエラーも発生しました。この問題は、単一のデータコンテキストを使用して解決しました。


0

私の場合、問題はサーバー全体のユーザーオプションにありました。以下:

https://msdn.microsoft.com/en-us/library/ms190763.aspx

パフォーマンスを向上させるために、NOCOUNTオプションを有効にしました。

EXEC sys.sp_configure 'user options', 512;
RECONFIGURE;

これにより、影響を受ける行に対するLinqのチェックが破られ(.NETソースから把握できる限り)、ChangeConflictExceptionが発生します。

オプションをリセットして512ビットを除外すると、問題が解決しました。


0

qub1nの回答を採用した後、私にとっての問題は、データベースの列が誤ってdecimal(18,0)であると宣言したことであることがわかりました。10進数値を割り当てていましたが、データベースはそれを変更して、小数部分を削除していました。これにより、行変更の問題が発生しました。

他の誰かが同様の問題に遭遇した場合は、これを追加してください。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.