ما الذي يمكنني فعله لحل الاستثناء "لم يتم العثور على الصف أو تغييره" في LINQ إلى SQL على قاعدة بيانات SQL Server Compact Edition؟

StackOverflow https://stackoverflow.com/questions/45045

  •  09-06-2019
  •  | 
  •  

سؤال

عند تنفيذ submitchanges إلى dataContext بعد تحديث خصائص زوجين باستخدام اتصال LINQ إلى SQL (مقابل إصدار SQL Server Compact) أحصل على "صف غير موجود أو تغيير". 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

المشكلة الواضحة هي حيث 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

لقد أكدت أنه تم تحديد قيم الحقول الأساسية المناسبة في كل من مخطط قاعدة البيانات وDBML الذي ينشئ فئات LINQ.

أعتقد أن هذا سؤال يتكون من جزأين تقريبًا:

  1. لماذا يتم طرح الاستثناء؟
  2. بعد مراجعة المجموعة الثانية من SQL التي تم إنشاؤها، يبدو أنه سيكون من الجيد التحقق من جميع الحقول لاكتشاف التعارضات، لكنني أتصور أن هذا سيكون غير فعال إلى حد ما.هل هذه هي الطريقة التي يعمل بها هذا دائمًا؟هل هناك إعداد للتحقق فقط من المفتاح الأساسي؟

لقد كنت أقاتل مع هذا طوال الساعتين الماضيتين لذا سيكون موضع تقدير أي مساعدة.

هل كانت مفيدة؟

المحلول

هذا مقرف ولكنه بسيط:

تحقق مما إذا كانت أنواع البيانات لجميع الحقول في مصمم O/R تتطابق مع أنواع البيانات الموجودة في جدول SQL الخاص بك.تحقق مرة أخرى من وجود قيمة لاغية! يجب أن يكون العمود إما nullable في كل من O/R-Designer وSQL، أو غير nullable في كليهما.

على سبيل المثال، تم وضع علامة "العنوان" على عمود NVARCHAR على أنه NULLable في قاعدة البيانات الخاصة بك، ويحتوي على القيمة NULL.على الرغم من أنه تم وضع علامة على العمود على أنه غير قابل للإلغاء في تعيين O/R الخاص بك، فسوف يقوم LINQ بتحميله بنجاح وتعيين سلسلة العمود إلى قيمة فارغة.

  • الآن يمكنك تغيير شيء ما وتتصل بـ Submitchanges ().
  • ستقوم LINQ بإنشاء استعلام SQL الذي يحتوي على "حيث [العنوان] NULL" ، للتأكد من أن العنوان لم يتغير من قبل شخص آخر.
  • LINQ يبحث عن خصائص [العنوان] في التعيين.
  • سيجد LINQ [العنوان] غير قابل للإلغاء.
  • نظرًا لأن [العنوان] ليس باطلاً ، فبالمنطق لا يمكن أن يكون فارغًا!
  • لذا ، تحسين الاستعلام ، يحل محله LINQ بـ "حيث 0 = 1" ، ما يعادل SQL لـ "Never".

ستظهر نفس الأعراض عندما لا تتطابق أنواع بيانات الحقل مع نوع البيانات في SQL، أو إذا كانت الحقول مفقودة، حيث لن يتمكن LINQ من التأكد من عدم تغيير بيانات SQL منذ قراءة البيانات.

نصائح أخرى

أولاً، من المفيد معرفة سبب المشكلة.من المفترض أن يساعد حل البحث في Google، حيث يمكنك تسجيل التفاصيل (جدول، عمود، قيمة قديمة، قيمة جديدة) حول التعارض لإيجاد حل أفضل لحل التعارض لاحقًا:

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
}

هناك طريقة تسمى DataContext ينعش والتي قد تساعد هنا.فهو يسمح لك بإعادة تحميل سجل قاعدة البيانات قبل إرسال التغييرات، ويوفر أوضاعًا مختلفة لتحديد القيم التي يجب الاحتفاظ بها.يبدو "KeepChanges" هو الأذكى لأغراضي، فهو يهدف إلى دمج تغييراتي مع أي تغيير غير متعارض حدث في قاعدة البيانات في هذه الأثناء.

إذا فهمت ذلك بشكل صحيح.:)

