Tôi có thể làm gì để giải quyết Ngoại lệ “Hàng không được tìm thấy hoặc đã thay đổi” trong LINQ to SQL trên Cơ sở dữ liệu SQL Server Compact Edition?


96

Khi thực thi SubmitChanges cho DataContext sau khi cập nhật một số thuộc tính có kết nối LINQ to SQL (đối với SQL Server Compact Edition), tôi nhận được thông báo "Không tìm thấy hoặc đã thay đổi hàng". 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();

Truy vấn tạo ra SQL sau:

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

Vấn đề rõ ràng là WHERE 0 = 1 , Sau khi bản ghi được tải, tôi đã xác nhận rằng tất cả các thuộc tính trong "deviceSessionRecord" là chính xác để bao gồm khóa chính. Ngoài ra khi bắt "ChangeConflictException", không có thông tin bổ sung về lý do tại sao điều này không thành công. Tôi cũng đã xác nhận rằng ngoại lệ này được ném với chính xác một bản ghi trong cơ sở dữ liệu (bản ghi mà tôi đang cố gắng cập nhật)

Điều kỳ lạ là tôi có một câu lệnh cập nhật rất giống nhau trong một phần mã khác và nó tạo ra SQL sau và thực sự cập nhật cơ sở dữ liệu SQL Server Compact Edition của tôi.

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

Tôi đã xác nhận rằng các giá trị trường chính thích hợp đã được xác định trong cả Lược đồ cơ sở dữ liệu và DBML tạo ra các lớp LINQ.

Tôi đoán đây gần như là một câu hỏi gồm hai phần:

  1. Tại sao ngoại lệ được ném?
  2. Sau khi xem xét tập hợp thứ hai của SQL được tạo, có vẻ như để phát hiện xung đột, sẽ rất tốt nếu kiểm tra tất cả các trường, nhưng tôi tưởng tượng điều này sẽ khá kém hiệu quả. Đây có phải là cách này luôn luôn hoạt động? Có cài đặt nào để chỉ kiểm tra khóa chính không?

Tôi đã đấu tranh với điều này trong hai giờ qua nên mọi sự giúp đỡ sẽ được đánh giá cao.


FWIW: Tôi đã gặp lỗi này khi vô tình gọi phương thức hai lần. Nó sẽ xảy ra trong cuộc gọi thứ hai.
Kris

Thông tin cơ bản tuyệt vời được tìm thấy tại c-sharpcorner.com/article/…
CAK2

Câu trả lời:


189

Đó là điều khó chịu, nhưng đơn giản:

Kiểm tra xem kiểu dữ liệu cho tất cả các trường trong O / R-Designer có khớp với kiểu dữ liệu trong bảng SQL của bạn không. Kiểm tra kỹ xem có thể trống không! Một cột phải có giá trị rỗng trong cả O / R-Designer và SQL, hoặc không thể vô hiệu hóa trong cả hai.

Ví dụ, một cột NVARCHAR "tiêu đề" được đánh dấu là NULLable trong cơ sở dữ liệu của bạn và chứa giá trị NULL. Mặc dù cột được đánh dấu là KHÔNG THỂ ĐẦY ĐỦ trong Ánh xạ O / R của bạn, LINQ sẽ tải nó thành công và đặt Cột-Chuỗi thành rỗng.

  • Bây giờ bạn thay đổi thứ gì đó và gọi SubmitChanges ().
  • LINQ sẽ tạo một truy vấn SQL có chứa "WHERE [title] IS NULL", để đảm bảo rằng tiêu đề không bị thay đổi bởi người khác.
  • LINQ tra cứu các thuộc tính của [title] trong ánh xạ.
  • LINQ sẽ tìm thấy [title] NOT NULLable.
  • Vì [title] KHÔNG NULL, nên theo logic, nó không bao giờ có thể là NULL!
  • Vì vậy, tối ưu hóa truy vấn, LINQ thay thế nó bằng "where 0 = 1", SQL tương đương với "never".

Triệu chứng tương tự sẽ xuất hiện khi kiểu dữ liệu của một trường không khớp với kiểu dữ liệu trong SQL hoặc nếu trường bị thiếu, vì LINQ sẽ không thể đảm bảo dữ liệu SQL không thay đổi kể từ khi đọc dữ liệu.


