Apa yang dapat saya lakukan untuk menyelesaikan pengecualian "Baris tidak ditemukan atau diubah" di LINQ ke SQL pada Database SQL Server Compact Edition?

96

Saat menjalankan SubmitChanges ke DataContext setelah memperbarui beberapa properti dengan sambungan LINQ ke SQL (terhadap SQL Server Compact Edition) saya mendapatkan "Baris tidak ditemukan atau diubah." 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();

Kueri menghasilkan SQL berikut ini:

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

Masalah yang jelas adalah WHERE 0 = 1 , Setelah record dimuat, saya telah mengonfirmasi bahwa semua properti di "deviceSessionRecord" sudah benar untuk menyertakan kunci utama. Juga saat menangkap "ChangeConflictException" tidak ada informasi tambahan tentang mengapa ini gagal. Saya juga mengonfirmasi bahwa pengecualian ini dilemparkan dengan tepat satu catatan dalam database (catatan yang saya coba perbarui)

Yang aneh adalah bahwa saya memiliki pernyataan pembaruan yang sangat mirip di bagian kode yang berbeda dan menghasilkan SQL berikut ini dan memang memperbarui database SQL Server Compact Edition saya.

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

Saya telah mengkonfirmasi bahwa nilai bidang utama yang tepat telah diidentifikasi di Skema Database dan DBML yang menghasilkan kelas LINQ.

Saya kira ini hampir merupakan pertanyaan dua bagian:

  1. Mengapa pengecualian itu dilempar?
  2. Setelah meninjau set kedua dari SQL yang dihasilkan, sepertinya untuk mendeteksi konflik akan lebih baik untuk memeriksa semua bidang, tetapi saya membayangkan ini akan menjadi cukup tidak efisien. Apakah ini cara yang selalu berhasil? Apakah ada pengaturan untuk memeriksa kunci utama?

Saya telah berjuang dengan ini selama dua jam terakhir sehingga bantuan apa pun akan sangat kami hargai.

Kevin
sumber
FWIW: Saya mendapatkan kesalahan ini ketika secara tidak sengaja memanggil metode ini dua kali. Itu akan terjadi pada panggilan kedua.
Kris
Info latar belakang yang sangat bagus dapat ditemukan di c-sharpcorner.com/article/…
CAK2

Jawaban:

189

Itu buruk, tapi sederhana:

Periksa apakah tipe data untuk semua bidang di O / R-Designer cocok dengan tipe data di tabel SQL Anda. Periksa ulang untuk nullable! Kolom harus berupa nullable baik di O / R-Designer dan SQL, atau tidak nullable di keduanya.

Misalnya, kolom "judul" NVARCHAR ditandai sebagai NULLable dalam database Anda, dan berisi nilai NULL. Meskipun kolom tersebut ditandai sebagai NOT NULLable di O / R-Mapping Anda, LINQ akan memuatnya dengan sukses dan mengatur kolom-String ke null.

  • Sekarang Anda mengubah sesuatu dan memanggil SubmitChanges ().
  • LINQ akan menghasilkan query SQL yang berisi "WHERE [title] IS NULL", untuk memastikan bahwa judul tersebut tidak diubah oleh orang lain.
  • LINQ mencari properti [judul] dalam pemetaan.
  • LINQ akan menemukan [title] NOT NULLable.
  • Karena [judul] TIDAK NULLable, secara logika tidak pernah bisa menjadi NULL!
  • Jadi, mengoptimalkan kueri, LINQ menggantinya dengan "di mana 0 = 1", SQL yang setara dengan "tidak pernah".

Gejala yang sama akan muncul ketika tipe data bidang tidak cocok dengan tipe data dalam SQL, atau jika bidang hilang, karena LINQ tidak akan dapat memastikan data SQL tidak berubah sejak membaca data.

