كائن C# المستند إلى الأدوية العامة إلى كائن Mapper سؤال

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

  •  26-09-2019
  •  | 
  •  

سؤال

لدي حاجة لكائن إلى كائن Mapper في طلبي. لقد جربت القليل ، لكنني لم أتمكن من العثور على أي شيء يناسب احتياجاتي ، لذلك أنا أكتب بلدي. حاليا لدي واجهة مثل أدناه:

public interface IMapper<T, R> {
    T Map(R obj);
}

أقوم بعد ذلك بتنفيذ حساب accountmapper يقوم بتعيين العميل إلى حساب على النحو التالي:

public class AccountMapper : IMapper<Account, Customer> {
    Account Map(Customer obj) {
        // mapping code
    }
}

هذا يعمل بشكل جيد حتى الآن ، ومع ذلك لدي العديد من الكيانات المصدر التي تخطط إلى نفس كيان الوجهة. على سبيل المثال ، لدي دفعة وفاتورة تعرض كلا الخريطة إلى Billhistory. لكي يدعم ما سبق هذا ، أحتاج إلى إنشاء محبين منفصلين (أي billhistorypaymentmapper و billhistoryinvoicemapper) ، وهو أمر جيد. ومع ذلك ، أحب أن أكون قادرًا على تنفيذها بشكل مختلف قليلاً كما هو موضح أدناه. المشكلة الوحيدة هي أنني لا أعرف ما إذا كان ذلك ممكنًا وإذا كان الأمر كذلك ، فأنا لا أعرف بناء الجملة الصحيح.

public interface IMapper<T> {
    T Map<R>(R obj);
}

public class BillHistoryMapper : IMapper<Account> {
    public BillHistory Map<Invoice>(Invoice obj) {
        // mapping code
    }
    public BillHistory Map<Payment>(Payment obj) {
        // mapping code
    }
}

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

تعديل-------

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

public class BillHistoryMapper : Mapper<BillHistory, Invoice> {
}

حيث يحتوي Mapper على:

public abstract class Mapper<T, R> : IMapper<T, R> {
    public IList<T> Map(IList<R> objList) {
        return objList.ToList<R>().ConvertAll<T>(new Converter<T, R>(Map));
    }
}
هل كانت مفيدة؟

المحلول

سيتعين عليك استخدام الواجهة الأولى وتنفيذ الواجهة عدة مرات على كائنك:

public class BillHistoryMapper : IMapper<Account, Invoice>, 
                                 IMapper<Account, Payment> {     
   ...
}

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

نصائح أخرى

فيما يتعلق بالصف المجرد ، فكر في التخلص منه واستبداله بطريقة تمديد. هذا سيسمح لك باستخدام MapAll الوظيفة بغض النظر عما إذا كنت تنفذ الواجهة أو تستخدم نوعًا من سلسلة الميراث.

public static class MapperExtensions
{
    public static IEnumerable<TOutput> MapAll<TInput, TOutput>
        (this IMapper<TInput, TOutput> mapper, IEnumerable<TInput> input)
    {
        return input.Select(x => mapper.Map(x));
    }
}

سيجعل هذا الأمر الآن أسهل عند محاولة حل مشكلتك أعلاه لأنك لم تعد مضطرًا للوراثة من فئة أساسية يمكنك الآن تنفيذ واجهة التعيين للأنواع التي تريد تعيينها.

public class BillHistoryMapper :
    IMapper<Invoice, BillHistory>, IMapper<Payment, BillHistory>
{
    public BillHistory Map<Invoice>(Invoice obj) {}
    public BillHistory Map<Payment>(Payment obj) {}
}

فكر أيضًا في تغيير الخاص بك IMapper المعلمات العامة لتكون في الاتجاه الآخر (أخذت الحرية في الأمثلة السابقة):

public interface IMapper<in TInput, out TOutput>
{
    TOutput Map(TInput input);
}

والسبب في ذلك هو أنه يخطط مباشرة إلى System.Converter<T> مندوب ويمكنك أن تفعل شيئًا مثل:

IMapper<ObjectA, ObjectB> myAToBMapper = new MyAToBMapper();

ObjectA[] aArray = { new ObjectA(), new ObjectA() };
ObjectB[] bArray = Array.ConvertAll<ObjectA, ObjectB>(aArray, myAToBMapper.Map);

List<ObjectA> aList = new List<ObjectA> { new ObjectA(), new ObjectA() };
List<ObjectB> bList = aList.ConvertAll<ObjectB>(myAToBMapper.Map);

// Or

var aToBConverter = new Converter<ObjectA, ObjectB>(myAToBMapper.Map);
bArray = Array.ConvertAll(aArray, aToBConverter);
bList = aList.ConvertAll(aToBConverter);

السيارات كما تم اقتراح مما يجعل حياتك أسهل. ومع ذلك ، إذا كنت ترغب في الحفاظ على تجريد الخرائط الخاص بك وأن يكون رمزك غير مؤلف لاستراتيجية التعيين الخاصة بك ، فمن السهل جدًا استخدام الواجهة أعلاه لضخ الغلاف حول السيارات. وهذا يعني أيضًا أنه يمكنك الاستمرار في استخدام MapAll طريقة التمديد الموضحة أعلاه.

public class AutoMapperWrapper<in TInput, out TOutput> : IMapper<TInput, TOutput>
{
    public TOutput Map(TInput input)
    {
        return Mapper.Map<TOutput>(input);
    }
}

كلمة أخيرة

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

سيعمل مثالك الثاني مع بعض التغييرات فقط:

// you have to include the R type in the declaration of the Mapper interface
public interface IMapper<T, R> {
    T Map<R>(R obj);
}

// You have to specify both IMapper implementations in the declaration
public class BillHistoryMapper : IMapper<Account, Invoice>, IMapper<Account, Payment> {
    public BillHistory Map<Invoice>(Invoice obj) {
        // mapping code
    }
    public BillHistory Map<Payment>(Payment obj) {
        // mapping code
    }
}

لست متأكدًا مما إذا كان هذا يكسب لك أي شيء على النمط الحالي.

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

public class BillHistoryMapper : IMapper<BillHistory, Invoice>, IMapper<BillHistory, Payment>
{
   ...
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top