EF TPH Inheritance

EF TPH Inheritance

מה שאני מנסה לעשות הוא דבר פשוט. מערכת ווב שיש לה משתמשים רבים, אבל הם נחלקים לקבוצות עם תפקידים שונים. תחשבו על uber: יש נהגים, יש נוסעים, יש מנהלי מערכת. כולם "משתמשים" וכולם נרשמים באותו מקום, אבל מבחינת טבלאות בדטהבייס, הם שונים לגמרי. יש להם כמה דברים משותפים: אימייל, טלפון, שם, וכן הלאה. אבל יש גם הרבה שונה. השאלה: איך לנהל את זה ב EF6 code first ?
דרך אחת: טבלת משתמשים עם נתונים בסיסיים המשותפים לכולם, ועוד טבלת נהגים, ועוד טבלת נוסעים. אפשר להשתמש ב roles כדי לדעת (ולקבוע) מי נהג ומי נוסע. זה קצת מקשה על העבודה, כי תמיד נתוני המשתמש נמצאים בשתי טבלאות שונות.
דרך שניה נקראת TPH והיא לעשות base class של משתמשים, ואז 2 או יותר קלאסים של סוגי משתמשים (נהגים, נוסעים, מה שלא יהיה) שיורשים מטבלת הבסיס. משם בערך נותנים ל EF לעשות את העבודה. התוצאה אמורה להיות טבלה אחת ב DB (בזבוז מקום, אבל ביצועים טובים), ו3 סוגי entity לרשותך בקוד המשתמש ב EF. לדוגמה יהיו לך person, driver, passenger שתוכל לעשות איתם כל פעולה. לנהג למשל יהיה מספר רישיון נהיגה (כדאי), ולנוסע - לא. בפועל כאמור ב DB כולם יושבים ביחד באותה טבלה. זו רק נוחיות למתכנת.

האם יצא לכם להשתמש בסיפור הזה? במה אתם משתמשים, הרי הסנריו הזה קורה בהמון מערכות. אולי יש דרך פשוטה יותר ואני סתם מסתבך?

לינקים למתעניין:
http://weblogs.asp.net/manavi/inher...ode-first-ctp5-part-1-table-per-hierarchy-tph

http://www.asp.net/mvc/overview/get...ntity-framework-in-an-asp-net-mvc-application
 
יש גם דרך שלישית

TPC. דהיינו, שלש טבלאות נפרדות לחלוטין. בצד הדטהבייס אין שום ירושה.
הירושה ממומשת בצד הקוד, אבל לא בצד הDB.
מבחינה מסוימת זו הדרך הפשוטה ביותר. אבל השיטה הזו נוחה מאד למי שעובד code first. אם אתה מוסיף פרופרטי לBase class, אזי EF יידע להוסיף שדה תואם גם לטבלת הנהגים וגם לטבלת המשתמשים. לעומת זאת, השיטה הזו איננה טובה למי שעובד DB FIRST.

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

וזה מעניין, חוץ מהבעיה שבכל מקום כתוב שזה פחות טוב מבחינת ביצועים.
התקדמתי היום יפה עם ה TPH, אבל יש משהו שמתחיל לעצבן אותי בכל הסיפור הזה.
א. למה זה צריך להיות מסובך? וזה מסובך. ביזבזתי על זה כמה שעות בגלל הסיפור שאצלי הטבלה הראשית שממנה יורשים (ואם אין לה את הסימון abstract אז כל העסק לא עובד), היא טבלת ApplicationUser וזה לא יכול להיות אבסרטקטי, כי יש מקומות שיוצרים ApplicationUser ואם הוא אבסטרקטי אז הם לא יכולים ואז זה לא מתקמפל... ואז אני יוצר קלאס אחר שיירש מ ApplicationUser והוא יהיה האבסטרקט... מסובך, ולפי דעתי מסובך שלא לצורך.
ב. יש בזה יותר מדי black magic. כלומר, דברים שקורים מאחורי הקלעים ואין לי שליטה עליהם או הבנה איך הם קורים. ואני, בנאדם פשוט, ורוצה תהליכים פשוטים ומובנים וניתנים לחיזוי. לכן, למרות שעקרונית זה עובד, אני שוקל לרדת מכל העניין וללכת על משהו שמבחינת הנדסת תוכנה הוא גועל נפש, אבל יעבוד מייד.
 
למה זה מסובך?

כי זה באמת דבר מסובך.
לקחת טבלה בדטהבייס ולתת לה כמה פנים בקוד, זה דבר מסובך.

