Bir SQL Server Compact Edition Veritabanında LINQ to SQL'deki "Satır bulunamadı veya değiştirilmedi" İstisnasını çözmek için ne yapabilirim?


96

Bir LINQ to SQL bağlantısıyla (SQL Server Compact Edition'a karşı) bir çift özelliği güncelledikten sonra DataContext'e SubmitChanges'ı çalıştırırken "Satır bulunamadı veya değiştirildi" mesajı alıyorum. 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();

Sorgu aşağıdaki SQL'i oluşturur:

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

Bariz sorun, WHERE 0 = 1'dir . Kayıt yüklendikten sonra, "deviceSessionRecord" daki tüm özelliklerin birincil anahtarı içerecek şekilde doğru olduğunu onayladım. Ayrıca, "ChangeConflictException" öğesini yakalarken bunun neden başarısız olduğu hakkında ek bilgi yoktur. Ayrıca, bu istisnanın veritabanında tam olarak bir kayıtla (güncellemeye çalıştığım kayıt) atıldığını da onayladım.

Garip olan, kodun farklı bir bölümünde çok benzer bir güncelleme ifadesine sahip olmam ve aşağıdaki SQL'i oluşturması ve gerçekten de SQL Server Compact Edition veritabanımı güncellemesi.

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

Hem Veritabanı Şemasında hem de LINQ sınıflarını oluşturan DBML'de uygun birincil alan değerlerinin tanımlandığını doğruladım.

Sanırım bu neredeyse iki parçalı bir soru:

  1. İstisna neden atılıyor?
  2. İkinci oluşturulan SQL kümesini inceledikten sonra, çatışmaları tespit etmek için tüm alanları kontrol etmek güzel olurdu, ancak bunun oldukça verimsiz olacağını düşünüyorum. Bu her zaman böyle mi çalışır? Yalnızca birincil anahtarı kontrol etmek için bir ayar var mı?

Son iki saattir bununla mücadele ediyorum, bu yüzden herhangi bir yardım takdir edilecektir.


FWIW: Bu hatayı, yöntemi istemeden iki kez çağırdığımda alıyordum. İkinci görüşmede olur.
Kris

Mükemmel arka plan bilgisi c-sharpcorner.com/article/…
CAK2

Yanıtlar:


191

Bu iğrenç ama basit:

O / R-Designer'daki tüm alanların veri türlerinin SQL tablonuzdaki veri türleriyle eşleşip eşleşmediğini kontrol edin. Nullable için iki kez kontrol edin! Bir sütun, hem O / R-Designer hem de SQL'de null yapılabilir veya her ikisinde de null yapılamaz olmalıdır.

Örneğin, bir NVARCHAR sütunu "başlık" veritabanınızda NULLable olarak işaretlenmiştir ve NULL değerini içerir. O / R-Mapping işleminizde sütun NULLable DEĞİL olarak işaretlenmiş olsa da, LINQ bunu başarıyla yükleyecek ve sütun-String değerini null olarak ayarlayacaktır.

  • Şimdi bir şeyi değiştirirsiniz ve SubmitChanges () çağırırsınız.
  • LINQ, başlığın başkası tarafından değiştirilmediğinden emin olmak için "WHERE [title] IS NULL" içeren bir SQL sorgusu oluşturacaktır.
  • LINQ, eşlemede [başlık] 'ın özelliklerini arar.
  • LINQ, [başlık] 'ın NULLable DEĞİL olduğunu bulacaktır.
  • [Başlık] NULL OLAMAZ olduğundan, mantıksal olarak hiçbir zaman NULL olamaz!
  • Bu nedenle, sorguyu optimize ederken, LINQ onu "hiçbir zaman" SQL eşdeğeri olan "0 = 1" ile değiştirir.

LINQ, verileri okuduktan sonra SQL verilerinin değişmediğinden emin olamayacağından, bir alanın veri türleri SQL'deki veri türleriyle eşleşmediğinde veya alanlar eksik olduğunda aynı belirti görünecektir.


4
Biraz farklı olsa da benzer bir sorun yaşadım ve nullable'ı tekrar kontrol etme tavsiyeniz günümü kurtardı! Zaten keldim, ama bu sorun elimde olsaydı bana başka bir saça mal olurdu .. teşekkürler!
Rune Jacobsen

7
Özellikler penceresinde 'Nullable' özelliğini True olarak ayarladığınızdan emin olun. Ben onu değiştirmeyi, 'Sunucu Veri Türü' özelliği düzenleme yapıldı VARCHAR(MAX) NOT NULLetmek VARCHAR(MAX) NULLve işe bunu bekliyor. Çok basit bir hata.

Buna olumlu oy vermeliydim. Bana çok zaman kazandırdı. İzolasyon seviyelerime bakıyordum çünkü bunun bir eşzamanlılık sorunu olduğunu düşünmüştüm
Adrian

3
NUMERIC(12,8)Bir Decimalmülke eşlenmiş bir sütunum vardı . Sütun niteliğindeki DbType'ı hassaslaştırmak zorunda kaldım [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
Costo

3
Sorun alanlarını / sütunlarını tanımlamanın bir yolu, .dbml dosyasında bulunan mevcut Linq-to-SQL varlık sınıflarınızı ayrı bir dosyaya kaydetmektir. Ardından, mevcut modelinizi silin ve yeni bir .dbml dosyası oluşturacak şekilde veritabanından (VS kullanarak) yeniden oluşturun. Ardından, sorun farklılıklarını bulmak için iki .dbml dosyası üzerinde WinMerge veya WinDiff gibi bir karşılaştırıcı çalıştırın.
david.barkhuizen

26

İlk olarak, soruna neyin sebep olduğunu bilmek faydalıdır. Googling çözümü yardımcı olmalı, çatışmayı daha sonra çözmek için daha iyi bir çözüm bulmak için çatışmayla ilgili ayrıntıları (tablo, sütun, eski değer, yeni değer) günlüğe kaydedebilirsiniz:

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

Sumbit'inizi sarmak için yardımcı oluşturun

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

Ve sonra değişiklikleri gönderme kodunu çağırın:

Datamodel.SubmitChangesWithDetailException();

Son olarak, istisnayı genel istisna işleyicinize kaydedin:

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

3
Harika çözüm! Yaklaşık 80 alana sahip bir tablom var ve tablodaki ekler ve güncellemeler sırasında çeşitli alanları güncelleyen çok sayıda tetikleyici var. Bu hatayı, L2S kullanarak veri bağlamını güncellerken alıyordum, ancak bunun bir alanı güncelleyen tetikleyicilerden birinin neden olduğundan ve bu nedenle veri bağlamının tablodaki verilerden farklı olmasına neden olduğundan oldukça emindim. Kodunuz, tam olarak hangi alanın veri bağlamının tabloyla senkronize olmamasına neden olduğunu görmeme yardımcı oldu. Çok teşekkürler !!
Jagd

1
Bu, büyük masalar için harika bir çözümdür. Boş değerleri işlemek için 'col.XValue.ToString ()' 'col.XValue == null? Üç değer alanının her biri için "null": col.XValue.ToString () '.
humbads

Aynı şekilde, OriginalValue, CurrentValue ve DatabaseValue dizilimleri yapılırken boş referanslara karşı koruma sağlanır.
Floyd Kosch

16

DataContext'te Refresh adlı ve burada yardımcı olabilecek bir yöntem vardır . Değişiklikler gönderilmeden önce veritabanı kaydını yeniden yüklemenize olanak tanır ve hangi değerlerin saklanacağını belirlemek için farklı modlar sunar. "KeepChanges" benim amaçlarım için en akıllıca görünüyor, değişikliklerimi bu arada veritabanında meydana gelen çakışmayan herhangi bir değişiklikle birleştirmeyi amaçlıyor.

Doğru anladıysam. :)