Sam
sumber
4
Saya memiliki masalah yang serupa - meskipun sedikit berbeda -, dan saran Anda untuk memeriksa ulang untuk nullable menyelamatkan hari saya! Saya sudah botak, tetapi masalah ini pasti akan membuat saya kehilangan kepala jika saya punya satu .. terima kasih!
Rune Jacobsen
7
Pastikan Anda menyetel properti 'Nullable' di jendela properti ke True. Saya sedang mengedit properti 'Jenis Data Server', mengubahnya dari VARCHAR(MAX) NOT NULLmenjadi VARCHAR(MAX) NULLdan mengharapkannya berfungsi. Kesalahan yang sangat sederhana.
Harus mendukung ini. Ini menghemat banyak waktu. Sedang melihat tingkat isolasi saya karena saya mengira itu adalah masalah konkurensi
Adrian
3
Saya memiliki NUMERIC(12,8)kolom yang dipetakan ke Decimalproperti. Saya harus tepat DbType di atribut Kolom [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
Costo
3
Salah satu cara untuk mengidentifikasi bidang / kolom masalah adalah dengan menyimpan kelas entitas Linq-ke-SQL Anda saat ini, yang terletak di file .dbml, ke file terpisah. Kemudian, hapus model Anda saat ini dan buat ulang dari database (menggunakan VS), yang akan menghasilkan file .dbml baru. Kemudian, jalankan komparator seperti WinMerge atau WinDiff pada dua file .dbml untuk menemukan perbedaan masalah.
david.barkhuizen
24

Pertama, penting untuk diketahui, apa yang menyebabkan masalah. Solusi Googling akan membantu, Anda dapat mencatat detail (tabel, kolom, nilai lama, nilai baru) tentang konflik untuk menemukan solusi yang lebih baik untuk menyelesaikan konflik nanti:

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

Buat pembantu untuk membungkus sumbitChanges Anda:

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

Dan kemudian panggil kirim kode perubahan:

Datamodel.SubmitChangesWithDetailException();

Terakhir, catat pengecualian di penangan pengecualian global Anda:

protected void Application_Error(object sender, EventArgs e)
{         
    Exception ex = Server.GetLastError();
    //TODO
}
Tomas Kubes
sumber
3
Solusi luar biasa! Saya memiliki tabel yang memiliki sekitar 80 bidang, dan ada banyak pemicu pada tabel yang memperbarui berbagai bidang selama penyisipan dan pembaruan. Saya mendapatkan kesalahan ini dari saat memperbarui datacontext menggunakan L2S, tetapi cukup yakin bahwa itu disebabkan oleh salah satu pemicu yang memperbarui bidang, sehingga menyebabkan konteks data berbeda dari data di tabel. Kode Anda membantu saya melihat dengan tepat bidang mana yang menyebabkan konteks data tidak sinkron dengan tabel. Terima kasih banyak!!
Jagd
1
Ini adalah solusi yang bagus untuk tabel besar. Untuk menangani nulls, ubah 'col.XValue.ToString ()' menjadi 'col.XValue == null? "null": col.XValue.ToString () 'untuk masing-masing dari tiga bidang nilai.
humbad
Lakukan pencegahan terhadap referensi null saat merangkai OriginalValue, CurrentValue, dan DatabaseValue.
Floyd Kosch
16

Ada metode di DataContext yang disebut Refresh yang dapat membantu di sini. Ini memungkinkan Anda memuat ulang rekaman database sebelum perubahan dikirimkan, dan menawarkan mode berbeda untuk menentukan nilai mana yang akan disimpan. "KeepChanges" tampaknya yang paling cerdas untuk tujuan saya, ini dimaksudkan untuk menggabungkan perubahan saya dengan perubahan yang tidak bertentangan yang terjadi di database untuk sementara.

Jika saya memahaminya dengan benar. :)