4
Tôi đã gặp một vấn đề tương tự - mặc dù hơi khác một chút - và lời khuyên của bạn để kiểm tra kỹ xem có nullable đã cứu rỗi ngày của tôi! Tôi đã bị hói rồi, nhưng vấn đề này chắc chắn sẽ khiến tôi phải trả giá bằng một mái tóc khác nếu tôi có .. cảm ơn!
Rune Jacobsen

7
Đảm bảo rằng bạn đặt thuộc tính 'Nullable' trong cửa sổ thuộc tính thành True. Tôi đang chỉnh sửa thuộc tính 'Kiểu dữ liệu máy chủ', thay đổi nó từ VARCHAR(MAX) NOT NULLthành VARCHAR(MAX) NULLvà mong nó hoạt động. Sai lầm rất đơn giản.

Đã phải ủng hộ điều này. Nó đã tiết kiệm cho tôi rất nhiều thời gian. Đang xem xét mức độ cô lập của tôi vì tôi đã nghĩ rằng đó là một vấn đề đồng thời
Adrian

3
Tôi đã có một NUMERIC(12,8)cột được ánh xạ tới một thuộc Decimaltính. Tôi đã phải chính xác hóa DbType trong thuộc tính Cột [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
Costo

3
Một cách để xác định các trường / cột có vấn đề là lưu các lớp thực thể Linq-to-SQL hiện tại của bạn, nằm trong tệp .dbml, vào một tệp riêng biệt. Sau đó, xóa mô hình hiện tại của bạn và tạo lại nó từ cơ sở dữ liệu (sử dụng VS), sẽ tạo ra một tệp .dbml mới. Sau đó, chỉ cần chạy trình so sánh như WinMerge hoặc WinDiff trên hai tệp .dbml để xác định sự khác biệt của vấn đề.
david.barkhuizen

24

Đầu tiên, cần biết điều gì đang gây ra sự cố. Giải pháp của Google sẽ hữu ích, bạn có thể ghi lại chi tiết (bảng, cột, giá trị cũ, giá trị mới) về xung đột để tìm giải pháp tốt hơn cho việc giải quyết xung đột sau này:

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

Tạo người trợ giúp để gói sumbit của bạn

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

Và sau đó cuộc gọi gửi mã thay đổi:

Datamodel.SubmitChangesWithDetailException();

Cuối cùng, ghi lại ngoại lệ trong trình xử lý ngoại lệ chung của bạn:

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

3
Giải pháp tuyệt vời! Tôi có một bảng có khoảng 80 trường và có rất nhiều trình kích hoạt trên bảng đang cập nhật các trường khác nhau trong quá trình chèn và cập nhật. Tôi đã gặp lỗi này khi cập nhật văn bản dữ liệu bằng L2S, nhưng khá chắc chắn rằng lỗi này do một trong các trình kích hoạt cập nhật trường gây ra, do đó khiến ngữ cảnh dữ liệu khác với dữ liệu trong bảng. Mã của bạn đã giúp tôi biết chính xác trường nào khiến ngữ cảnh dữ liệu không đồng bộ với bảng. Cảm ơn rất nhiều !!
Jagd

1
Đây là một giải pháp tuyệt vời cho các bảng lớn. Để xử lý null, hãy thay đổi 'col.XValue.ToString ()' thành 'col.XValue == null? "null": col.XValue.ToString () 'cho mỗi trường trong số ba trường giá trị.
humbads

Ditto để bảo vệ chống lại các tham chiếu rỗng khi chuỗi OriginalValue, CurrentValue và DatabaseValue.
Floyd Kosch

16

Có một phương pháp trên DataContext được gọi là Làm mới có thể hữu ích ở đây. Nó cho phép bạn tải lại bản ghi cơ sở dữ liệu trước khi gửi các thay đổi và cung cấp các chế độ khác nhau để xác định giá trị nào cần giữ lại. "KeepChanges" có vẻ thông minh nhất cho mục đích của tôi, nó nhằm mục đích hợp nhất các thay đổi của tôi với bất kỳ thay đổi không xung đột nào đã xảy ra trong cơ sở dữ liệu trong thời gian chờ đợi.

Nếu tôi hiểu nó một cách chính xác. :)


5
Câu trả lời này cố định các vấn đề trong trường hợp của tôi: dc.Refresh(RefreshMode.KeepChanges,changedObject);trước dc.SubmitChanges
HugoRune

