Saturday, August 16, 2008

Entity Framework - Key Extension Methods

We created the following extension methods for entity framework to avoid code bloat in consuming applications when working with these key types. I hope you find them useful if you are working with the Entity Framework.

 

EntityObject

/// <summary>
/// Extension methods for EntityObject
/// </summary>
public static class EntityObjectExtensions
{

    /// <summary>
    /// Gets the original value for a modified entity object's property
    /// </summary>
    /// <returns>the value before the property was modified</returns>
    public static T GetOriginalValue<T>(this EntityObject entityObject, string propertyName)
    {
        if (entityObject == null)
            return default(T);
        if (entityObject.EntityState == EntityState.Modified)
        {
            ObjectContext context = [YOUR OBJECT CONTEXT]
            ObjectStateEntry stateEntry = null;
            context.ObjectStateManager.TryGetObjectStateEntry(entityObject, out stateEntry);

            if (stateEntry != null)
                return (T)stateEntry.OriginalValues.GetValue(stateEntry.OriginalValues.GetOrdinal(propertyName));

        }

        // return the value of the property
        return (T)entityObject.GetType().GetProperty(propertyName).GetValue(entityObject, null);
    }

EntityCollection<T>

/// <summary>
   /// Extension methods for EntityCollection
   /// </summary>
   public static class EntityCollectionExtensions
   {
       /// <summary>
       /// Loads the entity collection if it hasn't already been loaded
       /// </summary>
       /// <typeparam name="T">Type of entity collection</typeparam>
       /// <param name="entityCollection">Entity collection to potentially load entities into</param>
       /// <param name="entitySource">The source entity which has the entity collection relationship (modified or unchanged only)</param>
       public static void EnsureLoaded<T>(this EntityCollection<T> entityCollection, EntityObject entitySource) where T : class, IEntityWithRelationships
       {
           if (entitySource != null && entityCollection != null && !entityCollection.IsLoaded )
           {
               if (entitySource.EntityState == System.Data.EntityState.Modified || entitySource.EntityState == System.Data.EntityState.Unchanged)
               {
                   entityCollection.Load();
               }
           }
       }

       /// <summary>
       /// Returns the collection as a queryable type
       /// </summary>
       /// <typeparam name="T">Type of entity collection</typeparam>
       /// <param name="entityCollection">Entity collection to return as a queryable object</param>
       /// <param name="ensureLoaded">Flag to determine if to load the collection if it has not been done so already</param>
       /// <returns>Queryable object for the in memory collection</returns>
       public static IQueryable<T> AsQueryable<T>(this EntityCollection<T> entityCollection, bool ensureLoaded, EntityObject entitySource) where T : class, IEntityWithRelationships
       {
           if (ensureLoaded)
               EnsureLoaded(entityCollection, entitySource);
           return entityCollection.AsQueryable();
       }

 

EntityReference<T>

 

/// <summary>
   /// Extension methods for EntityReference
   /// </summary>
   public static class EntityReferenceExtensions
   {

       /// <summary>
       /// Loads the entity reference or its value if it hasn't already been loaded.
       /// </summary>
       /// <typeparam name="T">Type of entity reference</typeparam>
       /// <param name="entitySource">The source entity which has the entity reference relationship (added, modified or unchanged only)</param>
       public static void EnsureLoaded<T>(this EntityReference<T> entityReference, EntityObject entitySource) where T : class, IEntityWithRelationships
       {
           if (entitySource != null && entityReference != null && !entityReference.IsLoaded && entityReference.EntityKey != null)
           {
               if (entitySource.EntityState == System.Data.EntityState.Added) // add the value directly as load will throw
               {
                   if (entityReference.Value == null)
                       entityReference.Value = LoadByKey<T>(entityReference.EntityKey);
               }
               else if (entitySource.EntityState == System.Data.EntityState.Modified || entitySource.EntityState == System.Data.EntityState.Unchanged)
               {
                   entityReference.Load();
               }
           }
       }

privateT LoadByKey<T>(object entityKey)
{
    if (entityKey == null)
        throw new ArgumentNullException("Supplied entity key is null, unable to load entity", "entityKey");
    // make sure the object is loaded in the object context
    ObjectContext objectContext = [YOUR OBJECT CONTEXT];
    EntityKey key = (EntityKey)entityKey;
    ObjectStateEntry entry;
    if (!objectContext.ObjectStateManager.TryGetObjectStateEntry(entityKey, out entry) || entry.Entity == null)
    {
        return (T)objectContext.GetObjectByKey(key);
    }
    return (T)entry.Entity;
}

