entity framework

Serializing Entity Framework Things

There are two flavors of serialization you can use on Framework entities:  good ol' XML serialization and the DataContract serialization.

XML serialization is pretty straightforward - it serializes the object it's given.  But it does not walk references (I'm talking foreign keys here) or collections.  whereas the DataContract serializer does handle the references and keeps track of what objects have been serialized and what hasn't.

If you're going to use XSLT on the serialization, I'm not sure at this point which one is better - they're probably equal but time will tell.

Unfortunately you can't directly serialize the entire ObjectContext, only collections or individual objects can be serialized.  But this isn't as bad as it sounds as usually you'll want to start at some 'master object' in a collection and serialize it and all it references, so the inability to directly serialize the Context is not a big loss.

So how do you do it?

Assume we have a Context named RecipeModel which holds a bunch of recipes and related items (ingredients, techniques, etc.) and we want to serialize a specific recipe and all the ingredient info and technique info that it references. 

First find the Recipe:

  Recipe recipe = RecipeData.Recipes.Find( r => r.Name == targetName );

Then setup the serializer:

  using ( FileStream fs = File.OpenWrite( "RecipeData.xml" ) ) {
      XmlWriter writer = XmlWriter.Create( f2 );
      XmlSerializer srlz = new XmlSerializer( typeof( Recipe ) );
      srlz.Serialize( writer, recipe );
      writer.Close(); // ensure it's properly flushed
  }

That's it. Recipe and all its data are now in the file. But the foreign key references to things like ingredients and techniques aren't there.  If you want to include them you'll need to provide a wrapper which references them directly.  Ugly if you have a lot of different scenarios you need to address.  But for one or two it may not be too much work.

On the other hand, you can use the DataContractSerializer - it handles the foreign key stuff nearly magically.  Try this:

  using ( FileStream fs = File.OpenWrite( "RecipeData.xml" ) ) {
      XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter( fs, Encoding.UTF8 );
      DataContractSerializer srlz = new DataContractSerializer( typeof( Recipe ) );
      srlz.Serialize( writer, recipe );
      writer.Close(); // ensure it's properly flushed
  }

That's it. The Recipe and all data it references via keys are collected into a single serialization.

But of course there's a caveat or two.  What if the referenced objects/collections aren't loaded?  Does this somehow load them prior to serialization?  Nope - if the referenced objects aren't loaded then all you'll get serialized is the keys that refer to them.   If you want the data then make sure it's loaded.

  recipe.Ingredients.Load();
  recipe.Techniques.Load();

Or some such.  Of course there is a better way - using a LinQ query to eager load the references (and isn't there normally a query involved in all this anyways?):

  srlz.Serialize( writer,
    ( from rcp in model.Recipes.Include( "Ingredients" ).Include( "Techniques" )
      where rcp.Name = "Bordelaise Sauce"
      select rcp ).First() 
  );      

Two things: using the "Include()" forces eager loading of the entire referenced graph (whih in this case is probably what's needed) and second, notice the First() method on the query - this forces the query to execute and returns a single object rather than the query object itself (you can't serialize an ObjectQuery).

Obvioulsy if you're needing to deserialize back to objects, the DataContract method is the way to go but if you're needing to translate your database data to something else (which is what I need to do) then XML/XSLT is worth a look and the above serializers will get half the job done with virtually no pain.

XSLT on the other hand.....

EntityObject & Custom Attributes - A Thought

As slick as the EF is, there are limitations (one being my refusal to take 3 days to read MS's disjoint documentation).

A case in point:  An EntityObject classis defined as partial so one may add necessary properties, methods, attributes, etc. at will.  But the generated properties (database column accessors) do not allow for custom attributes.  They're not partial and C# doesn't allow for partial properties anyway.  

So how to add things like DisplayName or DefaultValue or something more esotaric like UnitType(Unit.Meter)?

Searching high and low renders nothing except some obscure references to System.ComponentModel.DataAnnotations.MetadataType(type).  So far I've found nothing definitive - the discussions seem related to the ASP end of things rather than the Forms.

Here, here and here.

It seems that the meta data can be extended via an associated class (MetadataType) but only if TypeDescriptor is used to fetch the metadata.  There may be no general solution to this issue but maybe an application specific solution can be created.

  • Create a custom reflection method that
    • Uses the MetadataType attribute
    • Combines the referenced attributes into the normal custom attributes (using standard reflection)

On the other hand, if I'm forcing the use of a special method to gather attributes, why not just use the TypeDescriptor approach?

More later....

Adding Entities

Once a BindingSource is hooked to a LinQ EF query, editing/deletion become trivial.  But how about adding new objects?

My first attempt, it turns out, was just backwards:  I added a new object to the EntitySet and then added that new object to the BindingSource.  While this worked, it causes havoc in the ObjectStateManager - key inconsistencies, etc.

Turns out the pattern is this:

Add a new object via the BindingSource:

User u = (User)UserVarBindingSource.AddNew();

That's it.  The new object is created and added to the underlying list (in this case the EntitySet) and the BindingSource will position itself (and all bound Controls) to the new records.

But that's not quite the end of the story - the new object probably requires some type of initialization and if that's explicitly done in a default constructor then it needs to be done as a result of the add operation.

We make use of the BindingSource's AddingNew event to set a flag indicating that a new object is added:

private void UerBindingSource_AddingNew( object sender, AddingNewEventArgs e )
{
    m_adding = true;
}

Then utilizing the CurrentChanged event (after the object is added the BindingSource sets it as the current object, triggering this event) the new object is initialized:

private void UserBindingSource_CurrentChanged( object sender, EventArgs e )
{
    // adding new object flag set in AddingNew event handler
    if ( ! m_adding )
        return;
    // reset the flag so we don't modify every object viewed
    m_adding = false;

    //
    // fetch the new object and dress it up
    Model.User usr = (Model.User)UserBindingSource.Current;
    sv.name = "NoName";

    ....

    // The object is stil in the 'edit' state which means the changes will not 
    // be propogated to the underlying layer - end the edit so the object is "live".
    sysVarBindingSource.EndEdit();
}

The call to EndEdit is important - either at this point or prior to saving the data:  if EndEdit isn't called, the BindingSource does not propogate changes to the underlying data store (hmmmmmm....  undo anyone?).

Syndicate content