باستخدام Linq إلى خريطة facebook الشخصية مع معلومات المستخدم

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

  •  03-07-2019
  •  | 
  •  

سؤال

بعد قراءة كتاب عن LINQ أنا أفكر في إعادة كتابة مخطط الدرجة التي كتبت في c# باستخدام LINQ.أنا أتساءل عما اذا كان أي شخص يمكن أن تساعدني.ملاحظة:لها قليلا مربكة ، ولكن كائن المستخدم هو المستخدم المحلي و المستخدم (صغيرة) هو كائن المتولدة من Facebook XSD.

الأصلي معين

public class FacebookMapper : IMapper
{
    public IEnumerable<User> MapFrom(IEnumerable<User> users)
    {
      var facebookUsers = GetFacebookUsers(users);
      return MergeUsers(users, facebookUsers);
    }

    public Facebook.user[] GetFacebookUsers(IEnumerable<User> users)
    {
      var uids = (from u in users
        where u.FacebookUid != null
        select u.FacebookUid.Value).ToList();

      // return facebook users for uids using WCF
    }

    public IEnumerable<User> MergeUsers(IEnumerable<User> users, Facebook.user[] facebookUsers)
    {
      foreach(var u in users)
      {
        var fbUser = facebookUsers.FirstOrDefault(f => f.uid == u.FacebookUid);
        if (fbUser != null)
          u.FacebookAvatar = fbUser.pic_sqare;
      }
      return users;
    }
}

أول اثنين من محاولات ضرب الجدران

محاولة 1

public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
  // didn't have a way to check if u.FacebookUid == null
  return from u in users
    join f in GetFacebookUsers(users) on u.FacebookUid equals f.uid
    select AppendAvatar(u, f);
}

public void AppendAvatar(User u, Facebook.user f)
{
  if (f == null)
    return u;
  u.FacebookAvatar = f.pic_square;
  return u;
}

محاولة 2

public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
  // had to get the user from the facebook service for each single user,
  // would rather use a single http request.
  return from u in users
    let f = GetFacebookUser(user.FacebookUid)
    select AppendAvatar(u, f);
}
هل كانت مفيدة؟

المحلول

حسنا ، ليس من الواضح بالضبط ما IMapper له في ذلك ، ولكن أقترح عدد قليل من الأشياء التي قد لا يكون ممكنا بسبب القيود الأخرى.لقد كتبت هذا الى حد كبير كما فكرت في الأمر أعتقد أنه يساعد على رؤية قطار الفكر في العمل ، التي سوف تجعل من الأسهل بالنسبة لك أن تفعل نفس الشيء في المرة القادمة.(على افتراض كنت مثل بلدي حلول بالطبع :)

LINQ بطبيعتها الفنية في الاسلوب.وهذا يعني أنه من الناحية المثالية ، الاستفسارات يجب أن لا يكون لها آثار جانبية.فعلى سبيل المثال كنت اتوقع طريقة مع التوقيع:

public IEnumerable<User> MapFrom(IEnumerable<User> users)

العودة إلى تسلسل جديد من كائنات المستخدم مع المعلومات الإضافية بدلا من تحور المستخدمين الحاليين.المعلومات الوحيدة التي كنت حاليا إلحاق هو الرمزية, لذلك أود إضافة أسلوب في User على غرار:

public User WithAvatar(Image avatar)
{
    // Whatever you need to create a clone of this user
    User clone = new User(this.Name, this.Age, etc);
    clone.FacebookAvatar = avatar;
    return clone;
}

قد ترغب في جعل User تماما ثابتة - هناك استراتيجيات مختلفة ، مثل البناء نمط.تسألني إذا كنت تريد المزيد من التفاصيل.على أي حال, الشيء الرئيسي هو أن قمنا بإنشاء مستخدم جديد الذي هو نسخة من القديم ولكن مع تحديد الرمزية.

المحاولة الأولى:inner join

الآن مرة أخرى إلى جهاز معين...كنت قد حصلت حاليا على ثلاثة العامة أساليب لكن تخمين هذا فقط أول واحد يجب أن تكون علنية ، وأن بقية API لا تحتاج في الواقع إلى فضح Facebook المستخدمين.يبدو أن GetFacebookUsers الأسلوب هو أساسا حسنا, على الرغم من أنني كنت على الأرجح خط الاستعلام حيث بيضاء.

