Question from Tapuz .Net forum: Linq to SQL OnValidate partial method doesn't fire

2 בינואר 2008

אין תגובות

שאלה:

יצרתי partial class עם שם הטבלה שלי, product במקרה זה, ואז 
implements the OnValidate() partial method
ושם כתבתי את החוקים שלי לפי הצורך.
שמתי BreakPoint בתוך OnValidate ואני רואה שהיא לא נפגעת.
כלומר, בזמן שמירה של השורה, המתודה שלי לא נקראת, ולכן כל העסק לא עובד.
האם יש איזה רעיון למה?

    public partial class Product

    {

        public void OnValidate()

        {

            if ((this.Discontinued == true) && (this.UnitsOnOrder > 0))

            {

                throw new ArgumentException("Reorder level cannot be greater then 0 when you discontinue");

            }

        }

    }

 

תשובה:

בוא נבין קודם מה זה כל ה-Partial הזה.

בואו למשל נביט על טופס Winform.

image

על הטופס הזה שמנו כל מיני פקדים גרפיים, למשל Button ו-TextBox.

image

אנחנו יודעים הרי שאין כזה דבר "קסם" בדוט נט, והכל בסופו של דבר נרשם היכן שהוא.

בדוט נט 1.1 הייתה לנו מתודה בשם InitializeCompoenent שישבה בתוך Form1.cs וה-Designer הכניס לשם עוד הרבה קוד אוטומטי.

image

נעבור על זה חלק-חלק.

image

כאן יש לנו Constructor שיוצר ע"י ה-Designer וזה דואג לקרוא למתודה InitializeComponent.

image

החלק המסומן באדום זאת הצהרה למשתנה ברמת הטופס שנדרש בשביל InitializeComponent.

image

בחלק המודגש לעיל באדום יש מימוש לפונקציית ה-Dispose של הטופס.
ספציפית כאן גם הקטנו את הפונקציה. נראה איך היה נראית פתוחה:

        /// <summary>

        /// Clean up any resources being used.

        /// </summary>

        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>

        protected override void Dispose(bool disposing)

        {

            if (disposing && (components != null))

            {

                components.Dispose();

            }

            base.Dispose(disposing);

        }

עכשיו נביט על דברים שהם קצת פחות "תשתית" וקצת יותר ברור לנו מה הם עושים בפועל:

image

החלק הזה בקוד האוטומטי שמייצר ה-Designer איפשר לנו בתוך המחלקת Form1 שלנו לפנות לכל הפקדים הגרפיים שישבו על הטופס.

ככה למשל היה אפשר לעשות:

image

image

כלומר, הייתה הצהרה ברמת הטופס על הפקד הגרפי כמשתנה.

פקד גרפי –> משתנה ברמת הטופס.

ובתוך ה-InitializeComponent נעשתה מרבית העבודה של אתחול הפקדים הגרפים.

image

ניתן לראות שבשתי שורות הראשונות מאתחלים את הפקדים.

image

ואחר כך עוברים פקד-פקד וקובעים לו משתנים בהתאם לעיצוב שיש כרגע ב-Designer הגרפי שלנו.

image

קודם הכפתור.

image

ואחר כך ה-TextBox שלנו.

 

אז כמו שאמרנו, כל זה היה עוד בדוט נט 1.1 וב-Visual Studio 2003.

בדוט נט 2.0 וב-Visual Studio 2005 הבינו מיקרוסופט שמדובר בהמון קוד מג'ונרנט אוטומטית שנמצא בתוך הטופס שלנו וזה מקשה עלינו כמתכנתים לעבוד.

אז המציאו את ה-Partial Classes כחלק מ-C# 2.0 ו-VB.Net 2.0.
הקונספט הוא לאפשר לכתוב את אותה מחלקה (גם באנגלית: Class) בשניים או יותר קבצים שמתקמפלים ביחד.

למשל, בנושא ה-Designer של winform, זה מאפשר את המבנה הבא:

image

כאן, יש לנו שני קבצי CS שכל אחד מהם מכיל "חלק מהמחלקה Form1".

ב-VS2005 דאגו "להגלות" את הקוד המג'ורנט הרחק מאתנו לתוך קובץ ה-designer.cs (שהוא קובץ CS לכל דבר).

 

בקובץ הראשי שלנו השאירו רק את המינימום קוד הנדרש.

 image

שימו לב ש-Form1 הוא Partial Class עכשיו ולא Class רגיל.
כלומר, קובץ אחר בתוך אותו פרוייקט יכול גם להכיל קוד בשביל Form1.

ובאמת ב-Form1.designer.cs יש את ההצהרה הבאה למעלה:

image

וכל הקוד שג'ונרנט (מלשון To Generate) עבורנו, נמצא עכשיו בקובץ הצדדי הזה.

 