ואני חושב שהפתרון הטוב ביותר הוא מה שהצעתי: TPC. זה פתרון פשוט שאין בו שום קסם. בפועל, מבחינת EF אין USERS, אלא נהגים, עובדים, ונוסעים. לכל אחד יש טבלה נפרדת לחלוטין. הירושה היא רק בצד הקוד. במקום לכתוב שלש פעמים FirstName אתה עושה זאת במחלקת הבסיס, שאיננה חייבת להיות אבסטרקטית.
אין בזה שום בעית ביצועים ורק בעיה לוגית שמסד הנתונים איננו מנורמל בצורה מהודקת. מי שיכעס עליך, תגיד לי שזה מימוש של NOSQL.
 
אגב:

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

אגב #2, אני מציע לך לא לקרוא אף מאמר על EF, מלבד המאמרים הטובים של Microsoft. אני אבדתי הרבה זמן בגלל מאמרים לא אקטואליים, ונדמה לי שבכלל זה גם מאמרים מהאתר שקישרת אליו.
 

tenen

New member
לדעתי אתה מפספס את החלק הנח בEF

המטרה היא שתכתוב קוד C# כמו שנח לך לכתוב והEF כבר יתעסק עם לתרגם את זה למה שלא יהיה ולך לא אמור להיות איכפת.
 
באמת אני מחפש את החלק הנוח כבר הרבה זמן

ולא מוצא אותו. יכול להיות שאין?
 

nocgod

New member
אני חושב שאתה לא מוצא אותו כי אתה מכניס את עצמך לפינות

נניח - למה אתה צריך TPH? תמדל את זה כTPC, יהיו עמודות משוכפלות בין כמה וכמה טבלאות אבל לא יהיה שכפול מידע.
מה שכן זה יסבך אותך בצד התכנותי אם לצורך העיניין אותה הישות יכולה ללבוש כמה כובעים (לצורך העיניין נהג ומנהל) אבל את זה אפשר לפתור באמצעות decorators או באמצעות composition של כמה ישויות כמו שהיית עושה כדי לפתור בעיה של multiple inheritence כשאי אפשר לעשות אותו.

בגדול EF הוא מאוד straight forward כשאתה עובד by conventions, ברגע שאתה מנסה לעשות איתו משהו שהוא לא בדיוק מותאם לו, אתה תסתבך. זה הניסיון שלי איתו.
 
בוא נניח רגע ל TPC,

אני אכתוב עליו הודעה נפרדת ואשאל כמה שאלות עליו, אולי באמת הוא פתרון טוב.

מה דעתכם על הפתרון הזה (תמונה מצורפת) ?
טבלת משתמשים רגילה של aspnetusers כך שיש לי את כל ההרשמה, סיסמאות, וכל מה שהולך עם זה, פלוס כמה שדות שמשותפים לכל בעלי התפקידים. לכל יוזר יש ID, שהוא בגרסה הנוכחית מחרוזת ולא מספר. שיהיה.
לכל תפקיד יש טבלה נפרדת. בדוגמה שלנו, טבלת נהגים, עם ID מחרוזת שמקושרת לטבלת היוזרים, אחד לאחד. אותו ID של היוזר עם השם משה כהן, הוא ה ID של הנהג משה כהן, עם מספר רישיון הנהיגה שלו וצבע האוטו. וכן הלאה לתפקידים האחרים במערכת.
יש גם שימוש סטנדרטי בטבלאות roles, userRoles כדי שאפשר יהיה להשתמש בדקורציות ולאפשר גישה לאזורים מסוימים רק לבעלי תפקידים.
עכשיו בצד הקוד: כשאני עובד עם טבלת Drivers, לפעמים אני צריך נתונים מטבלת האחות users שהיא עכשיו טבלה מקושרת. אז אני אכתוב Driver.User.Email ויש לי את המייל שלו.
מה אתם אומרים על זה? טכנית זה יעבוד לדעתי. יעילות? נוחות בכתיבה? כסחו חופשי, אני סתם זורק רעיונות.

 
נפלא

נתמצת את הרעיון בשתי מילים: בלי ירושה.

הסרבול היחיד הוא כשמוסיפים משתמש חדש. במקום להוסיף לטבלה אחת, אתה צריך להוסיף לטבלת האב, ואחר כך לטבלה המתאימה.
 
אני חושב שהבנתי את TPC