وذلك بالنظر إلى سلسلة من المستخدمين المحليين ومجموعة من Facebook المستخدمين, نحن تركنا القيام الفعلي تعيين بت.مستقيم "الانضمام" شرط إشكالية لأنها لن تسفر عن المستخدمين المحليين التي لا تملك مطابقة Facebook المستخدم.بدلا من ذلك, نحن بحاجة إلى طريقة علاج غير Facebook المستخدم كما لو كانوا Facebook المستخدم دون الرمزية.أساسا هذا هو null كائن نمط.

يمكننا القيام بذلك عن طريق الخروج مع Facebook المستخدم الذي لديه null uid (على افتراض نموذج كائن يسمح ذلك):

// Adjust for however the user should actually be constructed.
private static readonly FacebookUser NullFacebookUser = new FacebookUser(null);

إلا أننا في الواقع تريد تسلسل من هؤلاء المستخدمين ، لأن هذا هو ما Enumerable.Concat الاستخدامات:

private static readonly IEnumerable<FacebookUser> NullFacebookUsers =
    Enumerable.Repeat(new FacebookUser(null), 1);

الآن يمكننا ببساطة "إضافة" هذه الدمية الدخول إلى واحد حقيقي و لا عادي inner join.علما أن هذا يفترض أن بحث Facebook المستخدمين سوف تجد دائما المستخدم عن أي "الحقيقي" Facebook UID.إذا كان هذا ليس هو الحال ، نحن نحتاج إلى إعادة النظر في هذا و لا تستخدم صلة داخلية.

نحن تشمل "null" المستخدم في النهاية ، ثم القيام الانضمام إلى المشروع باستخدام WithAvatar:

public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
    var facebookUsers = GetFacebookUsers(users).Concat(NullFacebookUsers);
    return from user in users
           join facebookUser in facebookUsers on
                user.FacebookUid equals facebookUser.uid
           select user.WithAvatar(facebookUser.Avatar);
}

لذا الدرجة الكاملة ليكون:

public sealed class FacebookMapper : IMapper
{
    private static readonly IEnumerable<FacebookUser> NullFacebookUsers =
        Enumerable.Repeat(new FacebookUser(null), 1);

    public IEnumerable<User> MapFrom(IEnumerable<User> users)
    {
        var facebookUsers = GetFacebookUsers(users).Concat(NullFacebookUsers);
        return from user in users
               join facebookUser in facebookUsers on
                    user.FacebookUid equals facebookUser.uid
               select user.WithAvatar(facebookUser.pic_square);
    }

    private Facebook.user[] GetFacebookUsers(IEnumerable<User> users)
    {
        var uids = (from u in users
                    where u.FacebookUid != null
                    select u.FacebookUid.Value).ToList();

        // return facebook users for uids using WCF
    }
}

بعض النقاط هنا:

  • كما لوحظ من قبل ، inner join يصبح مشكلة إذا كان المستخدم Facebook UID قد لا يكون المنال صالح المستخدم.
  • كما نعاني من مشاكل إذا كان لدينا مكررة Facebook المستخدمين - يمكن لكل المستخدمين المحليين في نهاية المطاف الخروج مرتين!
  • هذا محل (يزيل) الرمزية غير Facebook المستخدمين.

الثانية النهج:الانضمام إلى المجموعة

دعونا نرى اذا كنا نستطيع معالجة هذه النقاط.سوف نفترض أنه إذا قمنا المنال متعددة Facebook المستخدمين واحد Facebook UID, ثم لا يهم من منهم ونحن الاستيلاء على الرمزية من أنها يجب أن تكون هي نفسها.

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

ونحن يمكن أن تبقي WithAvatar كما كان من قبل - ولكن هذه المرة نحن ذاهبون فقط أن نسميها إذا كان لدينا Facebook المستخدم إلى انتزاع الصورة من.مجموعة أشارك في C# تعبيرات الاستعلام يمثله join ... into.هذا الاستعلام هو معقول طويلة لكنها ليست مخيفة جدا, صادق!

public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
    var facebookUsers = GetFacebookUsers(users);
    return from user in users
           join facebookUser in facebookUsers on
                user.FacebookUid equals facebookUser.uid
                into matchingUsers
           let firstMatch = matchingUsers.DefaultIfEmpty().First()
           select firstMatch == null ? user : user.WithAvatar(firstMatch.pic_square);
}

هنا هو تعبير استعلام مرة أخرى ، ولكن مع التعليقات:

// "Source" sequence is just our local users
from user in users
// Perform a group join - the "matchingUsers" range variable will
// now be a sequence of FacebookUsers with the right UID. This could be empty.
join facebookUser in facebookUsers on
     user.FacebookUid equals facebookUser.uid
     into matchingUsers
