قليلا من البرنامج التعليمي
أنا حقا أحب Automapper ، وخاصة QueryableExtensions وطريقة <Toogle> . باختصار ، تتيح هذه الطريقة إسقاط الأنواع مباشرةً في استعلام SQL. سمح لتلقي DTO فعلا من قاعدة بيانات. أي لا حاجة للحصول على جميع الكيانات من قاعدة البيانات ، وتحميلها في الذاكرة ، واستخدام Automapper.Map<>
، مما أدى إلى استهلاك كبير وحركة مرور الذاكرة.
نوع الإسقاط
للحصول على عرض في linq ، يلزمك كتابة شيء مثل هذا:
from user in dbContext.Users where user.IsActive select new { Name = user.Name, Status = user.IsConnected ? "Connected" : "Disconnected" }
باستخدام QueryableExtensions ، يمكن استبدال هذا الرمز بما يلي (بالطبع ، بشرط أن تكون قواعد التحويل User -> UserInfo موصوفة بالفعل)
dbContext.Users.Where(x => x.IsActive).ProjectTo<UserInfo>();
التعداد والمشاكل معها
الإسقاط له عيب واحد يحتاج إلى النظر فيه. هذا قيد على العمليات المنفذة. ليس كل شيء يمكن ترجمته إلى استعلام SQL . على وجه الخصوص ، لا يمكن الحصول على معلومات حسب نوع التعداد. على سبيل المثال ، هناك التعداد التالي
public enum FooEnum { [Display(Name = "")] Any, [Display(Name = "")] Open, [Display(Name = "")] Closed }
يوجد كيان يتم فيه الإعلان عن خاصية من النوع FooEnum. في dto ، تحتاج إلى عدم الحصول على التعداد نفسه ، ولكن قيمة خاصية الاسم لسمة DisplayAttribute. لتحقيق هذا من خلال الإسقاط لا يعمل ، لأنه يتطلب الحصول على قيمة السمة انعكاسًا ، والذي ببساطة "لا يعرف شيئًا عن SQL".
نتيجة لذلك ، يتعين عليك إما استخدام Map<>
المعتادة Map<>
، أو تحميل جميع الكيانات في الذاكرة ، أو بدء جدول إضافي به قيم التعداد ومفاتيح خارجية عليه.
هناك حل - تعبيرات
ولكن "سيكون هناك انتقاد على المرأة العجوز." بعد كل شيء ، كل قيم التعداد معروفة مسبقا. SQL لديه تطبيق switch
التي يمكنك إدراجها عند تشكيل الإسقاط. يبقى أن نفهم كيف نفعل هذا. HashTag: "تعبير الأشجار ، كلنا".
يمكن لـ Automapper ، عند عرض الأنواع ، تحويل التعبير إلى تعبير ، بعد Entity Framework ، يحول استعلام SQL المقابل.
للوهلة الأولى ، بناء جملة إنشاء أشجار التعبير في وقت التشغيل غير مريح للغاية. ولكن بعد عدد قليل من المشاكل الصغيرة التي تم حلها ، يصبح كل شيء واضحًا. لحل مشكلة Enum ، تحتاج إلى إنشاء شجرة متداخلة من التعبيرات الشرطية التي تُرجع القيم ، اعتمادًا على البيانات المصدر. شيء مثل هذا
IF enum=Any THEN RETURN "" ELSE IF enum=Open THEN RETURN "" ELSE enum=Closed THEN RETURN "" ELSE RETURN ""
اتخاذ قرار بشأن طريقة التوقيع.
public class FooEntity { public int Id { get; set; } public FooEnum Enum { get; set; } } public class FooDto { public int Id { get; set; } public string Name { get; set; } } // Automapper CreateMap<FooEntity, FooDto>() .ForMember(x => x.Enum, options => options.MapFrom(GetExpression())); private Expression<Func<FooEntity, string>> GetExpression() { }
يجب أن الأسلوب GetExpression()
إنشاء تعبير يتلقى مثيل FooEntity وإرجاع تمثيل سلسلة لخاصية Enum
.
أولاً ، حدد معلمة الإدخال واحصل على قيمة الخاصية نفسها
ParameterExpression value = Expression.Parameter(typeof(FooEntity), "x"); var propertyExpression = Expression.Property(value, "Enum");
بدلاً من سلسلة اسم الخاصية ، يمكنك استخدام nameof(FooEntity.Enum)
التحويل البرمجي nameof(FooEntity.Enum)
أو حتى الحصول على بيانات حول الخاصية System.Reflection.PropertyInfo
أو getter System.Reflection.MethodInfo
. ولكن من أجل المثال ، يكفينا تعيين اسم العقار بشكل صريح.
لإرجاع قيمة محددة ، نستخدم أسلوب Expression.Constant
. نحن تشكيل القيمة الافتراضية
Expression resultExpression = Expression.Constant(string.Empty);
بعد ذلك ، نحن "نلف" على التوالي النتيجة في حالة.
resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(FooEnum.Any)), Expression.Constant(EnumHelper.GetShortName(FooEnum.Any)), resultExpression); resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(FooEnum.Open)), Expression.Constant(EnumHelper.GetShortName(FooEnum.Open)), resultExpression); resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(FooEnum.Closed)), Expression.Constant(EnumHelper.GetShortName(FooEnum.Closed)), resultExpression);
public static class EnumHelper { public static string GetShortName(this Enum enumeration) { return (enumeration .GetType() .GetMember(enumeration.ToString())? .FirstOrDefault()? .GetCustomAttributes(typeof(DisplayAttribute), false)? .FirstOrDefault() as DisplayAttribute)? .ShortName ?? enumeration.ToString(); } }
كل ما تبقى هو وضع النتيجة
return Expression.Lambda<Func<TEntity, string>>(resultExpression, value);
أكثر قليلا التفكير
نسخ جميع قيم التعداد غير مريح للغاية. دعونا إصلاحه
var enumValues = Enum.GetValues(typeof(FooEnum)).Cast<Enum>(); Expression resultExpression = Expression.Constant(string.Empty); foreach (var enumValue in enumValues) { resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(enumValue)), Expression.Constant(EnumHelper.GetShortName(enumValue)), resultExpression); }
دعونا نحسن الحصول على قيمة العقار
عيب الكود أعلاه هو الارتباط الضيق لنوع الكيان المستخدم. إذا كانت هناك حاجة إلى حل مشكلة مماثلة بالنسبة لفئة أخرى ، فأنت بحاجة إلى التوصل إلى طريقة للحصول على قيمة خاصية تعداد النوع. لذلك دع التعبير يفعل ذلك لنا. كمعلمة للطريقة ، سنقوم بتمرير تعبير يتلقى قيمة الخاصية ، والكود نفسه - نحن ببساطة نشكل مجموعة من النتائج لهذه الخاصية الممكنة. قوالب لمساعدتنا
public static Expression<Func<TEntity, string>> CreateEnumShortNameExpression<TEntity, TEnum>(Expression<Func<TEntity, TEnum>> propertyExpression) where TEntity : class where TEnum : struct { var enumValues = Enum.GetValues(typeof(TEnum)).Cast<Enum>(); Expression resultExpression = Expression.Constant(string.Empty); foreach (var enumValue in enumValues) { resultExpression = Expression.Condition( Expression.Equal(propertyExpression.Body, Expression.Constant(enumValue)), Expression.Constant(EnumHelper.GetShortName(enumValue)), resultExpression); } return Expression.Lambda<Func<TEntity, string>>(resultExpression, propertyExpression.Parameters); }
بعض التوضيحات. بسبب نحصل على قيمة الإدخال من خلال تعبير آخر ، ثم لا نحتاج إلى إعلان المعلمة من خلال Expression.Parameter
. نأخذ هذه المعلمة من خاصية تعبير الإدخال ، ونستخدم نص التعبير للحصول على قيمة الخاصية.
ثم يمكنك استخدام الطريقة الجديدة مثل هذا:
CreateMap<FooEntity, FooDto>() .ForMember(x => x.Enum, options => options.MapFrom(GetExpression<FooEntity, FooEnum>(x => x.Enum)));
كل التنمية الناجحة لأشجار التعبير.
أنا أوصي قراءة المقالات التي كتبها مكسيم Arshinov . خاصة حول تعبيرات الأشجار في تطوير المؤسسات .