Enums on client side of WCF Data Services

26 במרץ 2014

3 תגובות

לאחרונה יצא לי להתעסק עם WCF Data Services – אחד הדברים שהציקו לי היה העבודה עם enum, אמנם החל מ – Entity Framework 6 יש תמיכה ב – enums במודל, אבל לא בעבודה עם DataServices, במידה והמודל יכיל Enum Type לא ניתן יהיה לבצע Add Service Reference.

אחד הפתרונות שחשבתי לעשות הוא לכתוב בצד הלקוח קוד כמו זה:

Code Snippet
namespace DataCache.ServiceReference
{

    public partial class DegreeType
    {
        public EDegreeType EType
        {
            get
            {
                return (EDegreeType)this.Type;
            }
            set
            {
                this.Type = (int)value;
            }
        }
    }

    public enum EDegreeType
    {
        [Description("לפני")]
        Before,

        [Description("אחרי")]
        After
    }
}

כך בצורה זו – אוכל לעבוד עם enum בצד הלקוח, ומה שישלח לשרת יהיה הערך ב – Type (כך לפחות חשבתי לתומי).

בזמן שינוי של אובייקט מסוג DegreeType ושמירתו בשרת – התוכנה התעופפה לה עם הודעת השגיאה הבאה: The type 'DataCache.ServiceReference.EDegreeType' has no settable properties.

שיטוט בגוגל הביא אותי לכאן, הפיתרון שלו מעניין – אבל יעבוד רק על מאפיינים פשוטים כמו string, int שלא קיימים בצד שרת, על enums ובטח על אובייקטים מורכבים יותר זה לא יעזור, באחד מהפוסטים ברשת עלה רעיון להגדיר את המאפיין כ – internal, זה אכן עבד – אבל אז לא יכולתי לבצע Binding ב – WPF למאפיין הנ”ל.

בשלב הזה התייאשתי, והתחלתי לדבג את הקוד של מייקרוסופט בעזרת ה – reflector (שזה אחד הדברים האהובים עלי בשעות הלילה המאוחרות), לאחר כמה שעות של דיבגינג גיליתי שהם כתבו מנגנון סירליזציה חדש (לא משתמשים באף אחד – מה שהגיוני מכיוון שהם צריכים לסרלז לפי odata) אבל מה שפחות הגיוני, למה הם לא חשבו על attribute פשוט כמו Ignore שיש בכל סריליזר המכבד את עצמו.

בסופו של תהליך – מצאתי שבכל פעם שמישהו מבקש Type כלשהו – הם מחפשים אותו במטמון שלהם ובודקים האם יש לו מיפוי במה שהגיע מהשרת, אם אין לו הם מעיפים שגיאה.

כדי לפתור את הבעייה – עד שהחברה שם למעלה יכתבו לנו Attribute כלשהו – נגשתי בעזרת reflection למטמון שלהם, ושיניתי את ההגדרה של type המסויים שלי שיש לו מיפוי – ובצורה זו הכל בא על מקומו בשלום.

כדי לאפשר קריאות ולא לכתוב תמיד את הקוד מחדש, יצרתי partial class עבור ה – Context בצד הלקוח שנראה כך:

Code Snippet
namespace DataCache.ServiceReference
{
    public partial class EHEntities
    {
        partial void OnContextCreated()
        {
            DataServiceContextHelper.SetAllIgnoreProps(this, typeof(EDegreeType));
        }
    }
}

המתודה מקבלת את ה – DataContext ומערך של types שאין להם מיפוי – ומרמה את המערכת שתחשוב שיש להם מיפוי.

השימוש ב – OnContextCreated מוודא שהקוד שלי ייקרא תמיד לפני כל קוד אחר. וכמובן לא צריך לדאוג לקרוא לו בכל פעם שיוצרים Context.

ה – Helper נראה כך:

Code Snippet
public static class DataServiceContextHelper
{
    private static Type dataServiceContextType;
    private static Type modelType;
    private static Type edmTypeCacheType;

    private static PropertyInfo modelProperty;

    private static MethodInfo getOrCreateEdmTypeInternalMethod;
    private static BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;

    public static void SetAllIgnoreProps(DataServiceContext context, params Type[] types)
    {
        SetTypes(context);
        var modelInstance = modelProperty.GetValue(context);

        foreach (var type in types)
        {
            var edmTypeCacheValue = GetEdmCacheValue(context, type, modelInstance);
            ChangeHasProperties(edmTypeCacheValue);
        }
    }

    private static object GetEdmCacheValue(DataServiceContext context, Type type, object modelInstance)
    {
        var edmTypeCacheValue = getOrCreateEdmTypeInternalMethod.Invoke(modelInstance, new object[] { type });
        if (edmTypeCacheType == null)
        {
            edmTypeCacheType = edmTypeCacheValue.GetType();
        }
        return edmTypeCacheValue;
    }

    private static void ChangeHasProperties(object edmTypeCacheValue)
    {
        var hasProperties = edmTypeCacheType.GetProperty("HasProperties");
        hasProperties.SetValue(edmTypeCacheValue, true);
    }

    private static void SetTypes(DataServiceContext context)
    {
        if (dataServiceContextType == null)
        {
            dataServiceContextType = context.GetType().BaseType;
            modelProperty = dataServiceContextType.GetProperty("Model", flags);
            modelType = modelProperty.PropertyType;
            getOrCreateEdmTypeInternalMethod = modelType.GetMethods(flags).First(x => x.Name == "GetOrCreateEdmTypeInternal");
        }
    }
}

פחות אסביר את הקוד כאן – רק אציין שאני מפעיל מתודה פרטית ב – internal class של המודל, מוציא את המידע המאוחסן במטמון ומשנה לו את ההגדרה שכאילו יש לו מיפוי.

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

3 תגובות

  1. משה6 באפריל 2014 ב 15:03

    ignore לא עובד, אבל notMapping יעבוד,
    בעצם , הסריאליזציה מתבססת על ההגדרות של EF.
    מניסיון שלי, ניתן להעביר את enum עם מאפיין מספרי מקביל \ המרה.

    הגב
    1. הרב דוטנט
      הרב דוטנט6 באפריל 2014 ב 20:21

      לא מדוייק –
      מה שאתה אומר נכון לגבי השרת ללקוח – אך לא הפוך.

      בלי קשר זה נכון לגבי WCF אבל לא לגבי WCF DATA SERVICE

      הגב
  2. מני שמואלי23 במאי 2014 ב 14:13

    לאחרונה גם אני עבדתי עם WCF יחד עם entity framework ולצערי ככל שעבר הזמן הרגשתי ביותר ויותר מגבלות של הטכנולוגייה הזו וחבל, כי לדעתי זו טכנולוגייה מדהימה.
    אישית הייתי צריך להעביר Stream ועוד מספר פרמטרים ופשוט הWCF לא מאפשר את זה.
    הייתי צריך להמיר את הStream לbase64string בשביל שהתוכנה תעבוד…

    הגב