אני קצת חמור קשה הבנה. הרפרנס/הסבר היחיד שמצאתי היה זה:
https://msdn.microsoft.com/en-us/data/jj591617#2.6
&nbsp
והחלטתי שלפני שאני פוסל את השיטה, אני אקרא את הדף הזה 10 פעמים מלמעלה למטה וההיפך, עד שאבין על מה מדובר. ובסוף הבנתי! צריך לרדת למטה לחלק שנקרא Models used in sample ואז הדברים נהיים ברורים.
&nbsp
והתוצאה היא בעצם בדיוק כמו בציור שלי, עם שיפור אחד משמעותי: בקלאס Drivers (כשאתה כותב את הקונטרולרים שלך) יש את כל השדות של טבלת האב: email, id, phone וכמובן גם את השדות היחודיים של הטבלה. בפועל, קורה בדיוק מה שחשבתי לעשות ידנית: קישור אחד לאחד בין טבלת האב (יוזרים של דוט נט) לטבלאות הבנות העצמאיות שלי (נהגים, נוסעים, וכו) .
נראה טוב. אושר גדול! תודה רבה לכל העוזרים!
&nbsp
 

nocgod

New member
אל תשכח לחשוב על הבעיה שבה entity אחד לובש כמה כובעים

נגיד נהג הוא גם מנהל וכד'
 
לעניות דעתי

אם אתה משתמש בTPC, ז.א. שיש לך שתי טבלאות Users.
במקרה שלך, זה לא רעיון טוב, כי לטבלת Usesr יש קשרים נוספים, למשל עם טבלת UserRoles.

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

אז מה שקורה אצלי הוא בעצם TPT. כי דרישת הבסיס שלי, האילוץ שנובע מהשימוש במערכת רישום המשתמשים של דוט נט, הוא שטבלת האב AspNetUsers תמשיך להתקיים. אחרת כל המנגנון "שלהם" לא יעבוד. יש לי שתי טבלאות בנות והן יורשות מטבלת האב. בקוד יש לי 3 קלאסים, האב ושני הבנים. כל בן מכיל את השדות שלו פלוס שדות מטבלת האב. ירושה לכל דבר. בדטהבייס יש 3 טבלאות: AspNetUsers, Driver, Passenger .
&nbsp
 
לכאורה

יש לך שלש אפשרויות:
1. TPT
2. TPH [זה מתאים רק אם המשותף לנהגים ולמנהלים רב על המפריד ביניהם]
3. TPT אבל בלי ירושה, כפי שהצעת לעיל. לשדות המשותפים אתה ניגש דרך Driver.User.Email
 
לתועלת הקוראים

הנה סיכום פשוט בשיטת 1-2-3.
נניח שיש לי מחלקת בסיס ושתי מחלקות יורשות:
public abstract class MyBaseClass{
public int Id {get; set; }
public string Name {get; set; }}public class MyClassA : MyBaseClass{ public int PropA {get; set;}}public class MyClassB : MyBaseClass{ public string PropB {get; set;}}
איך למפות את זה לDB? שלש שיטות:

  1. טבלה אחת לכל ההיררכיה. לטבלה הזו תהיה עמודה PropA בשביל מופעים של ClassA. העמודה הזו תהיה Null במופעי ClassB. וכן הדבר גם בעמודה PropB שיהיה בה ערך או תהיה Null לפי הענין.
  2. שתי טבלאות. אחת לClassA ואחת לClassB. לBaseClass אין משמעות מבחינת הדטהבייס. וזה נקרא Table Per Concrete.
  3. שלש טבלאות. אחת לBaseClass, אחת לClassA ואחת לClassB. וזה נקרא Table Per Type.
 
יש בעקרון 2 תשובות לשאלה שלך.

1. *אם* אתה עובד עם EF code first *וגם* מעוניין למדל ירושה (או הרכבה, או משהו אחר), *אז* אתה צריך להגדיר את זה ב OnModelCreating

2. *אם* אכפת לך מדאטה *אז* אל תעבוד עם code first.
תחשוב data. לא ישויות, לא ירושות, ולא קישקושים.
כן זה אומר שיהיה לך קצת (הרבה) entity splitting.

נ.ב
*אם* אתה משתמש ב-db רק לצורך persistent serialization *אז* תתעלם מסעיף [2] שאינו מיועד למקרה שלך *וגם* תעשה מה שיותר קל לעבוד איתו מצד הקוד - לדעתי זו TPT כפי שציין מר צור לעיל (supertype/subtype).
 
למעלה