DCSIMG
August 2008 - Posts - Ido Flatow's Blog Veni Vidi Scripsi

Ido Flatow's Blog

Veni Vidi Scripsi

News

Have you heard me speak?
Powered
<style type='text/css' media='screen' id='sm_css'> #smix {overflow: visible;height: auto;border-radius: 10px;max-width: 250px;background-color: #323232;text-align: left;font-size: 12px;line-height: 16px;font-family:'Lucida Sans Unicode','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;-webkit-border-radius: 10px;-moz-border-radius: 10px;border-radius: 10px;} #smix a {color: #0056CC;text-decoration: none;} #smix .sm_head {color: #fff; line-height: 1em;font-size: 1.4em;padding: 10px;color: #fff;} #smix .sm_lanyard_wrapper {background-color: #fff;;clear: both;width: 97%;margin: 0 auto;margin-bottom: 0px;} #smix .sm_lanyard_content {padding: 7px;}#smix button.sm_rec, #smix a.sm_rec, #smix input[type=submit].sm_rec { padding: 6px 10px; -webkit-border-radius: 2px 2px;-moz-border-radius: 2px; border-radius: 2px; border: solid 1px rgb(153, 153, 153); background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(255, 255, 255)), to(rgb(221, 221, 221))); color: #333; text-decoration: none; cursor: pointer; display: inline-block; text-align: center; text-shadow: 0px 1px 1px rgba(255,255,255,1); line-height: 1; }#smix .sm_rec:hover { background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(248, 248, 248)), to(rgb(221, 221, 221))); }#smix .sm_rec:active { background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(204, 204, 204)), to(rgb(221, 221, 221))); }#smix .sm_rec.medium { padding: 3px 7px; font-size: 13px; }#smix .sm_rec span.icon.thumbs_up {background-position: 0px 36px;vertical-align: text-top;display: inline-block;margin-right: 4px;height: 18px;width: 16px;background-image: url(http://speakermix.com/images/new/thumbsold.png);}#smix .sm_rec:hover span.icon.thumbs_up {background-position: 0px 18px;} #smix .sm_events {padding:2px 0px 4px 0px;} #smix .sm_section {font-size: 10px; border-bottom: 1px solid silver; margin-bottom: 6px;} #smix .sm_subline {font-size:120%;margin-top:4px;font-weight:bold} #smix .powered {text-align: right} #smix .powered img {margin: 7px} </style>
Sela Technology Center

Advertisement

August 2008 - Posts

Change Entity Framework storage DB schema in runtime

When you build a EF schema, it automatically plant the name of the imported tables schema into the EDMX file. But alas, what happens when you work in different environments (such as Dev, Test, Prod...) and in each database, the schema of the tables is different?

Basically you either reset all your databases to use the same schema or worse - change the EDMX and recompile the assembly before updating the wanted environment.

But there is another solution, not a clear one, probably not a supported one, but still, it works.

Every EF model is accompanied by metadata, accessible from the MetadataWorkspace property of the model. This metadata class holds the collections of the conceptual, store and mapping items, and is loaded in sections: first of all the conceptual model is loaded, and before the first query is executed, the store and mapping model loads.

The idea is to interrupt to load process and change the schema name before any query is executed.

