פרסום: אפליקציית קוד פתוח קטנה ושימושית ליצירת Data Transfer Objects באופן אוטומטי על בסיס LINQ to SQL Data Classes

22 בSeptember 2009

2 תגובות

הקדמה

באחד הפוסטים הקודמים כתבתי על Data Transfer Objects והשימוש בהם לעבודה נכונה ונוחה עם LINQ to SQL ו- ADO.NET Entity Framework.
בין התגובות שקיבלתי בפורום “תכנות .NET” בתפוז שבתגובה לשאלה שעלתה בו פורסם הפוסט, עלה העניין שלמרות שמדובר בכתיבה יותר נוחה, היא דורשת למעשה עבודה כפולה בהתחלה, וגם ציינתי זאת בפוסט. כשמתחילים לכתוב, יש צורך לכתוב DTO נפרד לכל אחת מהישויות עם תחילת העבודה, וגם בד”כ לכתוב מתודות שמבצעות העברה בין הDTO לאובייקט שנמצא ב DAL (האובייקט שמופה ע”י הORM) ולהיפך – לכתוב מתודה שמחזירה את ה DTO בהתאם לאובייקט שנמצא ב DAL. לטעמי, לא מדובר בהרבה עבודה, אבל יש כאלה שזה מפריע להם.

למי שהקצת יותר כתיבת קוד זה מה שמפריע לו, כתבתי היום במסגרת העבודה בסטארטאפסידס Code Generator קטן, שיוצר על סמך DataClasses של LINQ to SQL אובייקטים של DTO מקבילים ותואמים.

LINQ2SQLDTOCreator – האפליקציה עצמה

האפליקציה שכתבתי, ששמה הלא-מקורי-בעליל מופיע בכותרת של הפיסקה (LINQ2SQLDTOCreator), מקבלת נתיב לאסמבלי כלשהו (קובץ DLL של האפליקציה, למשל) ונתיב של המקום אליו היא תפלוט את התוצר שלה. האפליקציה עוברת על כל המחלקות ב DLL ומתעלמת מכולן למעט ממחלקות שמסומנות באטריביוט: System.Data.Linq.Mapping.TableAttribute שמסמל אובייקט שמייצג למעשה מיפוי של טבלה מהדטאבייס. 
בכל אחת מהמחלקות הללו עוברת האפליקציה על כל ה properties שמסומנים באטריביוט System.Data.Linq.Mapping.ColumnAttribute שמסמן מיפוי של ה property לעמודה כלשהי בדטאבייס.
בסופו של דבר, מייצרת האפליקציה עבור כל אובייקט בDAL שממופה לטבלה, מחלקה (.cs) שמכילה הגדרות של properties עבור כל אחד מה types (כאשר ה properties הם מאותו ה type שהוגדרו ב DAL – במידה שהם מוגדרים ב database כ Allow Null, אז הם יוגדרו גם ב DTO כ Nullable Types). בנוסף, במחלקה הזאת, כלולות שתי מתודות באופן אוטומטי: מתודה סטאטית שמקבלת מופע מה type של האובייקט שבDAL שממופה לטבלה המקורית ומחזירה DTO, ומתודה שמביאה עבור המופע של הDTO את האובייקט DAL התואם, עם הערכים המתאימים (זה אמנם לא חלק מההגדרה של DTO, אבל שילבתי את זה לשם הנוחות. כמובן, שזה דורש שאם ה DTO בפרוייקט נפרד, אז יהיה רפרנס לDAL, כדי שאפשר יהיה ליצור אובייקט של הDAL ולקבל אותו).
לדוגמא, הנה קובץ שנוצר על-ידי האפליקציה:

   1: using System;

   2: /*

   3: ------------------------DTO OBjECT-----------------------------

   4: --------------Generated By LINQ2SQLDTOCreator------------------

   5: ----------------Developed by Shahar Gvirtz---------------------

   6: ---------http://blogs.microsoft.co.il/blogs/shahar-------------

   7: ---------------http://weblogs.asp.net/shahar-------------------

   8: */

   9:  

  10: namespace DTO

  11: {

  12:     public class VideoCategoryDTO

  13:     {

  14:         public static VideoCategoryDTO GetDTOFromDALObject( DAL.VideoCategory src )

  15:         {

  16:             VideoCategoryDTO obj = new VideoCategoryDTO ();

  17:             obj.ID = src.ID;

  18:             obj.Name = src.Name;

  19:             obj.Sorting = src.Sorting;

  20:  

  21:             return obj;

  22:         }

  23:         public DAL.VideoCategory GetDALObject()

  24:         {

  25:             DAL.VideoCategory obj = new DAL.VideoCategory ();

  26:             obj.ID = ID;

  27:             obj.Name = Name;

  28:             obj.Sorting = Sorting;

  29:  

  30:  

  31:             return obj;

  32:         }

  33:         

  34:         public Int32 ID { get; set; }

  35:         public String Name { get; set; }

  36:         public Int32 Sorting { get; set; }

  37:  

  38:  

  39:     }

  40: }