בדוט נט 3.5 לקחו את הקונספט הזה צעד קדימה, למקום הרבה יותר מזעזע.
אמרו שבתוך Partial Class יכול להיות Partial Method.

הקונספט מאחורי Partial Method מאפשר להצהיר על Partial Method בקובץ X1.cs, לקרוא לה בקובץ X1.cs ולממש אותה רק בקובץ X2.cs.
כמובן שזה היה אפשרי כבר קודם (לקרוא למתודות מ-Partial Class של המחלקה שאתה כרגע נמצא בה).
החידוש כאן הוא שאם ה-Partial Method לא ממושת ב-Partial class שלך, הקריאה לפונקציה פשוט לא מתקמפלת .

בואו נדגים את זה באמצעות Linq To Sql. 

image

יש כאן דיאגרמת Linq to Sql עם מחלקה בשם Address שמקושרת לטבלת Address במסד הנתונים.

מאחורי הקלעים, יש לנו שלושה קבצים:

image

קובץ ה-DBML מכיל את ההגדרות שמחברות בין Classes לבין טבלאות במסד הנתונים (זהו קובץ ה-ORM שלנו, ה-Object-To-Relational mapping).
יש את קובץ ה-layout שמכיל כל מיני פרטים על הסידור הגרפי שראינו למעלה (קורדינטות X,Y של כל טבלה על משטח העיצוב, מה פתוח ומה לא וכך הלאה). בעקרון, שום מידע שימושי מלבד ל-Designer הגרפי.

ויש לנו את קובץ ה-designer.cs שמיוצר אוטומטית מקובץ ה-DBML עבורנו ע"י ה-MSLinqToSqlGenerator.

נביט קצת לתוך הקובץ הזה:

image

אפשר לראות שבין השאר יש בקובץ הזה Partial Class בשם Address.

