如何在SQL Server Compact Edition数据库中解决LINQ to SQL中的"未找到或未更改行"异常?

Kev*_*vin 91 .net linq linq-to-sql

在使用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();
Run Code Online (Sandbox Code Playgroud)

该查询生成以下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
Run Code Online (Sandbox Code Playgroud)

显而易见的问题是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
Run Code Online (Sandbox Code Playgroud)

我已经确认已在Database Schema和生成LINQ类的DBML中识别出正确的主字段值.

我想这几乎是一个两部分的问题:

  1. 为什么抛出异常?
  2. 在查看第二组生成的SQL之后,看起来检测冲突似乎很好,检查所有字段,但我想这将是相当低效的.这是否总是有效?是否有设置只检查主键?

在过去的两个小时里,我一直在与之抗争,所以任何帮助都会受到赞赏.

Sam*_*Sam 182

多数令人讨厌,但很简单:

检查O/R-Designer中所有字段的数据类型是否与SQL表中的数据类型匹配. 双重检查可空!列应该在O/R-Designer和SQL中都可以为空,或者在两者中都不可为空.

例如,NVARCHAR列"title"在数据库中标记为NULLable,并包含值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!
  • 因此,在优化查询时,LINQ将其替换为"where 0 = 1",SQL等价于"never".

当字段的数据类型与SQL中的数据类型不匹配或缺少字段时,将出现相同的症状,因为LINQ将无法确保自读取数据后SQL数据未发生更改.

  • 确保将属性窗口中的"Nullable"属性设置为True.我正在编辑"服务器数据类型"属性,将其从"VARCHAR(MAX)NOT NULL"更改为"VARCHAR(MAX)NULL"并期望它能够正常工作.非常简单的错误. (7认同)
  • 我有一个类似的 - 虽然略有不同 - 的问题,你的建议,双重检查可空可以节省我的一天!我已经秃了,但是这个问题肯定会让我失去另一头头发,如果我有一个......谢谢! (4认同)
  • 我有一个`NUMERIC(12,8)`列映射到`Decimal`属性.我必须在Column属性`[Column(DbType ="numeric(12,8)")]公共小数中精确定义DbType?MyProperty ......` (3认同)
  • 识别问题字段/列的一种方法是将位于.dbml文件中的当前Linq-to-SQL实体类保存到单独的文件中.然后,删除当前模型并从数据库(使用VS)重新生成它,这将生成一个新的.dbml文件.然后,只需在两个.dbml文件上运行WinMerge或WinDiff之类的比较器即可找到问题的差异. (3认同)

Tom*_*bes 20

首先,了解导致问题的原因很有用.谷歌搜索解决方案应该有所帮助,您可以记录有关冲突的详细信息(表,列,旧值,新值),以便找到解决冲突的更好解决方案:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

创建包装sumbitChanges的帮助器:

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

然后调用提交更改代码:

Datamodel.SubmitChangesWithDetailException();
Run Code Online (Sandbox Code Playgroud)

最后,在全局异常处理程序中记录异常:

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

  • 一流的解决方案!我有一个包含大约80个字段的表,并且表中有许多触发器在插入和更新期间更新各个字段.我在使用L2S更新datacontext时遇到此错误,但非常确定它是由更新字段的其中一个触发器引起的,从而导致数据上下文与表中的数据不同.您的代码帮助我确切地查看哪个字段导致数据上下文与表不同步.万分感谢!! (2认同)

Mat*_*man 15

DataContext上有一个名为Refresh的方法,可以在这里提供帮助.它允许您在提交更改之前重新加载数据库记录,并提供不同的模式来确定要保留的值.对于我的目的,"KeepChanges"似乎是最聪明的,它旨在将我的更改与同时在数据库中发生的任何非冲突的更改合并.

如果我理解正确的话.:)

  • 这个答案解决了我的问题:`dc.Refresh(RefreshMode.KeepChanges,changedObject);`在dc.SubmitChanges之前 (4认同)

小智 10

我通过将表从服务器资源管理器重新划分到设计器并重新构建来解决了这个错误.


Chr*_*ini 10

这也可能是由于使用多个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();
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码会不时地以一种看似不可预测的方式失败,因为用户在两个上下文中使用,更改并保存在一个中,然后保存在另一个中.拥有"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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以现在你的方法需要一个可选的数据库,如果没有,那就自己去做一个.如果有它只是重用传入的内容.帮助方法可以很容易地在您的应用程序中重用此模式.