Look at this code:

   1: Employees e = new Employees();
   2:  
   3: XmlReader[] sReaders = new XmlReader[]{ XmlReader.Create(
   4:     Assembly.GetExecutingAssembly().GetManifestResourceStream("Employees.ssdl"))};
   5:  
   6: XmlReader[] mReaders = new XmlReader[]{ XmlReader.Create(
   7:     Assembly.GetExecutingAssembly().GetManifestResourceStream("Employees.msl"))};
   8:  
   9: StoreItemCollection sCollection = new StoreItemCollection(sReaders);
  10: EdmItemCollection cCollection = e.MetadataWorkspace.GetItemCollection(
  11:     DataSpace.CSpace) as EdmItemCollection;
  12:  
  13: StorageMappingItemCollection csCollection = 
  14:     new StorageMappingItemCollection(cCollection, sCollection, mReaders);
  15:  
  16: e.MetadataWorkspace.RegisterItemCollection(sCollection);
  17: e.MetadataWorkspace.RegisterItemCollection(csCollection);
  18:  
  19: EntityContainer container = 
  20:     e.MetadataWorkspace.GetItem<EntityContainer>(
  21:         "TestModelStoreContainer", 
  22:         DataSpace.SSpace);            
  23:                     
  24: EntitySetBase set = container.BaseEntitySets["Person"];
  25:  
  26: typeof(EntitySetBase).GetField(
  27:     "_schema",
  28:     BindingFlags.NonPublic | BindingFlags.Instance).SetValue(set, "dbo");
  29:           
  30: var q = from p in e.Person
  31:         select p;
  32:  
  33: Console.WriteLine(e.Person.ToTraceString());
  34: Console.WriteLine(q.First().Age);

Let's inspect the code:

Lines 3-7: We load the conceptual and storage xml from the resources of the containing assembly (if the code is run in another assembly, change the code to return the assembly that holds the model).

Lines 16-17: We must load both new parts to the MetadataWorkspace, otherwise we'll get an exception when trying to execute a query.

Lines 20-24: Extract an entity set definition from the store collection (if all tables are needed to be changed, you may want to use a foreach statement on the BaseEntitySets collection).

Lines 26-29: Here's the catch - in order to change the schema, we need to reset the value of a private field (the access property is an inline one). So we use wonderful reflection that allows us to do it (full trust required).

From there on, every time a query is built, it will use the wanted schema.

Note:

  1. Using this approach you can also change other stuff, like table names, defining queries and so on.
  2. There is a simpler way to load the store model (instead of writing rows 3-22), explained by Julie Lerman here, but it will work for examining the collection, not for changing it - the use of ToTraceString on an entity will cause the query to be cached, so future queries on the same entity will use the old schema name instead of the new. Loading the store collection first and then creating a query will use the new schema name.

 

 

עוד קצת AJAX לא הזיק לאף אחד

כפי שרשמתי בפוסט קודם, ב-15/9 אני אעביר הרצאה בנושא AJAX ו-Entity Framework.

ההרצאה על AJAX תהיה ברמת סקירה של הטכנולוגיה והיכולות שלה, אך למי שמעוניין בהרצאה קצת יותר מעמיקה (רמת Deep Dive), אני אעביר הרצאה שכזו בכנס המפתחים של סלע, אשר יתקיים בין התאריכים 22-25 לספטמבר (ההרצאה שלי תהיה ב-22 לספטמבר).

ההרצאה תעסוק בסקירה מעמיקה של איך טכנולוגיית AJAX עובדת (מי ש-Javascript עושה לו בחילה מומלץ שיבוא עם כדורי פראמין ושקית הקאה) והצגה של אפשרויות ההרחבה לרכיבים הקיימים והאופן בו יוצרים רכיבים חדשים בסביבה.

clip_image001[1]

Entity Framework and Lazy Loading

I've received a couple of request to write some of my previous posts in English so that all other 99.7% of the developers community in the world which finds these subjects interesting can understand what I'm writing.

The following post is a translation to English of this Hebrew post.

When Entity Framework (EF for short) was designed, Microsoft decided the loading of entities will be during run-time in a JIT like mechanism. They achieved this by using the lazy loading technique - access the database and load the entity for the first time only when someone asks for it.

There is some logic to this design - the wish to spare unnecessary work with the database. Still, there are some disadvantages to this design:

  1. In order to perform the lazy load, one must invoke the Load method of the RelatedEnd object (either EntityCollection or EntityReference) explicitly.
    This means that you must remember to put a load command before accessing the navigation property, otherwise you will get a NullReferenceException or worse - A bug in your application's logic
  2. Every call to Load causes a call to the DB, so you must remember while code reviewing to check that prior to each Load operation you have a "If (!navig.IsLoaded)" condition (the IsLoaded is a property of the RelatedEnd object).
  3. In case you've built a code that iterates on a collection of entities and performs some operation on each of them, and that entity contains navigable properties, you will find yourself calling Load on each navigation property, for each entity in the collection, which means your DB will be accessed total of EntityCount X NavigablePropertiesCount - not very wise performance wise.