Matt Sherman
sumber
5
Jawaban ini memperbaiki masalah dalam kasus saya: dc.Refresh(RefreshMode.KeepChanges,changedObject);sebelum dc.SubmitChanges
HugoRune
Saya mengalami masalah ini saat menerapkan ReadOnlyAttribute ke properti di situs web Data Dinamis. Pembaruan berhenti bekerja dan saya mendapatkan kesalahan "Baris tidak ditemukan atau diubah" (sisipan baik-baik saja). Perbaikan di atas menghemat banyak tenaga dan waktu!
Chris Cannon
Bisakah Anda menjelaskan nilai RefreshMode, misalnya, apa arti KeepCurrentValues? apa fungsinya? Terimakasih banyak. Saya bisa membuat pertanyaan ...
Chris Cannon
Saya memiliki masalah dengan transaksi bersamaan yang tidak selesai tepat waktu untuk memulai transaksi lain pada baris yang sama. KeepChanges membantu saya di sini, jadi mungkin itu hanya membatalkan transaksi saat ini (sambil menjaga nilai yang disimpannya) dan memulai yang baru (sejujurnya saya tidak tahu)
Erik Bergstedt
11

Ini juga bisa disebabkan oleh penggunaan lebih dari satu DbContext.

Jadi contohnya:

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

Kode ini akan gagal dari waktu ke waktu, dengan cara yang tampaknya tidak dapat diprediksi, karena pengguna digunakan dalam kedua konteks, diubah dan disimpan di satu konteks, lalu disimpan di konteks lain. Representasi dalam memori dari pengguna yang memiliki "Sesuatu" tidak cocok dengan apa yang ada di database, sehingga Anda mendapatkan bug yang mengintai ini.

Salah satu cara untuk mencegahnya adalah dengan menulis kode apa pun yang mungkin pernah disebut sebagai metode pustaka sedemikian rupa sehingga membutuhkan DbContext opsional:

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

Jadi sekarang metode Anda menggunakan database opsional, dan jika tidak ada, pergi dan membuatnya sendiri. Jika ada, itu hanya menggunakan kembali apa yang telah diteruskan. Metode helper memudahkan untuk menggunakan kembali pola ini di seluruh aplikasi Anda.

Chris Moschini
sumber
10

Saya memecahkan kesalahan ini dengan menyeret ulang tabel dari penjelajah server ke perancang dan membangun kembali.


sumber
Menyeret ulang tabel yang melanggar dari Penjelajah Server ke perancang dan membangun kembali memperbaiki ini untuk saya juga.
rstackhouse
4

Inilah yang Anda butuhkan untuk mengganti kesalahan ini pada kode C #:

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }
MarceloBarbosa
sumber
Saya telah menjadwalkan item yang dikirimkan oleh aplikasi front-end ke database. Ini memicu eksekusi dalam layanan, masing-masing di utas berbeda. Pengguna dapat menekan tombol 'batal' yang mengubah semua status perintah yang luar biasa. Layanan menyelesaikan masing-masing tetapi menemukan bahwa 'Tertunda' diubah menjadi 'Dibatalkan' dan tidak dapat mengubahnya menjadi 'Selesai'. Ini memperbaiki masalah saya.
pwrgreg007
2
Periksa juga enumerasi RefreshMode lainnya, seperti KeepCurrentValues. Perhatikan bahwa Anda harus memanggil SubmitChanges lagi setelah menggunakan logika ini. Lihat msdn.microsoft.com/en-us/library/… .
pwrgreg007
3

Saya tidak tahu apakah Anda telah menemukan jawaban yang memuaskan untuk pertanyaan Anda, tetapi saya memposting pertanyaan serupa dan akhirnya menjawabnya sendiri. Ternyata opsi koneksi default NOCOUNT diaktifkan untuk database, yang menyebabkan ChangeConflictException untuk setiap pembaruan yang dibuat dengan Linq ke Sql. Anda dapat merujuk ke posting saya di sini .

Michael Nero
sumber
3

Saya memperbaiki ini dengan menambahkan (UpdateCheck = UpdateCheck.Never)ke semua[Column] definisi.

