Tuesday, March 2, 2010

The Entity Framework

Okay, I finally have something worth posting, if only to avoid my own personal amnesia tragedy.  For the last month I have been trying to wrap my head around Entity Framework (EF).  Below is a list of my personal discoveries regarding this technology.

Why should I use EF instead of LINQ to SQL classes?
All of this .NET stuff is fairly new.  LINQ technology was not introduced until .NET 3.0.  LINQ to SQL grew out of the LINQ project and the Object Relational Designer was introduced in Visual Studio 2008.  EF on the other hand, was being created by an entirely separate team at Microsoft called Data Progammability.  Because EF was designed to work with multiple database platforms (not just SQL Server) it became the heir apparent and LINQ to SQL was brought into the Data Programmability team.  As you can guess, that means that LINQ to SQL, although not going away, will not have any shiny new features or abilities.  Like it or not, EF and LINQ to Entities are the future for .NET. (Programming Entitfy Framework, Page 14)

Not Every "Entity" is an EF Entity
For those of us who are new to .NET, it is unfortunate that the .NET landscape is cluttered with so many "Entity" classes from LINQ to SQL (L2S).  If you are making the transition from L2S to EF, you really have to be careful with your terminology.  The best way to keep things straight is to peruse the Designer.cs file generated by the *.edmx and look through all the base class names.  These are the basis for the EF.  Classes you'll need to be familiar with include the following:

EntityObject: The base EF object and corresponds to a record in a specific database table(s).

ObjectSet<TEntity>: This collection represents a database table(or tables). Based on ObjectQuery.

ObjectQuery<TEntity>: Base class for ObjectSet<TEntity> and is the type returned by LINQ to Entity queries.  If you modify an ObjectSet in any way, like using Include() or OrderBy() it will be returned as an ObjectQuery.

EntityCollection<TEntity>:  Represents the *many side of a relationship.  Entity Collections have several methods to help you deal with the relationship.

EntityReference<TEntity>: These objects get paired up with the *one side of relationships.  For example if an Order EntityObject (the many side) has a reference to a Customer EntityObject (the one side) then the Order's Customer property will automatically get paired with a CustomerReference property (an EntityReference object) that provides methods to deal with the one side of the association/relationship.

If you see any other objects with the word "Entity" in their names DO NOT assume that they belong to EF.  If you do, you will probably regret it.

INotifyPropertyChanged
This is the interface that allows objects to notify their bound controls that things have changed and should be updated.  All the EntityObjects created by the Object Relational Designer (ORD) implement this interface and do a good job of sending notifications from their property setter methods.  Unfortunately, Microsoft has not implemented this feature for Associations.  Below is the property setter for a "primitive" property from an EntityObject:

public global::System.String ActivityName
{
    get...
    set
    {
        OnActivityNameChanging(value);
        ReportPropertyChanging("ActivityName");
        _ActivityName = StructuralObject.SetValidValue(value, true);
        ReportPropertyChanged("ActivityName");
        OnActivityNameChanged();
    }
}

Here you can see that there is more than a little notification going on.  But the association setter has nothing:

public EntityReference<Reservation> ReservationReference
{
    get...
    set
    {
        if ((value != null))
        {
            ((IEntityWithRelationships)this).RelationshipManager...
        }
    }
}

Even in .NET 4.0 the oversight has not been corrected.  I will post the workaround for this along with automatically refreshed sorting that I culled from the web in my next post.

EntityCollections and ObjectQueries and ObjectSets, Oh My!
Even if you fix the missing notification stuff above you may not find things working as you would expect.  Some of this comes from the differences between the different collections of EntityObjects that EF delivers to your code.  A decent summary of the state of affairs can be found here, although it is a tad out of date.  What is important is the distinction between these three binding issues:


Binding Works?
EntityCollection
ObjectQuery
ObjectSet
Primitives (e.g., strings or numbers)
Yes
Yes
Yes
Entity Deletions
Yes
Yes
Yes
Entity Additions
Yes
Nope
Yes
Supports Sorting
Nope
.OrderBy()
Nope

