在SQL Server Compact Edition数据库上,如何解决LINQ to SQL中的“找不到或更改行”异常?


96

在使用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

明显的问题是WHERE 0 = 1,在加载记录之后,我已经确认“ deviceSessionRecord”中的所有属性都正确,可以包含主键。同样,在捕获“ ChangeConflictException”时,没有有关此失败原因的其他信息。我还确认了该异常与数据库中的一条记录(我正在尝试更新的记录)一起引发

奇怪的是,我在不同的代码部分中有一个非常相似的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中都已经标识了正确的主字段值。

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

  1. 为什么会引发异常?
  2. 在检查了第二组生成的SQL之后,似乎对于检测冲突而言,检查所有字段似乎很好,但是我认为这样做效率不高。这是始终有效的方式吗?是否有仅检查主键的设置?

在过去的两个小时中,我一直在与这个问题作斗争,因此我们将不胜感激。


FWIW:无意中两次调用该方法时,出现此错误。它会在第二次调用时发生。
克里斯(Kris)

优秀的背景信息可以在c-sharpcorner.com/article/…
CAK2

Answers:


189

那很讨厌,但是很简单:

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

例如,NVARCHAR列“ title”在您的数据库中被标记为NULLable,并且包含值NULL。即使在O / R映射中将该列标记为不可为空,LINQ也会成功加载它并将column-String设置为null。

  • 现在,您更改某些内容并调用SubmitChanges()。
  • LINQ将生成一个包含“ WHERE [title] IS NULL”的SQL查询,以确保标题未被其他人更改。
  • LINQ在映射中查找[title]的属性。
  • LINQ将发现[title]不可为空。
  • 由于[title]不可为空,因此从逻辑上讲它永远不可能为NULL!
  • 因此,为优化查询,LINQ将其替换为“ where 0 = 1”,SQL等效于“ never”。

当字段的数据类型与SQL中的数据类型不匹配时,或者如果缺少字段,则会出现相同的症状,因为LINQ将无法确保自读取数据以来SQL数据没有更改。


4
我遇到了一个类似的问题-尽管略有不同-问题,您的建议再次检查可为空的问题节省了我的时间!我已经秃顶了,但是如果我留个头的话,这个问题肯定会让我头疼。
符文雅各布森

7
确保在属性窗口中将“ Nullable”属性设置为True。我正在编辑“服务器数据类型”属性,将其从更改VARCHAR(MAX) NOT NULLVARCHAR(MAX) NULL并期望它能正常工作。很简单的错误。

不得不对此表示赞同。它节省了我很多时间。正在查看隔离级别,因为我曾认为这是并发问题
Adrian

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()'。
汉巴德,

在对OriginalValue,CurrentValue和DatabaseValue进行字符串化处理时,防止空引用也是同理。
Floyd Kosch

16

在DataContext上有一个称为Refresh的方法,在这里可能会有所帮助。它使您可以在提交更改之前重新加载数据库记录,并提供不同的模式来确定要保留的值。就我而言,“ KeepChanges”似乎是最聪明的,它旨在将我的更改与同时发生在数据库中的任何无冲突的更改合并。

如果我理解正确。:)


5
这个答案解决了我的问题:dc.Refresh(RefreshMode.KeepChanges,changedObject);dc.SubmitChanges之前
HugoRune 2012年

将ReadOnlyAttribute应用于动态数据网站中的属性时遇到此问题。更新停止工作,并且出现错误“找不到或更改行”(尽管插入没问题)。上面的修复节省了工作量和时间!
克里斯·坎农

您能否解释一下RefreshMode值,例如KeepCurrentValues是什么意思?它有什么作用?非常感谢。我可以提出一个问题...
克里斯·坎农

我遇到了并发事务无法及时完成以使另一个事务在同一行开始的问题。KeepChanges在这里帮助了我,所以也许它只是中止当前事务(同时保留它保存的值)并开始新事务(老实说我不知道​​)
Erik 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();
    }
}

这段代码有时会以无法预料的方式失败,因为在两种情况下都使用用户,将用户更改并保存在一个上下文中,然后在另一个上下文中保存。拥有“某些东西”的用户的内存中表示形式与数据库中的内容不匹配,因此您会遇到此潜在的错误。

防止这种情况的一种方法是编写任何可能被称为库方法的代码,使其采用可选的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);
        }
    }
}

因此,现在您的方法采用一个可选数据库,如果没有,则自行创建一个数据库。如果存在,则仅重用传入的内容。helper方法使在整个应用程序中重用此模式变得容易。


10

我通过将表从服务器资源管理器重新拖到设计器并重新构建来解决了此错误。


将有问题的表从服务器资源管理器重新拖动到设计器,然后重新构建,这对我来说也是如此。
rstackhouse

4

这是您需要在C#代码上覆盖此错误的内容:

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

我已经安排了应用程序前端向数据库提交的项目。它们触发服务中每个线程不同的执行。用户可以单击“取消”按钮,以更改所有未完成命令的状态。该服务完成了每个任务,但是发现“待处理”已更改为“已取消”,并且无法将其更改为“已完成”。这为我解决了问题。
pwrgreg007'5

2
还要检查RefreshMode的其他枚举,例如KeepCurrentValues。请注意,使用此逻辑后,您必须再次调用SubmitChanges。请参阅msdn.microsoft.com/en-us/library/…
pwrgreg007 '18

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

我最近遇到此错误,发现问题不在于我的数据上下文,而是在上下文上调用Commit之后在触发器内触发了更新语句。触发器正在尝试使用空值更新不可为空的字段,并且这导致上下文因上述消息而出错。

我添加此答案仅是为了帮助其他人处理此错误,而未在上面的答案中找到解决方案。



0

就我而言,问题在于服务器范围的用户选项。以下:

https://msdn.microsoft.com/zh-CN/library/ms190763.aspx

我启用了NOCOUNT选项,希望获得一些性能上的好处:

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

事实证明,这打破了Linq对“受影响的行”的检查(据我从.NET来源中可以找出的一样多),从而导致ChangeConflictException

重置选项以排除512位可解决此问题。


0

使用qub1n的答案后,我发现对我来说问题是我无意中将数据库列声明为十进制(18,0)。我正在分配一个十进制值,但是数据库正在更改它,剥离了十进制部分。这导致行更改问题。

如果其他人遇到类似问题,只需添加它即可。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.