6
Bu cevap benim durumumdaki sorunu çözdü: dc.Refresh(RefreshMode.KeepChanges,changedObject);dc'den önce SubmitChanges
HugoRune

Bir Dinamik Veri web sitesindeki özelliklere ReadOnlyAttribute uygularken bu sorunu yaşadım. Güncellemeler çalışmayı durdurdu ve "Satır bulunamadı veya değiştirilmedi" hatasını alıyordum (yine de eklemelerde sorun yoktu). Yukarıdaki düzeltme, çok fazla çaba ve zaman tasarrufu sağladı!
Chris Cannon

Lütfen RefreshMode değerlerini açıklar mısınız, örneğin KeepCurrentValues ​​ne anlama geliyor? bu ne işe yarıyor? Çok teşekkürler. Bir soru oluşturabilirim ...
Chris Cannon

Aynı satırlarda başka bir işlemin başlaması için eşzamanlı işlemlerin zamanında tamamlanamamasıyla ilgili sorunlar yaşadım. KeepChanges burada bana yardımcı oldu, bu yüzden belki de mevcut işlemi iptal ediyor (kaydettiği değerleri korurken) ve yenisini başlatıyor (dürüst olmak gerekirse hiçbir fikrim yok)
Erik Bergstedt

11

Buna birden fazla DbContext kullanılması da neden olabilir.

