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 모두에서 적절한 기본 필드 값이 식별되었음을 확인했습니다.
나는 이것이 거의 두 부분으로 된 질문이라고 생각합니다.
- 예외가 발생하는 이유는 무엇입니까?
- 생성 된 SQL의 두 번째 세트를 검토 한 후 충돌을 감지하려면 모든 필드를 확인하는 것이 좋을 것 같지만 이것이 상당히 비효율적이라고 생각합니다. 이것이 항상 작동하는 방식입니까? 기본 키만 확인하는 설정이 있습니까?
나는 지난 두 시간 동안 이것으로 싸웠으므로 어떤 도움을 주시면 감사하겠습니다.
답변
끔찍하지만 간단합니다.
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 데이터가 변경되지 않았는지 확인할 수 없기 때문입니다.
답변
첫째, 문제의 원인을 아는 것이 유용합니다. 인터넷 검색 솔루션이 도움이 될 것입니다. 나중에 충돌을 해결하기위한 더 나은 솔루션을 찾기 위해 충돌에 대한 세부 정보 (테이블, 열, 이전 값, 새 값)를 기록 할 수 있습니다.
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
}
답변
여기에 도움이 될 수있는 Refresh 라는 DataContext 메서드가 있습니다. 변경 사항을 제출하기 전에 데이터베이스 레코드를 다시로드 할 수 있으며 유지할 값을 결정하는 다양한 모드를 제공합니다. “KeepChanges”는 내 목적에 가장 현명 해 보이지만 그 동안 데이터베이스에서 발생한 충돌하지 않는 변경 사항과 변경 사항을 병합하기위한 것입니다.
내가 올바르게 이해한다면. 🙂
답변
이는 둘 이상의 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);
}
}
}
그래서 이제 당신의 방법은 선택적인 데이터베이스를 취하고, 하나가 없다면 스스로 하나를 만듭니다. 전달 된 것을 재사용합니다. 도우미 메서드를 사용하면 앱 전체에서이 패턴을 쉽게 재사용 할 수 있습니다.
답변
서버 탐색기에서 디자이너로 테이블을 다시 래깅하고 다시 빌드하여이 오류를 해결했습니다.
답변
다음은 C # 코드에서이 오류를 재정의하는 데 필요한 것입니다.
try
{
_db.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e)
{
foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
{
occ.Resolve(RefreshMode.KeepChanges);
}
}
답변
질문에 대해 만족스러운 답변을 찾았는지 모르겠지만 비슷한 질문을 게시하고 결국 직접 답변했습니다. 데이터베이스에 대해 NOCOUNT 기본 연결 옵션이 설정되어 Linq에서 Sql로 업데이트 할 때마다 ChangeConflictException이 발생했습니다. 여기 에서 내 게시물을 참조 할 수 있습니다 .