The first and second section is quite annoying to address, because you have to repeat these 2 rows each time you want to navigate a property. There is a solution for this problem by using Transparent Lazy Loading, but it's not part of EF yet (the guys at MS says that they are considering it for V2).

As for the third section, there is a solution in EF for the iterated load, by using the Include method which you can call from your ObjectQuery (for each EntityType you want to query). You can pass to the method a string parameter that holds the name of the navigated property we want to pre-load, and we can even call this method a couple of time, with different navigable properties to pre-load a couple of entities.

What goes on in the DB when you activate the Include method? the query which is executed in the DB contains not only the "select" query for the main entity but also other queries to return the navigable ends. For example, given the following model:

And the following DB table structure:

And given a code that looks like this:

TestModel.TestEntities model = new TestModel.TestEntities();

var all = from a in model.Person.Include("Pets").Include("Address")
          select a;

foreach (var person in all.ToList())
{
    if (!person.Pets.IsLoaded)
        person.Pets.Load();
    if (!person.Address.IsLoaded)
        person.Address.Load();
    
    Console.WriteLine(
        string.Format ("{0} {1}\n{2}\n{3}",
            person.FirstName, 
            person.LastName,
            String.Join("\n", (from ad in person.Address
             select string.Format("{0} {1} {2}", ad.City, ad.Street, ad.House)).ToArray()),
             String.Join("\n", (from p in person.Pets
             select string.Format("{0} {1}", p.Name, p.Species)).ToArray())));
    Console.WriteLine();
}
 
When the ToList method will be invoked, the following query will be executed:
 
SELECT [UnionAll1].[Id] AS [C1], 
[UnionAll1].[FirstName] AS [C2], 
[UnionAll1].[LastName] AS [C3], 
[UnionAll1].[C2] AS [C4], 
[UnionAll1].[C1] AS [C5], [UnionAll1].[Id1] AS [C6], 
[UnionAll1].[Name] AS [C7], 
[UnionAll1].[Species] AS [C8], 
[UnionAll1].[PersonId] AS [C9], 
[UnionAll1].[C3] AS [C10], 
[UnionAll1].[C4] AS [C11], 
[UnionAll1].[C5] AS [C12], 
[UnionAll1].[C6] AS [C13], 
[UnionAll1].[C7] AS [C14]
FROM  (SELECT 
    CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], 
    [Extent1].[Id] AS [Id], 
    [Extent1].[FirstName] AS [FirstName], 
    [Extent1].[LastName] AS [LastName], 
    1 AS [C2], 
    [Extent2].[Id] AS [Id1], 
    [Extent2].[Name] AS [Name], 
    [Extent2].[Species] AS [Species], 
    [Extent2].[PersonId] AS [PersonId], 
    CAST(NULL AS int) AS [C3], 
    CAST(NULL AS varchar(1)) AS [C4], 
    CAST(NULL AS varchar(1)) AS [C5], 
    CAST(NULL AS varchar(1)) AS [C6], 
    CAST(NULL AS int) AS [C7]
    FROM  [dbo].[Person] AS [Extent1]
    LEFT OUTER JOIN [dbo].[Pets] AS [Extent2] ON [Extent1].[Id] = [Extent2].[PersonId]