// Convert an empty sequence into a single null entry, and then take the first
// element - i.e. the first matching FacebookUser or null
let firstMatch = matchingUsers.DefaultIfEmpty().First()
// If we've not got a match, return the original user.
// Otherwise return a new copy with the appropriate avatar
select firstMatch == null ? user : user.WithAvatar(firstMatch.pic_square);

غير LINQ الحل

وثمة خيار آخر هو استخدام LINQ قليلا جدا.على سبيل المثال:

public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
    var facebookUsers = GetFacebookUsers(users);
    var uidDictionary = facebookUsers.ToDictionary(fb => fb.uid);

    foreach (var user in users)
    {
        FacebookUser fb;
        if (uidDictionary.TryGetValue(user.FacebookUid, out fb)
        {
            yield return user.WithAvatar(fb.pic_square);
        }
        else
        {
            yield return user;
        }
    }
}

يستخدم هذا التكرار كتلة بدلا من تعبير استعلام LINQ. ToDictionary سوف رمي استثناء إذا كان يتلقى نفس المفتاح مرتين خيار واحد للتغلب على هذا هو تغيير GetFacebookUsers للتأكد من أنها تبدو متميزة معرفات:

    private Facebook.user[] GetFacebookUsers(IEnumerable<User> users)
    {
        var uids = (from u in users
                    where u.FacebookUid != null
                    select u.FacebookUid.Value).Distinct().ToList();

        // return facebook users for uids using WCF
    }

التي يفترض خدمة ويب يعمل بشكل مناسب ، بالطبع - ولكن إذا كان لا ، ربما كنت ترغب في رمي استثناء على أي حال :)

الختام

أن تختار من ثلاثة.المجموعة الانضمام هو ربما أصعب أن نفهم ، ولكن يتصرف أفضل.مكرر كتلة الحل ربما هو أبسط ، ينبغي أن يتصرف حسنا مع GetFacebookUsers تعديل.

مما يجعل User ثابتة وبالتأكيد ستكون خطوة إيجابية على الرغم من.

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

آمل أن يساعد هذا - لقد كانت مسألة مثيرة للاهتمام :)

تحرير:هو طفرة الطريقة ؟

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

إذا كان هذا هو الحال ، قد يكون من المفيد تغيير واجهة الخاص بك لجعل هذا أكثر وضوحا.بدلا من إعادة IEnumerable<User> (وهو ما يعني ضمنا - إلى حد ما - الإسقاط) قد ترغب في تغيير التوقيع و الاسم ويترك لك مع شيء من هذا القبيل:

public sealed class FacebookMerger : IUserMerger
{
    public void MergeInformation(IEnumerable<User> users)
    {
        var facebookUsers = GetFacebookUsers(users);
        var uidDictionary = facebookUsers.ToDictionary(fb => fb.uid);

        foreach (var user in users)
        {
            FacebookUser fb;
            if (uidDictionary.TryGetValue(user.FacebookUid, out fb)
            {
                user.Avatar = fb.pic_square;
            }
        }
    }

    private Facebook.user[] GetFacebookUsers(IEnumerable<User> users)
    {
        var uids = (from u in users
                    where u.FacebookUid != null
                    select u.FacebookUid.Value).Distinct().ToList();

        // return facebook users for uids using WCF
    }
}

مرة أخرى هذه ليست خاصة "ينق ذ" الحل (في العملية الرئيسية) أي أكثر من ذلك - ولكن هذا معقول, كما أنك لا حقا "الاستعلام";أنت "تحديث".

نصائح أخرى

وأنا أميل إلى كتابة شيء مثل هذا بدلا من ذلك:

public class FacebookMapper : IMapper
{
    public IEnumerable<User> MapFacebookAvatars(IEnumerable<User> users)
    {
        var usersByID =
            users.Where(u => u.FacebookUid.HasValue)
                 .ToDictionary(u => u.FacebookUid.Value);

        var facebookUsersByID =
            GetFacebookUsers(usersByID.Keys).ToDictionary(f => f.uid);

        foreach(var id in usersByID.Keys.Intersect(facebookUsersByID.Keys))
            usersByID[id].FacebookAvatar = facebookUsersByID[id].pic_sqare;

        return users;
    }

    public Facebook.user[] GetFacebookUsers(IEnumerable<int> uids)
    {
       // return facebook users for uids using WCF
    }
}

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

(أنصح ضد باستخدام Select مثل foreach حلقة لأداء الفعلي تحور العمل على عنصر من مجموعة ، كما فعلت في إعادة بيع ديون المحاولات.يمكنك أن تفعل ذلك, ولكن سوف يفاجأ الناس من خلال الكود الخاص بك, وعليك أن تبقي كسول التقييم في الاعتبار طوال الوقت.)

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