Yani mesela:

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

Bu kod zaman zaman öngörülemez görünen şekillerde başarısız olur, çünkü kullanıcı her iki bağlamda da kullanılır, birinde değiştirilip kaydedilir, sonra diğerinde kaydedilir. "Bir şey" e sahip olan kullanıcının bellek içi temsili veritabanındakilerle eşleşmez ve bu nedenle bu gizlenen hatayı alırsınız.

Bunu önlemenin bir yolu, kütüphane yöntemi olarak adlandırılabilecek herhangi bir kodu, isteğe bağlı bir DbContext alacak şekilde yazmaktır:

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

Yani şimdi yönteminiz isteğe bağlı bir veri tabanı alıyor ve eğer yoksa, gidip kendisi bir tane oluşturuyor. Varsa, aktarılanı yeniden kullanır. Yardımcı yöntem, bu kalıbı uygulamanızda yeniden kullanmayı kolaylaştırır.


10

Bu hatayı, bir tabloyu sunucu gezgininden tasarımcıya yeniden sürükleyip yeniden inşa ederek çözdüm.


Sorunlu tabloyu Sunucu Gezgini'nden tasarımcıya yeniden sürüklemek ve yeniden oluşturmak bunu benim için de çözdü.
rstackhouse

4

C # kodunda bu hatayı geçersiz kılmanız gereken şey budur:

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

Veritabanına bir uygulama ön ucu tarafından gönderilen planlanmış öğelerim var. Bunlar, her biri farklı iş parçacıklarında olmak üzere bir hizmette yürütmeyi tetikler. Kullanıcı, tüm bekleyen komutun durumunu değiştiren bir 'iptal' düğmesine basabilir. Hizmet her birini bitirir, ancak 'Beklemede'nin' İptal Edildi 'olarak değiştirildiğini bulur ve' Tamamlandı 'olarak değiştirilemez. Bu benim için sorunu çözdü.
pwrgreg007

2
KeepCurrentValues ​​gibi diğer RefreshMode numaralandırmalarını da kontrol edin. Bu mantığı kullandıktan sonra SubmitChanges'ı tekrar çağırmanız gerektiğini unutmayın. Bkz msdn.microsoft.com/en-us/library/... .
pwrgreg007

3

Sorunuza tatmin edici bir yanıt bulup bulmadığınızı bilmiyorum, ancak benzer bir soru gönderdim ve sonunda kendim cevapladım. Veritabanı için NOCOUNT varsayılan bağlantı seçeneğinin açık olduğu ortaya çıktı, bu da Linq to Sql ile yapılan her güncelleme için ChangeConflictException oluşmasına neden oldu. Sen benim yazı başvurabilirsiniz burada .


3