UNION ALL
    SELECT 
    2 AS [C1], 
    [Extent3].[Id] AS [Id], 
    [Extent3].[FirstName] AS [FirstName], 
    [Extent3].[LastName] AS [LastName], 
    1 AS [C2], 
    CAST(NULL AS int) AS [C3], 
    CAST(NULL AS varchar(1)) AS [C4], 
    CAST(NULL AS varchar(1)) AS [C5], 
    CAST(NULL AS int) AS [C6], 
    [Extent4].[Id] AS [Id1], 
    [Extent4].[City] AS [City], 
    [Extent4].[Street] AS [Street], 
    [Extent4].[House] AS [House], 
    [Extent4].[PersonId] AS [PersonId]
    FROM  [dbo].[Person] AS [Extent3]
    INNER JOIN [dbo].[Address] AS [Extent4] 
    ON [Extent3].[Id] = [Extent4].[PersonId]) AS [UnionAll1]
 
Long isn't it? but for this table structure, the query is quite efficient.
 
Warning: beware of the number of includes you use and the number of levels you "walk" into your navigation tree - the more include methods you call, the bigger your query will become and the more inefficient it will become (have you ever tried to execute a left join on 4 large tables? not a pretty sight.
 
Another thing you should take into account - the include method tries to identify the navigated property and it's mapping (in order to build the union part of the query) prior to executing the query, which means that if your entity is polymorphic (for example you want to load an EntitySet that holds both Person and Employee), you can only use the include method for the navigated properties of the base class (say Person), which means we don't really have the option to perform an eager load - an implicit load of the entire entity structure, including all navigable properties, ours or our fathers (derived).
 
If you do wish to load a polymorphic entity, you'll need to perform a couple of Include execution, every time with a different entity type from the hierarchy tree (and combine the results).
 
But it doesn't end there - what happens if the both the entity and one of the navigated entity are of some base class (for example a Person that holds Cars and Cars is a polymorphic type)? you see where this is heading...
 
So for conclusion, lazy load is a bit problematic, we can use Include, but we have problems with it too. Eager loading isn't yet supported but that too isn't so simple to use - you'll just have to find the suitable solution for your situation.
 

מה משותף ל-Entity Framework ו-AJAX ?

בכללית, שתיהן טכנולוגיות שהן "תוספות" לתשתית המוכרת - Ado.Net Entity Framework ו-Asp.Net AJAX.

בפרט - כל אחת מהטכנולוגיות באו לענות על צורך שהיה חסר: EF באה לענות על מחסור בכלי ORM מיקרוסופטי (לאלו שאינם נוטים להשתמש בכלים צד שלישי) ו-AJAX בא לפתור בעיה חמורה שהיתה בביצועים של אפליקציות ASP.NET.

המצחיק הוא, אגב, ששתי טכנולוגיות אלו לא ממש עובדות טוב ביחד.

ב-15/9 אני אעביר הרצאה על כל אחת מהטכנולוגיות בפורום מפתחי דוטנט צה"ל

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

Serializing Expression Trees

אחד מהדברים השימושיים ב-3.0 #C, אע"פ שהוא מעט הוזנח במצגות השונות, הוא עץ הביטויים (Expression Tree) שהוא הדבק שמחבר את Lambda Expressions ו-Linq.

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

אבל... בדבר אחד Expression Tree נכשל - אי אפשר לבצע לו סריאליזציה.

הדבר מוסבר בפורומים השונים, כדוגמת כאן, והסיבות נשמעות די הגיוניות:

  1. המחלקות השונות של Expression Tree מכילות מאפיינים מטיפוסי Reflection (מחלקות Type, MemberInfo וכדומה) - טיפוסים אלו בעייתיים לסריאליזציה.
  2. Expression Tree הוא Immutable, היינו מרגע שנוצרו לא ניתן לשנותן. מה זה מרמז על מבנה המחלקה? צדקתם, אין להם Default Ctor. ומה קורה למחלקה שאין לה Default Ctor? שוב נכון, אין אפשר לעשות לה DeSerialize !!

אלו 2 בעיות רציניות שמאוד מקשות על האפשרות לבצע סריאליזציה ל-Expression Tree באמצעים "מובנים" של ה-Framework (כדוגמת XmlSerializer או DataContractSerializer).

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

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

שמות Properties כמחרוזות - חלק 2

להלן בעיה - בהנתן Property כלשהו, נניח ששמו FirstName, עליכם להעביר למתודה את שם ה-Property כמחרוזת, תחת המגבלות הבאות:

  1. אינכם יכולים לרשום במפורש את שם ה-Property כמחרוזת ("FirstName")
  2. אינכם יכולים להשתמש ב-GetProperties, איכשהו לאתר את ה-PropertyInfo ולחלץ את שמו ממאפיין Name (לדוגמה - איתור ה-PropertyInfo הראשון שמתחיל ב-F ואורך שמו הוא 9)

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

פתרון לבעיה לעיל לא מצאתי והתשובה שקיבלתי מאנשי מיקרוסופט היתה - צור מאפיין מחרוזתי שיכיל את שם המאפיין שאותו אתה רוצה לחלץ ותשתמש בו:

public string Name { get; set; }
public string NameDescriptor
   { get { return "Name"; } }

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

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

כשתתעייפו, תרדו למטה ...

.

.

.

.

.

.

.

.

.

.

.

.

.

הפתרון - שימוש ב-Expression Tree וב-Lambda Expression:

Expression<Func<SomeClass, string>> exp = (obj=>obj.SomeProperty);
string propertyName = (exp.Body as MemberExpression).Member.Name;

שמות Properties כמחרוזות

ברצוננו לכתוב מתודה אשר תקבל שם קובץ חדש ורשימת קבצים בתיקייה ותחזיר שם קובץ תקין שאינו קיים בתיקייה (כמו שיצירת קובץ חדש בתיקייה תחת וינדוס יוצרת קבצים בשם "new", "new1", "new2" וכו').

string GetUniqueFileName(List<File> files, string newFileName)

נוכל לכתוב מתודה אשר עוברת על רשימת הקבצים בתיקייה (List כלשהו) ומחפשת עפ"י מאפיין שם קובץ (obj.FileName) אם השם תפוס ואם כן, להוסיף לשם שהועבר איזשהו Suffix ולנסות שוב עד למציאת שם תקין.

עכשיו בואו נסבך - נניח שאנחנו רוצים עוד מתודה שכזו, הפעם על רשימה אחרת והשוואה מול מאפיין בשם אחר. חלקנו יכתוב עוד מתודה (copy&paste+שינויים קטנים), אבל יש דרך אחרת - הפיכת המתודה לגנרית (על הטיפוס המוכל ב-List) והעברת שם ה-Property כמחרוזת.

string GetUniqueString<T>(List<T> items, string newItemValue, string comparedPropertyName)

במתודה החדשה נבצע איזשהי איטרציה (foreach / linq) ואת ההשוואה נעשה באמצעות Reflection על-מנת לקבל את הערך מאחורי ה-Property.

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

פתרון אחר אפשרי, למקרה שבו שמות המאפיינים ידועים בזמן הפיתוח (מתאים למקרה בו מבצעים Code Reuse כמו בדוגמה למעלה) הינו להשתמש ב-lambda expressions:

string GetUniqueString<T>(List<T> items, string newItemValue, Func<T, string> comparedProperty)

בקוד המתודה נשתמש ב-First על-מנת למצוא אם קיימים איברים כאלו ברשימה

if (items.First(i=>(comparedProperty(i) == newItemValue)) != null) ...

קריאה למתודה שכזו יכולה להראות כך:

string newFileName = GetUniqueString<File>(files, "new file", (f=>f.Name));

כך נוכל להבטיח ששם ה-Property הוא Strongly Types וכל שינוי במבנה המחלקה תגרום לשגיאת קומפילציה (בין אם שינוי שם המאפיין, מחיקתו ואפילו שינוי הטיפוס שלו).

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

פתרון זה גם יכול להכתב באמצעות delegate שהרי lambdas הם מימוש של delegate, ולכן גם ניתן לתת פתרון לבעיה זו ב-2.0 net.