Tidak terasa seperti solusi yang tepat. Dalam kasus saya ini tampaknya terkait dengan fakta bahwa tabel ini memiliki asosiasi ke tabel lain dari mana baris dihapus.

Ini ada di Windows Phone 7.5.

Johan Paul
sumber
1

Dalam kasus saya, kesalahan muncul ketika dua pengguna yang memiliki konteks data LINQ-ke-SQL yang berbeda memperbarui entitas yang sama dengan cara yang sama. Ketika pengguna kedua mencoba pembaruan, salinan yang mereka miliki dalam konteks datanya sudah usang meskipun itu dibaca setelah pembaruan pertama selesai.

Saya menemukan penjelasan dan solusi dalam artikel ini oleh Akshay Phadke: https://www.c-sharpcorner.com/article/overview-of-concurrency-in-linq-to-sql/

Inilah kode yang paling sering saya angkat:

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

Ketika saya melihat jendela keluaran saya saat debugging, saya bisa melihat bahwa Nilai Saat Ini cocok dengan Nilai Database. "Nilai Asli" selalu menjadi pelakunya. Itu adalah nilai yang dibaca oleh konteks data sebelum menerapkan pembaruan.

Terima kasih kepada MarceloBarbosa atas inspirasinya.

CAK2
sumber
0

Saya tahu pertanyaan ini sudah lama dijawab tetapi di sini saya telah menghabiskan beberapa jam terakhir membenturkan kepala ke dinding dan saya hanya ingin membagikan solusi saya yang ternyata tidak terkait dengan salah satu item di utas ini:

Caching!

Bagian select () dari objek data saya menggunakan caching. Saat memperbarui objek, kesalahan Row Not Found or Changed telah muncul.

Beberapa jawaban memang menyebutkan menggunakan DataContext yang berbeda dan dalam retrospeksi mungkin inilah yang terjadi tetapi itu tidak langsung membuat saya berpikir caching jadi mudah-mudahan ini akan membantu seseorang!

rtpHarry
sumber
0

Saya baru-baru ini mengalami kesalahan ini, dan menemukan bahwa masalahnya bukan pada Konteks Data saya, tetapi dengan pernyataan pembaruan yang diaktifkan di dalam pemicu setelah Komit dipanggil pada Konteks. Pemicu mencoba memperbarui bidang yang tidak dapat dinolkan dengan nilai nol, dan itu menyebabkan kesalahan konteks dengan pesan yang disebutkan di atas.

Saya menambahkan jawaban ini semata-mata untuk membantu orang lain mengatasi kesalahan ini dan tidak menemukan resolusi dalam jawaban di atas.

jamisonLikeCode
sumber
0

Saya juga mendapatkan kesalahan ini karena menggunakan dua konteks yang berbeda. Saya menyelesaikan masalah ini dengan menggunakan konteks data tunggal.

srinivas vadlamudi
sumber
0

Dalam kasus saya, masalahnya adalah dengan opsi pengguna di seluruh server. Berikut:

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

Saya mengaktifkan opsi NOCOUNT dengan harapan mendapatkan beberapa manfaat kinerja:

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

dan ini ternyata mematahkan pemeriksaan Linq untuk Baris yang Terkena Dampak (sebanyak yang saya bisa ketahui dari sumber .NET), yang mengarah ke ChangeConflictException

Mengatur ulang opsi untuk mengecualikan 512 bit memperbaiki masalah.

Wojtek
sumber
0

Setelah menggunakan jawaban qub1n, saya menemukan bahwa masalah bagi saya adalah saya secara tidak sengaja menyatakan kolom database menjadi desimal (18,0). Saya menetapkan nilai desimal, tetapi database mengubahnya, menghapus bagian desimal. Hal ini mengakibatkan masalah berganti baris.

Tambahkan saja ini jika ada orang lain yang mengalami masalah serupa.

John Pasquet
sumber
0

gunakan saja Linq2DB, jauh lebih baik

nam vo
sumber