لقد قمت بحل هذا الخطأ عن طريق إعادة سحب الجدول من مستكشف الخادم إلى المصمم وإعادة البناء.

يمكن أن يحدث هذا أيضًا بسبب استخدام أكثر من 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);
        }
    }
}

إذن، تستخدم طريقتك الآن قاعدة بيانات اختيارية، وإذا لم تكن هناك واحدة، فستقوم بإنشاء واحدة بنفسها.إذا كان هناك فإنه يعيد استخدام ما تم تمريره.تسهل الطريقة المساعدة إعادة استخدام هذا النمط عبر تطبيقك.

لا أعرف إذا كنت قد وجدت أي إجابات مرضية لسؤالك، لكنني نشرت سؤالًا مشابهًا وأجبت عليه بنفسي في النهاية.اتضح أنه تم تشغيل خيار الاتصال الافتراضي NOCOUNT لقاعدة البيانات، مما تسبب في حدوث ChangeConflictException لكل تحديث يتم إجراؤه باستخدام Linq إلى Sql.يمكنك الرجوع إلى مشاركتي في هنا.

لقد أصلحت هذا عن طريق إضافة (UpdateCheck = UpdateCheck.Never) للجميع [Column] تعريفات.

ومع ذلك، لا يبدو أنه الحل المناسب.في حالتي يبدو أن الأمر مرتبط بحقيقة أن هذا الجدول له ارتباط بجدول آخر يتم حذف الصف منه.

وهذا موجود على نظام Windows Phone 7.5.

هذا هو ما تحتاجه لتجاوز هذا الخطأ في كود C#:

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

أعلم أنه تمت الإجابة على هذا السؤال منذ فترة طويلة ولكني أمضيت الساعات القليلة الماضية أضرب رأسي بالحائط وأردت فقط مشاركة الحل الذي تبين أنه لا علاقة له بأي من العناصر الموجودة في هذا الموضوع:

التخزين المؤقت!

كان الجزء المحدد () من كائن البيانات الخاص بي يستخدم التخزين المؤقت.عندما يتعلق الأمر بتحديث الكائن، ظهر خطأ لم يتم العثور على الصف أو تم تغييره.

ذكرت العديد من الإجابات استخدام DataContext مختلفًا وفي وقت لاحق ربما يكون هذا هو ما كان يحدث ولكنه لم يقودني على الفور إلى التفكير في التخزين المؤقت لذا آمل أن يساعد هذا شخصًا ما!

لقد واجهت هذا الخطأ مؤخرًا، ووجدت أن المشكلة لم تكن في سياق البيانات الخاص بي، ولكن في تشغيل بيان التحديث داخل المشغل بعد استدعاء الالتزام في السياق.كان المشغل يحاول تحديث حقل غير فارغ بقيمة فارغة، وكان يتسبب في حدوث خطأ في السياق مع الرسالة المذكورة أعلاه.

أقوم بإضافة هذه الإجابة فقط لمساعدة الآخرين في التعامل مع هذا الخطأ وعدم العثور على حل في الإجابات أعلاه.

لقد حصلت أيضًا على هذا الخطأ بسبب استخدام سياقين مختلفين.لقد قمت بحل هذه المشكلة باستخدام سياق بيانات واحد.

في حالتي كانت المشكلة تتعلق بخيارات المستخدم على مستوى الخادم.التالي:

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

لقد قمت بتمكين خيار NOCOUNT على أمل الحصول على بعض فوائد الأداء:

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

وتبين أن هذا يؤدي إلى كسر عمليات فحص Linq للصفوف المتأثرة (بقدر ما يمكنني اكتشافه من مصادر .NET)، مما يؤدي إلى ChangeConflictException

أدت إعادة تعيين الخيارات لاستبعاد 512 بت إلى حل المشكلة.

بعد استخدام إجابة qub1n، وجدت أن المشكلة بالنسبة لي هي أنني أعلنت عن غير قصد أن عمود قاعدة البيانات هو رقم عشري (18,0).كنت أقوم بتعيين قيمة عشرية، لكن قاعدة البيانات كانت تغيرها، وتجرد الجزء العشري.أدى هذا إلى تغيير مشكلة الصف.

ما عليك سوى إضافة هذا إذا واجه أي شخص آخر مشكلة مماثلة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top