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"을 포착 할 때 이것이 실패한 이유에 대한 추가 정보가 없습니다. 또한이 예외가 데이터베이스에 정확히 하나의 레코드 (업데이트하려는 레코드)와 함께 발생 함을 확인했습니다.

이상한 점은 다른 코드 섹션에 매우 유사한 업데이트 문이 있고 다음 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 모두에서 적절한 기본 필드 값이 식별되었음을 확인했습니다.

나는 이것이 거의 두 부분으로 된 질문이라고 생각합니다.

  1. 예외가 발생하는 이유는 무엇입니까?
  2. 생성 된 SQL의 두 번째 세트를 검토 한 후 충돌을 감지하려면 모든 필드를 확인하는 것이 좋을 것 같지만 이것이 상당히 비효율적이라고 생각합니다. 이것이 항상 작동하는 방식입니까? 기본 키만 확인하는 설정이 있습니까?

나는 지난 두 시간 동안 이것으로 싸웠으므로 어떤 도움을 주시면 감사하겠습니다.


FWIW : 실수로 메서드를 두 번 호출 할 때이 오류가 발생했습니다. 두 번째 호출에서 발생합니다.
Kris

답변:


189

끔찍하지만 간단합니다.

O / R-Designer의 모든 필드에 대한 데이터 유형이 SQL 테이블의 데이터 유형과 일치하는지 확인하십시오. nullable을 다시 확인하십시오! 열은 O / R-Designer와 SQL 모두에서 nullable이거나 둘 다에서 nullable이 아니어야합니다.

예를 들어, NVARCHAR 열 "title"은 데이터베이스에서 NULL 가능으로 표시되고 NULL 값을 포함합니다. 열이 O / R-Mapping에서 NOT NULLable로 표시 되더라도 LINQ는 열을 성공적으로로드하고 column-String을 null로 설정합니다.

  • 이제 무언가를 변경하고 SubmitChanges ()를 호출합니다.
  • LINQ는 "WHERE [title] IS NULL"을 포함하는 SQL 쿼리를 생성하여 다른 사람이 제목을 변경하지 않았는지 확인합니다.
  • LINQ는 매핑에서 [title]의 속성을 조회합니다.
  • LINQ는 [title] NOT NULLable을 찾습니다.
  • [title]은 NULL이 가능하지 않으므로 논리 상 NULL이 될 수 없습니다!
  • 따라서 쿼리를 최적화하면 LINQ는 쿼리를 "never"에 해당하는 SQL 인 "where 0 = 1"로 바꿉니다.

필드의 데이터 유형이 SQL의 데이터 유형과 일치하지 않거나 필드가 누락 된 경우에도 동일한 증상이 나타납니다. LINQ는 데이터를 읽은 후 SQL 데이터가 변경되지 않았는지 확인할 수 없기 때문입니다.


4
비슷한 문제가 있었지만 약간 다르긴했지만 nullable을 다시 확인하라는 조언이 하루를 절약했습니다! 나는 이미 대머리 였지만,이 문제로 인해 머리카락이 하나 더 있으면 당연히 또 다른 머리가 났을 것입니다 .. 감사합니다!
Rune Jacobsen

7
속성 창에서 'Nullable'속성을 True로 설정했는지 확인합니다. 나는에서 변화의 '서버 데이터 형식'속성을 편집 한 VARCHAR(MAX) NOT NULLVARCHAR(MAX) NULL작업에 기대. 아주 간단한 실수.

이것을 찬성해야했다. 시간이 많이 절약되었습니다. 내가 생각했기 때문에 내 격리 수준을보고되었습니다 그것은 동시성 문제였다
아드리안

3
속성에 NUMERIC(12,8)매핑 된 열이 Decimal있습니다. Column 속성에서 DbType을 정확하게 지정해야했습니다 [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
Costo

3
문제 필드 / 열을 식별하는 한 가지 방법은 .dbml 파일에있는 현재 Linq-to-SQL 엔티티 클래스를 별도의 파일에 저장하는 것입니다. 그런 다음 현재 모델을 삭제하고 데이터베이스 (VS 사용)에서 다시 생성하면 새 .dbml 파일이 생성됩니다. 그런 다음 두 .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를 사용하여 데이터 컨텍스트를 업데이트 할 때이 오류가 발생했지만 필드를 업데이트하는 트리거 중 하나에 의해 발생하여 데이터 컨텍스트가 테이블의 데이터와 달라지는 것으로 확인되었습니다. 귀하의 코드는 데이터 컨텍스트가 테이블과 동기화되지 않는 원인이되는 필드를 정확히 파악하는 데 도움이되었습니다. 정말 감사합니다!!
Jagd

1
이것은 큰 테이블을위한 훌륭한 솔루션입니다. null을 처리하려면 'col.XValue.ToString ()'을 'col.XValue == null? "null": 세 값 필드 각각에 대한 col.XValue.ToString () '.
humbads

OriginalValue, CurrentValue 및 DatabaseValue를 문자열화할 때 null 참조에 대한 보호에 대해서도 마찬가지입니다.
Floyd Kosch

16

여기에 도움이 될 수있는 Refresh 라는 DataContext 메서드가 있습니다. 변경 사항을 제출하기 전에 데이터베이스 레코드를 다시로드 할 수 있으며 유지할 값을 결정하는 다양한 모드를 제공합니다. "KeepChanges"는 내 목적에 가장 현명 해 보이지만 그 동안 데이터베이스에서 발생한 충돌하지 않는 변경 사항과 변경 사항을 병합하기위한 것입니다.

내가 올바르게 이해한다면. :)