השימוש באפליקציה הזאת פשוט יחסית:

  1. הורידו את האפליקציה
  2. הקובץ שהורדתם כולל את הקוד המלא. בתוך תיקיית הפרוייקט, בנתיב \bin\release תמצאו את קבצי ההרצה הנדרשים. העתיקו את כולם לאיזושהי תיקייה עם נתיב קצר לשם הנוחות.
  3. האפליקציה היא Console Application. בהרצה, היא מקבלת שני פרמטרים מופרדים ברווח. הראשון, הוא הנתיב ל DLL (אם הוא מכיל רווחים, יש לעטוף אותו במרכאות). השני, הוא הנתיב שבו רוצים את הפלט של הקבצי קוד שנוצרו. למשל:

       1: c:\myApp\LINQ2SQLDTOCreator.exe "c:\Dev\App\bin\debug\logic.dll" "c:\outputfromapp"

    שימו לב שהפרמטרים מופרדים עם רווח יחיד.

  4. האפליקציה תרוץ, ובתיקייה שהכנסתם תמצאו קבצים כמספר האובייקטים שממופים לטבלאות אצלכם באסמבלי. כל קובץ מורכב מהשם המקורי בסיומת DTO. את הקבצים האלה עליכם לשלב בפרוייקט (copy paste) במקום שאתם רוצים.

    זכרו שאם אתם שמים בפרוייקט נפרד, יש צורך שהוא יכיל רפרנס לפרוייקט שמכיל את ה LINQ to SQL Data Classes.
  5. בהמשך, כל פעם שאתם מבצעים שינוי, אתם יכולים להריץ את האפליקציה שוב כדי לקבל DTO’s מעודכנים.

מגבלות שימוש ידועות

  • האפליקציה אינה תומכת בקשרים בין האובייקטים. אם קיים Relationship בדטאבייס, בתהליך יצירת ה DTO יווצרו properties מתאימים לID בטבלה האחרת, אבל לא יהיה קיים קשר בין האובייקטים השונים.
  • האפליקציה מסוגלת לעבוד עם assemblies שה Target Framework שלהם הוא .NET 3.5. היא לא מסוגלת לעבוד עם אסמבליס שה target framework שלהם הוא 4. ההסבר לזה מופיע פה. מי שממש חשוב לו, פשוט צריך לפתוח את הפרוייקט, להגדיר לו target framework של 4 והכל יעבוד טוב. לטובת כאלה שהם לא פריקים של בטות של מיקרוסופט, אני בניתי אותו ב-3.5.
  • האפליקציה לא משלבת אוטומטית את הקבצים בפרוייקט ולא דואגת לנושאי רפרנסים וכו’.

במידה שאתם מוצאים בעיות נוספות, אני אשמח לשמוע ולתקן.

החלק הטכני – מה קורה מאחורי הקלעים?

כפי שכבר כתבתי בחלק הקודם, בגדול, מדובר ב Reflection לא מסובך במיוחד. האפליקציה מוצאת בהתאם ל attributes את הנתונים הרלוונטיים מבחינתה (השימוש ב attribute הוא כדי להבחין בין סתם class ל class של ה LINQ to SQL Data Classes. כעיקרון, ה properties היחידים שקיימים ב classes שנוצרים על ידי ה LINQ to SQL הם properties שממופים לעמודות. אבל, בכל זאת אני בודק בהתאם ל attributes, למקרה שהשמשתמש בחר לעשות שינויים בקבצים הללו בעצמו.

באפליקציה קיים Resource שמכיל קובץ טקסט שמהווה למעשה את התבנית למחלקות שנוצרות באמצעות המחולל. בתבנית יש מספר markers (מתחילים בסולמית) שלמקומות האלה מוכנסים (באמצעות replace פשוט) הערכים שנווצרים ע”י האפליקציה על סמך ה reflection.

מבחינת השימוש ב reflection – רובו פשוט למדיץ. הנקודה היחידה שייתכן שפחות מוכרת הוא העבודה מול ה Nullable types. כעיקרון, מי שלא יודע, nullable types זה למעשה מופעים של Nullable<T>. על-מנת לבדוק האם ה type של פרופרטי מסויים הוא nullable או לא, אני בודק האם מדובר ב type גנרי, והאם הוא שווה ל Nullable:

   1: if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))

   2:  

הסיבה לבדיקה, דרך אגב, היא כי אם אני ישר מדפיס את ה type באמצעות ה property שנקרא name, במידה ומדובר ב type גנרי הוא יודפס בצורה חסרת משמעות – למשל, Nullable’ (עם גרש בסוף). על-מנת שהקוד כן יעבור קומפילציה, אני משנה בידת הצורך את הסינטקס לסינטקס סי-שארפי.

סיכום

האפליקציה שהוצגה היא אפליקציה פשוטה, המשתמשת ב Reflection, על-מנת לייצר מחלקות Data Transfer Objects על סמך LINQ to SQL Data Classes. האפליקציה יכולה להיות שימושית מאד למפתחים המשתמשים ב LINQ to SQL ובORM-ים אחרים.

ניתן להוריד את האפליקציה מכאן.

שחר.

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

Leave a Reply

Your email address will not be published. Required fields are marked *

2 תגובות

  1. Shlomo22 בSeptember 2009 ב 23:42

    אחלה, כל הכבוד.

    למה לא העלית את זה ל – CodePlex ?

    Reply
  2. שחר גבירץ23 בSeptember 2009 ב 7:28

    כי תכלס זה כמה עשרות שורות קוד בודדות.

    ותודה.

    שחר

    Reply