       /// <summary>
       /// Whether or not the entity reference has an entity key with a value present
       /// </summary>
       public static bool HasEntityKeyFirstValue<T>(this EntityReference<T> entityReference) where T : class, IEntityWithRelationships
       {
           return entityReference != null && entityReference.EntityKey.HasFirstValue<int>();
       }

       /// <summary>
       /// Get entity key with a value present
       /// </summary>
       public static int GetEntityKeyFirstValue<T>(this EntityReference<T> entityReference) where T : class, IEntityWithRelationships
       {
           if (entityReference != null)
               return entityReference.EntityKey.GetFirstValue<int>();
           return 0;
       }

 

EntityKey

 

/// <summary>
   /// Extension methods for EntityKey
   /// </summary>
   public static class EntityKeyExtensions
   {

       /// <summary>
       /// Gets the first entity key value
       /// </summary>
       /// <returns>the first entity key value</returns>
       public static T GetFirstValue<T>(this EntityKey entityKey)
       {
           if (entityKey != null && entityKey.EntityKeyValues != null && entityKey.EntityKeyValues.Length > 0)
               return (T)entityKey.EntityKeyValues.First().Value;
           return default(T);
       }

       /// <summary>
       /// Sets the first entity key value
       /// </summary>
       public static void SetFirstValue<T>(this EntityKey entityKey, T value)
       {
           if (entityKey != null && entityKey.EntityKeyValues != null && entityKey.EntityKeyValues.Length > 0)
               entityKey.EntityKeyValues.First().Value = value;
           return;
       }