This makes sense since EF has no way of knowing if an added object should be in an already executed query.  The lack of sorting means that (for me) these are pretty much worthless for binding so now we need to find a sortable, bindable list/collection class.  This brings up another point: Why the dizzying collection of collections?  Should you be using ListViewCollection, BindingList, ObservableCollection or BindingListCollectionView?  It feels like I need a four year degree in collection science!!  Or at least a weekend seminar.  I can't say that I have any kind of a grasp of the differences, so I won't try to confuse anyone here with my ignorance.  What I can say, is that I see a lot of votes for ObservableCollection<T>.  For example:
"But ideally your source collection derives from ObservableCollection<T>, the mother of all collections in the eyes of data binding, since it provides several extra goodies such as property and collection change notifications."   - from Bea Stolnitz' Blog post (FYI, when reading this post "Avalon" was the old name for WPF)
Clearly I have a lot of learning to do on this whole topic.  Hopefully I will have something more definitive to post at a later date.  I will probably throw my lot in with ObservableCollection<T> and hope for the best.  To that end, below is a custom ObservableCollection<T> that works with EntityObjects in an ObjectQuery to provide proper change notification for additions and deletions.  It has been cobbled together from a posting by Beth Massi which I converted to C# and added generic support.  I also borrowed some code for the GetEntitySetName<T> extension method listed at the bottom from Martin Robins' comments at the bottom of another blog post.


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Linq;
using System.Text;
using BAGA;

namespace WPFApp
{
  public class WPFCollection<T> : ObservableCollection<T> where T: EntityObject
  {
    private ObjectContext _context;
    public ObjectContext Context;
    {
      get { return _context; }
    }

    private EntityCollection _baseCollection;
    public EntityCollection BaseCollection;
    {
      get { return _baseCollection; }
    }

    public WPFCollection(ObjectQuery<T> items, BAEntities context)
      :base(items);
    {
      _context = context;
      _baseCollection = null;
    }

    public WPFCollection(EntityCollection<T> items, BAEntities context);
      : base(items);
    {
      _context = context;
      _baseCollection = items;
    }

    protected override void InsertItem(int index, T item)
    {
      if (_baseCollection != null)
      {
        _baseCollection.Add(item);
      }      else
      {
        String ESName = _context.GetEntitySetName<T>();
        _context.AddObject(ESName, item);
      }
      base.InsertItem(index, item);
    }

    protected override void RemoveItem(int index)
    {
      if (_baseCollection != null)
      {
        _baseCollection.Remove(this[index]);
      }   
      else
      {

        this.Context.DeleteObject(this[index]);
      }
      base.RemoveItem(index);
    }

  }

  //This extension method allows an ObjectContext to get the name of the EntitySet that an EntityObject belongs to;
  static class EFExtensions
  {
    public static string GetEntitySetName<T>(this ObjectContext ctx) where T : EntityObject;
    {

      EdmEntityTypeAttribute attribute = ((EdmEntityTypeAttribute[])typeof(T).GetCustomAttributes(typeof(EdmEntityTypeAttribute), true)).Single();

      EntityContainer container = ctx.MetadataWorkspace.GetEntityContainer(ctx.DefaultContainerName, DataSpace.CSpace);

      return container.BaseEntitySets.Single(es => es.ElementType.Name == attribute.Name).Name;

    }

  }
}

I will use this class in my next post dealing with change notification for entity associations.

Beware the Silo Experts
In order to begin exploring EF, I have been going through the book that I menthioned above: Programming Entity Framework, by Julia Lerman.  I have been enjoying it and it seems to be well written, however it is NOT well edited as there are numerous minor mistakes in the code.  Part of this is undoubtably due to the fact that Julia's native language is VB and she has used a code conversion tool to covert all of her VB examples to C#.  Although this is mildly annoying, it is accompanied by the fact that Julia doesn't know much about WPF and so her examples contain not only bad practices, but misstatements regarding the capabilities of WPF.  I would still recommend Julia's book, just be aware of her weaknesses and take her C# code and statements regarding WPF with very large grain of salt.

This is the general problem with the whole .NET enterprise.  There are too many experts on this or that framework and very few with an holistic knowledge of the vast .NET landscape.  For example, I emailed a Microsoft employee who was an all-star EF guy and he had to defer my relatively simple question on binding to ObjectQuery/ObjectSet objects to someone in the WPF department. Seems like they should ship all of these guys to a summer-camp where they force them to create an application with all of this stuff so they can understand all of the gaping holes that exist between their frameworks.   


2 comments:

  1. very interesting posts regarding wpf and the entity framework, it as helped me out alot.

    ReplyDelete
  2. A very interesting solution, thanks

    ReplyDelete