Tôi gặp sự cố này khi áp dụng ReadOnlyAttribute cho các thuộc tính trong trang web Dữ liệu động. Các bản cập nhật ngừng hoạt động và tôi gặp lỗi "Không tìm thấy hoặc thay đổi hàng" (mặc dù vậy, các phần chèn vẫn ổn). Việc sửa chữa trên đã tiết kiệm rất nhiều công sức và thời gian!
Chris Cannon

Bạn có thể vui lòng giải thích các giá trị RefreshMode, ví dụ KeepCurrentValues ​​nghĩa là gì? nó làm gì? Cảm ơn nhiều. Tôi có thể tạo một câu hỏi ...
Chris Cannon

Tôi đã gặp sự cố với các giao dịch đồng thời không hoàn thành kịp thời để một giao dịch khác bắt đầu trên cùng các hàng. KeepChanges giúp tôi đây, vì vậy có lẽ nó chỉ hủy bỏ các giao dịch vãng lai (trong khi vẫn giữ các giá trị nó lưu) và bắt đầu cái mới (một cách trung thực tôi không có ý tưởng)
Erik Bergstedt

11

Điều này cũng có thể do sử dụng nhiều DbContext.

Ví dụ:

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

Đoạn mã này thỉnh thoảng sẽ bị lỗi, theo những cách có vẻ không thể đoán trước được, bởi vì người dùng được sử dụng trong cả hai ngữ cảnh, được thay đổi và lưu trong một ngữ cảnh, sau đó lưu vào một ngữ cảnh khác. Biểu diễn trong bộ nhớ của người dùng sở hữu "Cái gì đó" không khớp với nội dung trong cơ sở dữ liệu, và do đó bạn gặp phải lỗi tiềm ẩn này.

Một cách để ngăn chặn điều này là viết bất kỳ mã nào có thể được gọi là phương thức thư viện theo cách sao cho nó sử dụng một DbContext tùy chọn:

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

Vì vậy, bây giờ phương thức của bạn có một cơ sở dữ liệu tùy chọn, và nếu không có, hãy tự tạo một cơ sở dữ liệu. Nếu có, nó chỉ sử dụng lại những gì đã được chuyển vào. Phương thức helper giúp bạn dễ dàng sử dụng lại mẫu này trên ứng dụng của mình.


10

Tôi đã giải quyết lỗi này bằng cách vẽ lại bảng từ trình khám phá máy chủ đến trình thiết kế và xây dựng lại.


Vẽ lại bảng vi phạm từ Server Explorer đến trình thiết kế và xây dựng lại cũng đã khắc phục sự cố này cho tôi.
rstackhouse

4

Đây là những gì bạn cần để ghi đè lỗi này trên mã C #:

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

Tôi đã lên lịch các mục do front-end ứng dụng gửi đến cơ sở dữ liệu. Những kích hoạt thực thi trong một dịch vụ, mỗi trên các luồng khác nhau. Người dùng có thể nhấn nút 'hủy' để thay đổi tất cả trạng thái của lệnh chưa thực hiện. Dịch vụ kết thúc từng việc nhưng nhận thấy rằng 'Đang chờ xử lý' đã được thay đổi thành 'Đã hủy' và không thể thay đổi thành 'Đã hoàn thành'. Điều này đã khắc phục sự cố cho tôi.
pwrgreg007

2
Đồng thời kiểm tra các bảng liệt kê khác của RefreshMode, như KeepCurrentValues. Lưu ý rằng bạn phải gọi lại SubmitChanges sau khi sử dụng logic này. Xem msdn.microsoft.com/en-us/library/… .
pwrgreg007

3

Tôi không biết liệu bạn có tìm thấy câu trả lời thỏa đáng nào cho câu hỏi của mình hay không, nhưng tôi đã đăng một câu hỏi tương tự và cuối cùng tự mình trả lời. Hóa ra là tùy chọn kết nối mặc định NOCOUNT đã được bật cho cơ sở dữ liệu, điều này gây ra ChangeConflictException cho mọi bản cập nhật được thực hiện với Linq thành Sql. Bạn có thể tham khảo bài viết của tôi tại đây .


3

Tôi đã sửa điều này bằng cách thêm (UpdateCheck = UpdateCheck.Never)vào tất cả các [Column]định nghĩa.