       /// <summary>
       /// Whether or not the entity key has a first value
       /// </summary>
       public static bool HasFirstValue<T>(this EntityKey entityKey)
       {
           var firstValue = GetFirstValue<T>(entityKey);
           var defaultValue = default(T);
           return (!firstValue.Equals(defaultValue));
       }

Saturday, August 16, 2008 3:26:05 PM (New Zealand Standard Time, UTC+12:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, August 15, 2008

Tommy has a new bed

Friday, August 15, 2008 12:47:57 AM (New Zealand Standard Time, UTC+12:00)  #    Disclaimer  |  Comments [1]  |  Trackback

Tommy Goes on Holiday

Friday, August 15, 2008 12:37:04 AM (New Zealand Standard Time, UTC+12:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Saturday, August 09, 2008

Tamaki Drive Sunset

All i can say is roll on spring....Probably my favouritest part of auckland is the tamaki drive stretch out east. And sometimes you get blindingly beautiful sunsets back across the city view.

08042008231

Saturday, August 09, 2008 10:16:26 PM (New Zealand Standard Time, UTC+12:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, August 05, 2008

Tommy gets a new bed

Tommy is officially a big boy now he has his own bed. He even wants to go to bed early at the moment, not sure how long that will last though!

 

02082008524

Tuesday, August 05, 2008 1:34:13 AM (New Zealand Standard Time, UTC+12:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, July 20, 2008

Christchurch Weekend

Sorry lucy, no pictures of tommy as yet, here's some snaps of our weekend down at christchurch/hamner springs. Will some trees do instead??

DSC03537DSC03538DSC03539DSC03541DSC03542DSC03544DSC03546DSC03550DSC03557

Sunday, July 20, 2008 4:24:11 PM (New Zealand Standard Time, UTC+12:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, July 13, 2008

Sorry Lucy, more pics coming soon

Lucy just reminded me that i must post mroe family pics soon. She however requesed pics of tommy explicitly even though i'm undoubtedly more handsome, strange...

Here's one of us in London to keep you going lucy.

DSC03511

Sunday, July 13, 2008 8:44:48 PM (New Zealand Standard Time, UTC+12:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, July 04, 2008

Entity Framework Lazy Loading - A quick alternative with extension methods

I saw the following code on a customer object which made me think about affording a better way to query domain relationships without the extra methods, extension methods come to the rescue!

I realise transparent lazy loading a la Nhibernate is another option, but i think that this way keeps the spirit of explicitly loading from the database, but reduces the amount of code clutter on the client side so you are not forever checking whether a relationship is loaded or not, you simply request an IQueryable from the EntityCollection<T>.

        /// <summary>
        /// Finds a customer account with the specified ID
        /// </summary>
        /// <param name="customerAccountID">ID of the customer account to find</param>
        /// <returns>Customer account with the specified ID</returns>
        public CustomerAccount FindAccount(int customerAccountID)
        {
            if(!this.CustomerAccounts.IsLoaded)
            {
                this.CustomerAccounts.Load();
            }

            var query =
                from ca in this.CustomerAccounts
                where ca.CustomerAccountID == customerAccountID
                select ca;

            return query.FirstOrDefault();
        }

I added a static class with the extension methods for the EntityCollection type as follows:

public static class EntityCollectionExtensions
    {
        public static EntityCollection<T> EnsureLoaded<T>(this EntityCollection<T> entityCollection) where T : class, IEntityWithRelationships
        {
            if (!entityCollection.IsLoaded)
                entityCollection.Load();
            return entityCollection;
        }

        public static IQueryable<T> AsQueryable<T>(this EntityCollection<T> entityCollection, bool ensureLoaded) where T : class, IEntityWithRelationships
        {
            if (ensureLoaded)
                entityCollection = EnsureLoaded<T>(entityCollection);
            return entityCollection.AsQueryable();        }
    }

This allows you to do the following, which will only go to the database if the relationship is not loaded.

            var query =
                from ca in this.CustomerAccounts.AsQueryable(true) // ensure the relationship is loaded and return a queryable on it
                where ca.CustomerAccountID == customerAccountID
                select ca;
    return query.FirstOrDefault();

Friday, July 04, 2008 8:44:57 AM (New Zealand Standard Time, UTC+12:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, June 24, 2008

A vote of confidence for the Entity Framework

I saw a post from the alt.net community here saying the EF was crap basically....http://efvote.wufoo.com/forms/ado-net-entity-framework-vote-of-no-confidence/

The core complaints raise some important issues, but i really don't think we need to go ballistic, and in some cases the concerns are just plain wrong. Time would be better spent finishing the Linq for NHibernate provider don't you think??

I've been working solidly with EF since the SP1 beta came out and can honestly say it's holding up very well across a large, enterprise data model structure, and alongside usage of a repository pattern and dependency injection to factor out most of the direct dependencies on the EF ObjectContext, we have a system that can move towards POCO over time with relatively little refactoring.

Here's some of my responses:

The design of the framework entities is that they are data only, causing issues in maintainability and making it difficult or impossible to implement business objects or more “real-world” objects. 
Wrong, you use partial classes to extend the generated aspects of the entities. This is explicitly encouraged and we use it a lot for core business rules and validation. There is nothing to stop you adding new non persistent properties or methods, or indeed new classes that are used alongside the entities.

The design of the framework entities is that they cannot be persistence ignorant, so things like unit testing become next to impossible because you cannot test the model without the database (for instance).
There are (admittedly difficult) ways to get close to POCO. Rather than tear your hair out, we used the repository pattern around the EF and avoid using the object context directly. You are then only dependent on EntityObject, EntityReference and EntityCollection on your entities. If you are sensible about it and don't work on object context, your refactoring when vnext comes along will be significantly reduced. This is a happy compromise for us and works well.

Lazy loading is not supported at all and requires a lot of extra code to make work.  This confuses me.  I thought I saw "lazy-loading" demoed internally but I might be mistaken.
KP > 'By design';) but honestly pretty easy to wire it up, not really a "lot of extra code". I have worked for years with NH too, and the time saved using the designer with EF more than makes up for it:)

Shared, canonical models contradict software best practices.  This appears to be related to using a single shared model for multiple contexts in the EF, rather than multiple models, one for each individual context. 
KP > Well, it may be a criticism of the vision of EF (the more than just an ORM story), but not of its core ORM functionality. Our clients are interested in the reporting aspects on their entity models without writing excessive code. It remained to be seen how this will pan out, but surely this is not a reason to pledge a vote of no confidence in a piece of technology?

Excessive merge conflicts with source control.  Apparently, since the diagram contains the model and the visualization, it causes tons of merge conflicts when a team is using it.
A somewhat moot point for most teams. We've settled on having a 'domain master', not a biggy for most teams? shared design on the database, entities and relationships would get messy in the best of worlds, i don't think this is a biggy.

Tuesday, June 24, 2008 5:22:28 PM (New Zealand Standard Time, UTC+12:00)  #    Disclaimer  |  Comments [6]  |  Trackback