في مقال سابق، عرضت العديد من المشاكل التي قد تواجهها عند استخدام LINQ إلى SQL لعمليات التحديث. في الواقع، هذه ليست مشكلة واجهتها وحدي عندما بحثت عن إجابات على الإنترنت، وجدت أن العديد من الأشخاص قد نشروا مقالات مماثلة حول هذا الموضوع. لكن ما لست راضيًا عنه هو أنه على الرغم من أنهم أثاروا المشكلة، إلا أنهم لم يجروا تحليلاً مفصلاً، بل قدموا حلولاً فقط (مثل إضافة أعمدة RowVersion، وإزالة الارتباطات، وما إلى ذلك)، لكنهم لم يوضحوا سبب وجوب القيام بذلك. . وهذا أيضًا هو الهدف الأصلي من كتابة المقالة السابقة، وآمل أن أجد حل المشكلة خطوة بخطوة من خلال تحليل LINQ إلى كود مصدر SQL. ستناقش هذه المقالة هذه الطرق واحدة تلو الأخرى.
الخيار 1: إعادة التعيين في إطار العمل مفتوح المصدر Ezsocio بواسطة TerryLee وAnytao وDing Xue وآخرين، يتم اعتماد إعادة التعيين في بعض الأماكن. داخل طريقة التحديث، احصل على الكيانات الموجودة في قاعدة البيانات بناءً على المفتاح الأساسي، ثم قم بتعيين قيم لخصائصها واحدة تلو الأخرى مع الكيانات الموجودة في المعلمات.
UpdateProfile الفراغ العام (الملف الشخصي p)
{
باستخدام (RepositoryContext db = new RepositoryContext())
{
var Profile = db.GetTable<Profile>().First<Profile>(u => u.ID == p.ID);
Profile.Birthday = p.Birthday;
Profile.Gender = p.Gender;
Profile.Hometown = p.Hometown;
Profile.MSN = p.MSN;
Profile.NickName = p.NickName;
Profile.PhoneNumber = p.PhoneNumber;
QQ = p.QQ;
Profile.State = p.State;
Profile.TrueName = p.TrueName;
Profile.StateRefreshTime = p.StateRefreshTime;
Profile.Avatar = p.Avatar;
Profile.Website = p.Website;
db.SubmitChanges();
}
}
قدم Brother Yang Guo أيضًا طريقة انعكاس لهذا الحل لتحقيق النسخ التلقائي لقيم السمات.
لكنني شخصياً أعتقد أن هذا مخطط يتجنب الواقع ويتجنب الواقع، فهو لا يستخدم واجهة برمجة التطبيقات التي توفرها LINQ إلى SQL لعمليات التحديث، ولكنه يعتمد استراتيجية ملتوية. هذا في الواقع حل وسط، هل لأن طريقة الإرفاق "ليست سهلة الاستخدام"، لذلك لا نستخدمها؟ الكالينجيون.
الخيار 2: تعطيل تتبع الكائنات في هذا الصدد، اقترحت lea أنه يمكن تحقيق التحديث الصحيح عن طريق تعيين خاصية ObjectTrackingEnabled الخاصة بـ DataContext على false.
المنتج العام GetProduct(معرف كثافة العمليات)
{
NorthwindDataContext db = new NorthwindDataContext();
db.ObjectTrackingEnabled = false;
return db.Products.SingleOrDefault(p => p.ProductID == id);
}
لا توجد تغييرات رمز أخرى.
لماذا يمكن التحديث بشكل طبيعي بعد تعطيل تتبع الكائنات؟ دعنا نجد الإجابة من الكود المصدري.
المنطق العام ObjectTrackingEnabled
{
يحصل
{
this.CheckDispose();
إرجاع this.objectTrackingEnabled;
}
تعيين
{
this.CheckDispose();
إذا (this.Services.HasCachedObjects)
{
throw System.Data.Linq.Error.OptionsCannotBeModifiedAfterQuery();
}
this.objectTrackingEnabled = value;
إذا (!this.objectTrackingEnabled)
{
this.deferredLoadingEnabled = false;
}
this.services.ResetServices();
}
}
اتضح أنه عند تعيين ObjectTrackingEnabled على false، سيتم تعيين DeferredLoadingEnabled على false في نفس الوقت. بهذه الطريقة، عند تنفيذ الاستعلام، لن يتم تحميل أي بيانات تتطلب استعلامًا مؤجلًا للكيان، لذلك لن يتم طرح أي استثناء أثناء الإرفاق (راجع التحليل في المقالة السابقة).
في MSDN، نحصل أيضًا على المعلومات المفيدة التالية: يمكن أن يؤدي تعيين الخاصية ObjectTrackingEnable إلى false إلى تحسين أداء الاسترداد لأنه يقلل من عدد العناصر التي سيتم تعقبها. وهذه ميزة مغرية للغاية.
ومع ذلك، عند تعطيل تتبع الكائنات، يجب إيلاء اهتمام خاص لنقطتين: (1) يجب تعطيله قبل تنفيذ الاستعلام. (2) بعد التعطيل، لم يعد من الممكن استدعاء طريقتي Attach و SubmitChanges. وإلا سيتم طرح استثناء.
الخيار 3: إزالة الاقتران تم تقديم طريقة ضعيفة في المقالة السابقة، وهي تعيين الفئة المرتبطة بالمنتج يدويًا إلى قيمة خالية في أسلوب GetProduct. يمكننا استخراج هذا الجزء من الكود ووضعه في طريقة الفصل. نظرًا لأن هذا الانفصال هو أسلوب كيان، فيمكن استخدام الفئات الجزئية:
منتج فئة جزئية عامة
{
فصل الفراغ العام ()
{
this._Category = default(EntityRef<Category>);
}
}
فئة الطبقة العامة الجزئية
{
فصل الفراغ العام ()
{
foreach (منتج var في هذا.المنتجات)
{
فصل المنتج () ؛
}
}
}ولكن هذه الطريقة لتحديد الفصل لكل كيان مرهقة للغاية. مع زيادة عدد الكيانات، تصبح العلاقات أكثر تعقيدًا، ومن السهل ظهور السمات المفقودة. اقترح Zhang Yi طريقة أنيقة للغاية لتجريد هذا المنطق باستخدام الانعكاس:
فصل الفراغ الخاص (كيان TEntity)
{
foreach (FieldInfo fi في الكيان.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
{
إذا (fi.FieldType.ToString().Contains("EntityRef"))
{
قيمة فار = fi.GetValue(entity);
إذا (القيمة != فارغة)
{
fi.SetValue(entity, null);
}
}
إذا (fi.FieldType.ToString().Contains("EntitySet"))
{
قيمة فار = fi.GetValue(entity);
إذا (القيمة != فارغة)
{
MethodInfo mi = value.GetType().GetMethod("Clear");
إذا (مي != فارغة)
{
mi.Invoc(value, null);
}
fi.SetValue(entity, value);
}
}
}
}
يعتقد بعض الأشخاص أيضًا أنه يجب تعيين الأحداث PropertyChanging وPropertyChanged على قيمة خالية أثناء الفصل، لكن الفكرة العامة هي نفسها.
الخيار 4: استخدم التفويض. هذه هي الطريقة التي قدمتها ZC29 في تعليقات مقالتي الأخيرة، وأعتقد شخصيًا أنها تستحق التعلم منها.
UpdateProductWithDelegate الفراغ العام (Expression<Func<Product, bool>> المسند، الإجراء<المنتج>)
{
NorthwindDataContext db = new NorthwindDataContext();
فار المنتج = db.Products.SingleOrDefault(predicate);
الإجراء (المنتج)؛
db.SubmitChanges();
}
// رمز العميل
ProductRepository repository = new ProductRepository();
repository.UpdateProductWithDelegate(p => p.ProductID == 1, p =>
{
p.ProductName = "تم التغيير";
});
استخدم تعبيرات Lambda لتضمين منطق GetProduct في UpdateProduct، واستخدم المفوضين لتأجيل تنفيذ منطق التحديث، مما يؤدي إلى وضع البحث والتحديث بذكاء في DataContext، وبالتالي تجاوز Attach. ومع ذلك، فإن واجهة برمجة التطبيقات (API) الخاصة بهذه الطريقة معقدة بعض الشيء وتتطلب مستويات عالية جدًا من المبرمجين العملاء. علاوة على ذلك، يجب تنفيذ منطق Get مرة أخرى في التحديث، على الرغم من أن فقدان الأداء ضئيل، إلا أنه يبدو دائمًا أنه يمنح الناس شعورًا بأنه ليس جافًا بدرجة كافية.
الخيار 5: استخدام عبارة UPDATE في التعليمات البرمجية المصدر لـ Ezsocio، وجدت طريقة RepositoryBase.UpdateEntity. يتم ربط عبارات SQL داخل الطريقة، وسيتم تحديث الأعمدة التي تم تغييرها فقط. نظرًا لأن ITable لم يعد يُستخدم هنا ويلزم دعم إطار العمل الكامل، فلن يتم تقديم أي تعليقات أخرى. يرجى الرجوع إلى كود مصدر Ezsocio للحصول على التفاصيل.
ملخص تسرد هذه المقالة العديد من الحلول التي وجدتها على الإنترنت في الأيام الأخيرة، ولكل منها إيجابيات وسلبيات، وأي منها أفضل أو أسوأ يعتمد على آراء الأشخاص المختلفين. وفي المقالة التالية سأقوم بمقارنة أداء هذه الطرق للعثور على الحل الأمثل.