בתוך ה-Partial Class הזו יש גם כמה Partial Methods.

    [Table(Name="Person.Address")]

    public partial class Address : INotifyPropertyChanging, INotifyPropertyChanged

    {

     #region Extensibility Method Definitions

    partial void OnLoaded();

    partial void OnValidate(System.Data.Linq.ChangeAction action);

    partial void OnCreated();

    partial void OnAddressIDChanging(int value);

    partial void OnAddressIDChanged();

    partial void OnAddressLine1Changing(string value);

    partial void OnAddressLine1Changed();

    partial void OnAddressLine2Changing(string value);

    partial void OnAddressLine2Changed();

    partial void OnCityChanging(string value);

    partial void OnCityChanged();

    partial void OnStateProvinceIDChanging(int value);

    partial void OnStateProvinceIDChanged();

    partial void OnPostalCodeChanging(string value);

    partial void OnPostalCodeChanged();

    partial void OnrowguidChanging(System.Guid value);

    partial void OnrowguidChanged();

    partial void OnModifiedDateChanging(System.DateTime value);

    partial void OnModifiedDateChanged();

    #endregion

נביט למשל על המאפיין (גם באנגלית: Property) שנקרא City.

        [Column(Storage="_City", DbType="NVarChar(30) NOT NULL", CanBeNull=false)]

        public string City

        {

            get

            {

                return this._City;

            }

            set

            {

                if ((this._City != value))

                {

                   this.OnCityChanging(value);

                    this.SendPropertyChanging();

                    this._City = value;

                    this.SendPropertyChanged("City");

                   this.OnCityChanged();

                }

            }

        }

אפשר לראות שהמחלקה שלנו מחוברת לטבלה בשם Address, בעוד שהמאפיין City מחובר לטור באותה טבלה גם בשם City.

עכשיו לעניינו, אפשר לראות קריאה לשתי Partial Methods במאפיין הזה: OnCityChanging ו-OnCityChanged.
אלו הן חלק מה-Partial Methods של ה-Partial Class שלנו.

 

    [Table(Name="Person.Address")]

    public partial class Address : INotifyPropertyChanging, INotifyPropertyChanged

    {

     #region Extensibility Method Definitions

    partial void OnLoaded();

    partial void OnValidate(System.Data.Linq.ChangeAction action);

    partial void OnCreated();

    partial void OnAddressIDChanging(int value);

    partial void OnAddressIDChanged();

    partial void OnAddressLine1Changing(string value);

    partial void OnAddressLine1Changed();

    partial void OnAddressLine2Changing(string value);

    partial void OnAddressLine2Changed();

    partial void OnCityChanging(string value);

    partial void OnCityChanged();

    partial void OnStateProvinceIDChanging(int value);

    partial void OnStateProvinceIDChanged();

    partial void OnPostalCodeChanging(string value);

    partial void OnPostalCodeChanged();

    partial void OnrowguidChanging(System.Guid value);

    partial void OnrowguidChanged();

    partial void OnModifiedDateChanging(System.DateTime value);

    partial void OnModifiedDateChanged();

    #endregion

כלומר, בקובץ designer.cs שלנו יש הצהרה גם לשם של ה-Partial Method וגם קריאה עבורה.
image  – הצהרה + קריאה.

היות ואין שום מימוש ב-Partial Class אחר ל-Partial Methods האלו – ההצהרה והקריאה להן פשוט יעלמו בקומפילציה.
נפתח ב-Reflector ונביט על המחלקה המקומפלת:

image

 

image

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

והכל – כי אין להן מימוש.

עכשיו, נממש את הפונקציות האלו ב-Partial Class.

image

    partial class Address

    {

        partial void OnCityChanged()

        {

            // do something

        }

 

        partial void OnCityChanging(string value)

        {

            // do something else

        }

    }

נקמפל שוב את המחלקה שלנו, ונבדוק ב-Reflector איך היא נראית איך קומפילציה.

image

 

image

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

 

עכשיו שהבנו מה זה Partial וחמושים בידע מתאים, נחזור לשאלה המקורית. בקוד הבא OnValidate לא נקרא:  

    public partial class Product

    {

        public void OnValidate()

        {

            if ((this.Discontinued == true) && (this.UnitsOnOrder > 0))

            {

                throw new ArgumentException("Reorder level cannot be greater then 0 when you discontinue");

            }

        }

    }

דבר ראשון חשוב לשים לב שחסרה לנו מילת המפתח Partial בשביל להפוך את המתודה ל-Partial Method.

    public partial class Product

    {

        partial void OnValidate()

        {

            if ((this.Discontinued == true) && (this.UnitsOnOrder > 0))

            {

                throw new ArgumentException("Reorder level cannot be greater then 0 when you discontinue");

            }

        }

    }

 

דבר שני, נביט למשל במחלקת ה-Address שלנו על ההצהרה של OnValidate.

    partial void OnValidate(System.Data.Linq.ChangeAction action);

אנחנו יודעים שצריך שני דברים בקובץ שמצהיר Partial Method: הצהרה וקריאה.

אבל בקובץ המג'ורנט שלנו אין שום קריאה ל-OnValidate!

נביט על תמונת מסך של ה-Partial Methods:

image

אני משתמש בתוסף ל-Visual Studio 2008 בשם Resharper, שנכון לעכשיו לא תומך בצורה ב-Partial Methods וצובע הכל באטרף באדום.
נתעלם מכל האדום, ונחפש קצת אפור.

אפשר לראות ש-Resharper צובע באפור מתודות שרק מצהירים עליהן ולא קוראים להם. כלומר, OnLoaded ו-OnValidate בכלל לא נקראים בשום מקום במחלקה שלנו.

כלומר, אנחנו צריכים ב-Partial Class שלנו גם לקרוא ל-OnValidate וגם לממש אותו.

    partial class Address

    {

        partial void OnCityChanged()

        {

            OnValidate();

        }

 

        partial void OnCityChanging(string value)

        {

            // do something else

        }

 

        partial void OnValidate(System.Data.Linq.ChangeAction action)

        {

            // do validation

        }

    }

 

עכשיו קצת ביקורת.

דבר ראשון, כל הקונספט של OnValidate מיותר לחלוטין ודורש לכתוב המון חוקים עסקיים בצורה מפורשת שכוללת המון קוד.
בדיוק בשביל זה יש את Validation Application Block שבאמצעות Attributes מאפשר לדאוג לכל הוולידציה של המחלקה.

לעוד על VAB אפשר לקרוא כאן סדרת מאמרים שכתבתי עליו – http://www.justinangel.net/#Validation Application Block.

 

דבר שני, על Partial Methods בכלל.

המקום היחידי שבו כזה דבר באמת מתאים זה באמת איפה ש-Partial Classes עזר, רק שיש לך Designer שמגביל אותך מאוד במימוש.
אסור תחת שום תנאי לעבוד עם Partial Methods ככלי עבודה יום-יומי במפתחים.

הסיבה היא שיש לנו כבר כלים תכנותיים שיותר מתאימים למצבים כאלו, ספציפית abstract methods וירושה.

    public abstract class myEvening

    {

        public void Relex()

        {

            DrinkWhiskey();

        }

 

        protected abstract void DrinkWhiskey();

    }

 

    public class JustinsEvening : myEvening

    {

        protected override void DrinkWhiskey()

        {

            Justin.Drink(BlakcLabel);

        }

    }

ירושה מוכרת ומבוססת ככלי עבודה חזק ואין לו את המגבלות של Partial Methods, ולכן תמיד עדיף לעבוד איתו.

 

קישור: http://www.tapuz.co.il/tapuzforum/main/Viewmsg.asp?forum=831&msgid=110914286

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

כתיבת תגובה

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