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.
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...
}
}
}
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
|
"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;
_baseCollection = null;
}
public WPFCollection(EntityCollection<T> items, BAEntities context);
: base(items);
{
_context = context;
_baseCollection = items;
_baseCollection = items;
}
protected override void InsertItem(int index, T item)
{
if (_baseCollection != null)
{
_baseCollection.Add(item);
} else
{
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]);
}
{
_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.
very interesting posts regarding wpf and the entity framework, it as helped me out alot.
ReplyDeleteA very interesting solution, thanks
ReplyDelete