Bunu (UpdateCheck = UpdateCheck.Never)tüm [Column]tanımlara ekleyerek düzelttim .

Yine de uygun bir çözüm gibi gelmiyor. Benim durumumda, bu tablonun bir satırın silindiği başka bir tabloyla bir ilişkisi olduğu gerçeğiyle ilişkili görünüyor.

Bu, Windows Phone 7.5'tedir.


1

Benim durumumda, farklı LINQ-to-SQL veri bağlamlarına sahip iki kullanıcı aynı varlığı aynı şekilde güncellediğinde hata ortaya çıktı. İkinci kullanıcı güncellemeyi denediğinde, veri bağlamında sahip olduğu kopya, ilk güncelleme tamamlandıktan sonra okunmasına rağmen bayattı.

Akshay Phadke tarafından yazılan bu makaledeki açıklamayı ve çözümü keşfettim: https://www.c-sharpcorner.com/article/overview-of-concurrency-in-linq-to-sql/

İşte çoğunlukla kaldırdığım kod:

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

Hata ayıklama sırasında çıktı pencereme baktığımda, Mevcut Değerin Veritabanı Değeriyle eşleştiğini görebiliyordum. "Orijinal Değer" her zaman suçluydu. Bu, güncellemeyi uygulamadan önce veri bağlamı tarafından okunan değerdi.

İlham için MarceloBarbosa'ya teşekkürler.


0

Bu sorunun uzun zamandır cevaplandığını biliyorum ama burada son birkaç saatimi kafamı duvara vurarak geçirdim ve sadece bu konudaki maddelerden hiçbiriyle ilgili olmadığı ortaya çıkan çözümümü paylaşmak istedim:

Önbelleğe almak!

Veri nesnemin select () kısmı önbelleğe alma kullanıyordu. Nesneyi güncellemeye geldiğinde bir Satır Bulunamadı veya Değiştirildi hatası ortaya çıkıyordu.

Cevapların birçoğu farklı DataContext'lerin kullanıldığından bahsetti ve geriye dönüp bakıldığında muhtemelen olan buydu, ancak anında önbelleğe almayı düşünmeme neden olmadı, bu yüzden umarım bu birine yardımcı olur!


0

Yakın zamanda bu hatayla karşılaştım ve sorunun Veri Bağlamımda değil, Bağlamda Commit çağrıldıktan sonra bir tetikleyicinin içinde etkinleşen bir güncelleme ifadesinde olduğunu buldum. Tetikleyici, boş değer atanamayan bir alanı boş değerle güncellemeye çalışıyordu ve bağlamın yukarıda belirtilen mesajla hata yapmasına neden oluyordu.

Bu yanıtı yalnızca bu hatayla ilgilenen ve yukarıdaki yanıtlarda bir çözüm bulamayan başkalarına yardımcı olmak için ekliyorum.


0

İki farklı bağlam kullandığım için de bu hatayı aldım. Bu sorunu tek veri bağlamı kullanarak çözdüm.


0

Benim durumumda sorun sunucu çapında kullanıcı seçenekleriyle ilgiliydi. Takip etme:

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

Bazı performans avantajları elde etmeyi umarak NOCOUNT seçeneğini etkinleştirdim:

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

ve bu Linq'in Etkilenen Satırlar için kontrollerini (.NET kaynaklarından anlayabildiğim kadarıyla) bozarak ChangeConflictException'a yol açar.

512 biti hariç tutmak için seçeneklerin sıfırlanması sorunu çözdü.


0

Qub1n'nin cevabını kullandıktan sonra, benim için sorunun, yanlışlıkla bir veritabanı sütununu ondalık (18,0) olarak bildirmiş olmam olduğunu fark ettim. Ondalık bir değer atıyordum, ancak veritabanı onu değiştirerek ondalık kısmı çıkarıyordu. Bu, satır değişikliği sorunu ile sonuçlandı.

Başka biri benzer bir sorunla karşılaşırsa bunu eklemeniz yeterli.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.