100

LINQ to SQL接続でいくつかのプロパティを更新した後に(SQL Server Compact Editionに対して)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

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

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

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時間これと戦ってきたので、助けていただければ幸いです。

4

15 に答える 15

195

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

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

たとえば、NVARCHAR 列「タイトル」はデータベースで NULL 可能としてマークされており、値 NULL が含まれています。O/R マッピングで列が NOT NULLable としてマークされていても、LINQ はそれを正常に読み込み、列文字列を null に設定します。

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

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

于 2008-09-17T14:44:57.607 に答える
26

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

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);
        }           
    }
}

そして、submit changes コードを呼び出します。

Datamodel.SubmitChangesWithDetailException();

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

protected void Application_Error(object sender, EventArgs e)
{         
    Exception ex = Server.GetLastError();
    //TODO
}
于 2015-08-28T21:24:58.630 に答える
17

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

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

于 2010-09-03T04:05:31.337 に答える
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();
    }
}

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

これを防ぐ 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);
        }
    }
}

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

于 2011-05-23T19:31:38.993 に答える
10

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

于 2009-02-04T19:47:01.713 に答える
3

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

于 2009-03-19T17:22:23.177 に答える
3

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

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

これは Windows Phone 7.5 上にあります。

于 2012-07-03T10:27:21.013 に答える
0

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

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

于 2016-10-03T18:17:35.457 に答える
0

最近このエラーが発生しましたが、問題はデータ コンテキストにあるのではなく、コンテキストで Commit が呼び出された後にトリガー内で実行される update ステートメントにあることがわかりました。トリガーは、null 非許容フィールドを null 値で更新しようとしており、上記のメッセージでコンテキストにエラーが発生していました。

この回答を追加するのは、他の人がこのエラーに対処し、上記の回答で解決策を見つけられないのを助けるためだけです。

于 2011-11-25T17:20:28.887 に答える
0

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

于 2015-06-25T09:50:19.170 に答える
0

この質問にはずっと前に回答があったことは知っていますが、ここ数時間頭を壁にぶつけて過ごしてきました。このスレッドのどの項目にも関係しないことが判明した解決策を共有したかっただけです。

キャッシング!

私のデータ オブジェクトの select() 部分はキャッシュを使用していました。オブジェクトの更新に関しては、Row Not Found Or Changed エラーが発生していました。

回答のいくつかは、異なる DataContext の使用について言及していましたが、振り返ってみると、これはおそらく起こっていたことですが、キャッシングについてすぐには考えられなかったので、これが誰かの助けになることを願っています!

于 2011-09-06T15:26:21.000 に答える
0

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

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

パフォーマンス上の利点を得るために、 NOCOUNT オプションを有効にしました。

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

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

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

于 2015-10-26T11:46:18.923 に答える