5
이 답변은 제 경우에 문제를 해결했습니다 : dc.Refresh(RefreshMode.KeepChanges,changedObject);before dc.SubmitChanges
HugoRune

Dynamic Data 웹 사이트의 속성에 ReadOnlyAttribute를 적용 할 때이 문제가 발생했습니다. 업데이트가 작동을 멈추고 "행을 찾을 수 없거나 변경되었습니다"라는 오류가 발생했습니다 (삽입은 괜찮습니다). 위의 수정으로 많은 노력과 시간이 절약되었습니다!
Chris Cannon

RefreshMode 값 (예 : KeepCurrentValues의 의미)을 설명해 주시겠습니까? 그것은 무엇을합니까? 감사합니다. 나는 ... 질문을 만들 수 있습니다
크리스 캐논에게

다른 트랜잭션이 동일한 행에서 시작될 때까지 동시 트랜잭션이 완료되지 않는 문제가 발생했습니다. KeepChanges은 (는 저장된 값을 유지하면서)과 새 (솔직히 아무 생각이) 시작 그래서 아마도 그것은 단지 현재의 트랜잭션 (transaction)를 중단, 여기에 절 도와 줬어요
에릭 Bergstedt

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"을 소유 한 사용자의 메모리 내 표현이 데이터베이스에있는 것과 일치하지 않으므로이 숨어있는 버그가 발생합니다.

이를 방지하는 한 가지 방법은 선택적 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

4

다음은 C # 코드에서이 오류를 재정의하는 데 필요한 것입니다.

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

응용 프로그램 프런트 엔드에서 데이터베이스에 제출 한 항목을 예약했습니다. 이러한 트리거는 서비스에서 각각 다른 스레드에서 실행됩니다. 사용자는 모든 미해결 명령의 상태를 변경하는 '취소'버튼을 누를 수 있습니다. 서비스는 각각을 완료하지만 '보류 중'이 '취소됨'으로 변경되어 '완료 됨'으로 변경할 수 없음을 발견합니다. 이것은 나를 위해 문제를 해결했습니다.
pwrgreg007

2
KeepCurrentValues와 같은 RefreshMode의 다른 열거도 확인하십시오. 이 논리를 사용한 후 SubmitChanges를 다시 호출해야합니다. msdn.microsoft.com/en-us/library/…를 참조 하십시오 .
pwrgreg007

3

질문에 대해 만족스러운 답변을 찾았는지 모르겠지만 비슷한 질문을 게시하고 결국 직접 답변했습니다. 데이터베이스에 대해 NOCOUNT 기본 연결 옵션이 설정되어 Linq에서 Sql로 업데이트 할 때마다 ChangeConflictException이 발생했습니다. 여기 에서 내 게시물을 참조 할 수 있습니다 .


3

(UpdateCheck = UpdateCheck.Never)모든 [Column]정의 를 추가하여이 문제를 해결했습니다 .

그래도 적절한 해결책이 아닌 것 같습니다. 제 경우에는이 테이블이 행이 삭제 된 다른 테이블과 연관되어 있다는 사실과 관련된 것 같습니다.

이것은 Windows Phone 7.5에 있습니다.


1

제 경우에는 LINQ-to-SQL 데이터 컨텍스트가 다른 두 사용자가 같은 방식으로 동일한 엔터티를 업데이트 할 때 오류가 발생했습니다. 두 번째 사용자가 업데이트를 시도했을 때 첫 번째 업데이트가 완료된 후 읽었음에도 불구하고 데이터 컨텍스트에있는 복사본이 오래되었습니다.

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

최근에이 오류가 발생하여 문제가 내 데이터 컨텍스트가 아니라 커밋이 컨텍스트에서 호출 된 후 트리거 내부에서 실행되는 업데이트 문이 있음을 발견했습니다. 트리거가 널 (null) 값으로 널 불가능한 필드를 업데이트하려고 시도했으며 이로 인해 위에서 언급 한 메시지와 함께 컨텍스트 오류가 발생했습니다.

나는이 오류를 처리하고 위의 답변에서 해결책을 찾지 못하는 다른 사람들을 돕기 위해이 답변을 추가하고 있습니다.


0

두 가지 다른 컨텍스트를 사용하기 때문에이 오류가 발생했습니다. 단일 데이터 컨텍스트를 사용하여이 문제를 해결했습니다.


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)으로 선언 한 것입니다. 십진수 값을 할당했지만 데이터베이스가 소수점 부분을 제거하여 변경했습니다. 이로 인해 행 변경 문제가 발생했습니다.

다른 사람이 비슷한 문제가 발생하면 이것을 추가하십시오.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.