Tuy nhiên, cảm thấy không phải là một giải pháp thích hợp. Trong trường hợp của tôi, nó dường như liên quan đến thực tế là bảng này có một liên kết đến một bảng khác từ đó một hàng bị xóa.

Đây là trên Windows Phone 7.5.


1

Trong trường hợp của tôi, lỗi đã phát sinh khi hai người dùng có ngữ cảnh dữ liệu LINQ-to-SQL khác nhau cập nhật cùng một thực thể theo cùng một cách. Khi người dùng thứ hai cố gắng cập nhật, bản sao họ có trong ngữ cảnh dữ liệu của họ đã cũ mặc dù nó đã được đọc sau khi bản cập nhật đầu tiên hoàn thành.

Tôi đã khám phá ra lời giải thích và giải pháp trong bài viết này của Akshay Phadke: https://www.c-sharpcorner.com/article/overview-of-concurrency-in-linq-to-sql/

Đây là mã mà tôi hầu hết đã gỡ bỏ:

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

Khi tôi nhìn vào cửa sổ đầu ra của mình trong khi gỡ lỗi, tôi có thể thấy rằng Giá trị hiện tại khớp với Giá trị cơ sở dữ liệu. "Giá trị ban đầu" luôn là thủ phạm. Đó là giá trị được ngữ cảnh dữ liệu đọc trước khi áp dụng bản cập nhật.

Cảm ơn MarceloBarbosa đã truyền cảm hứng.


0

Tôi biết câu hỏi này đã được trả lời từ lâu nhưng ở đây tôi đã dành vài giờ qua để đập đầu vào tường và tôi chỉ muốn chia sẻ giải pháp của mình hóa ra không liên quan đến bất kỳ mục nào trong chủ đề này:

Bộ nhớ đệm!

Phần select () của đối tượng dữ liệu của tôi đang sử dụng bộ nhớ đệm. Khi cập nhật đối tượng, lỗi Hàng Không Tìm thấy Hoặc Đã thay đổi đã xuất hiện.

Một số câu trả lời đã đề cập đến việc sử dụng các DataContext khác nhau và khi nhìn lại thì đây có thể là điều đang xảy ra nhưng nó không khiến tôi ngay lập tức nghĩ đến việc lưu vào bộ nhớ đệm vì vậy hy vọng điều này sẽ giúp ích cho ai đó!


0

Gần đây tôi đã gặp phải lỗi này và nhận thấy sự cố không phải với Ngữ cảnh dữ liệu của tôi mà là với một câu lệnh cập nhật kích hoạt bên trong trình kích hoạt sau khi Cam kết được gọi trên Ngữ cảnh. Trình kích hoạt đang cố gắng cập nhật trường không thể null bằng giá trị null và nó đang gây ra lỗi ngữ cảnh với thông báo được đề cập ở trên.

Tôi thêm câu trả lời này chỉ để giúp những người khác giải quyết lỗi này và không tìm thấy giải pháp trong các câu trả lời ở trên.


0

Tôi cũng đã gặp lỗi này vì sử dụng hai ngữ cảnh khác nhau. Tôi đã giải quyết vấn đề này bằng cách sử dụng ngữ cảnh dữ liệu đơn lẻ.


0

Trong trường hợp của tôi, vấn đề là với các tùy chọn người dùng trên toàn máy chủ. Tiếp theo:

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

Tôi đã bật tùy chọn NOCOUNT với hy vọng nhận được một số lợi ích về hiệu suất:

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

và điều này hóa ra phá vỡ kiểm tra của Linq đối với các Hàng bị ảnh hưởng (nhiều nhất tôi có thể tìm ra từ các nguồn .NET), dẫn đến ChangeConflictException

Đặt lại các tùy chọn để loại trừ 512 bit đã khắc phục sự cố.


0

Sau khi sử dụng câu trả lời của qub1n, tôi thấy rằng vấn đề đối với tôi là tôi đã vô tình khai báo cột cơ sở dữ liệu là số thập phân (18,0). Tôi đã chỉ định một giá trị thập phân, nhưng cơ sở dữ liệu đang thay đổi nó, loại bỏ phần thập phân. Điều này dẫn đến vấn đề thay đổi hàng.

Chỉ thêm điều này nếu bất kỳ ai khác gặp phải vấn đề tương tự.


0

chỉ cần đi với Linq2DB, tốt hơn rất nhiều

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.