Introduction
The entity framework is Microsoft’s offering into the ORM market. Subsequently it maps databases to entities or objects with zero coding. Wikipedia has the following to say here.
Version 1.0 has been released as part of VS.NET 2008 Service Pack 1 and the MSDN pages start here
Although there has been some controversy about the tool, however Microsoft are determined that the Entity Framework is the ORM they are going to continue to develop (as opposed to LinqtoSQL), subsequently there are a lot of enhancements in VS.NET 2010.
There are a lot of guides to how to use the Entity Framework, however there where a number of pain points I came across, and a difference of behaviour depending on whether the object became “detached” or not. This blog discusses these, some of the code is mine some of it is not ;-) Also there is some usage of new features which I hope to reference as we go along.
The code is based on a real world example and subsequently it works! but also the entities in the examples jump around quite a bit, for which I apologise.
Creating the mappings.
Actually creating the mappings to the Database is a simple task, and there is a video here. however it is simply a case of create a new ADO.NET Entity Data Model from the Add Items dialog in Visual Studio.NET 2008. Then navigate the wizard to point it at the database, select the items you want to model and the model will be generated.
If you later need to refresh the model you can do so by right clicking and choosing “update model from the Database.”
It is also advised to change the names of the Entities for example pluralise etc.
You will notice that the Foreign Keys have been converted to “Navigation Properties” . These are quite clever for each navigation property there are 2 properties, one is the Object, and the other is a reference to the object.
Populating Objects
When you import the entities, you generate a class inherited from System.Data.Objects.ObjectContext, this is your object context (almost an equivalent to DataContext in LinqtoSQL). In the examples I am using this is called TimesheetsEntities.
My first lesson or Oh That is Why (pt 1).
So the first pain point I came across, was the time it takes to create and destroy the ObjectContext. Which frustrated me as the examples often showed all of the database work happening inside a Using statement.
To prevent this, I created a Static Property for the ObjectContext so I could reference it without constantly creating and destroying it. Something like below
- private static TimesheetsEntities _timesheetsEntities;
- public static TimesheetsEntities timesheetsEntities
- {
- get
- {
- if (_timesheetsEntities == null)
- {
- _timesheetsEntities = new TimesheetsEntities();
- }
- return _timesheetsEntities;
- }
- set { _timesheetsEntities = value; }
- \
Getting Data.
Populating the objects is relatively simple, the code below returns a Generic list of all of the Users.
- public List<User> GetUsers()
- {
- return timesheetsEntities.Users.ToList<User>();
- \
However if we want to do something a little more funky we can incorporate some Linq, for example an Order by
- public List<Status> GetStatuses()
- {
-
- var statuses =
- from n in timesheetsEntities.Statuses
- orderby n.OrderBy
- select n;
-
- return (statuses.ToList<Status>());
- \
or if you want to get an item based on a key, again I simple piece of linq this time using a Lambda expression.
- public Status GetStatus(int StatusId)
- {
- return timesheetsEntities.Statuses.FirstOrDefault(m => m.StatusId == StatusId);
- }
-
Saving Data
This is where the fun starts, and the degree of fun depends on whether the entity is attached to the Object Context or not also whether it is an insert or not.
Inserts
These are easy, as the entity is new it is always unattached.
In the case below I have my context (timesheetsEntities from above) and I want to add my “project” entity so I call the AddToProjects method. An AddTo… method is created for all of the entities you mapped above.
The save routine is clever and if you have an Object Graph, an Object with Child Objects the whole graph is saved. An example would be Invoice with Lines.
- timesheetsEntities.AddToProjects(project);
Updates (attached)
This is a little more complicated. So the object context can determine if and what needs changing you need to get a copy of the current object. Once you have done this you can apply the changes to the Object Context, using the ApplyPropertyChanges method.
- object pObject;
- if (timesheetsEntities.TryGetObjectByKey(project.EntityKey, out pObject))
- {
- timesheetsEntities.ApplyPropertyChanges(project.EntityKey.EntitySetName, project);
- \
At the end of this you need to tell the entity frame work to save.
all in all it looks a little like this
- //TODO: this is insert what about update?
- if (project.ProjectId == 0)
- {
- timesheetsEntities.AddToProjects(project);
- }
- else
- {
-
- object pObject;
- if (timesheetsEntities.TryGetObjectByKey(project.EntityKey, out pObject))
- {
- timesheetsEntities.ApplyPropertyChanges(project.EntityKey.EntitySetName, project);
- }
-
-
- }
-
- int changes = timesheetsEntities.SaveChanges();
-
Updates Detached (Or Oh that is how pt2 – Oh well I lost count)
It as at this point the Entity framework starts getting inconsistent, and I nearly lost a lot of hair.
You can identify if the entity is attached or not by testing it’s entityState property.
- if (timeSheet.EntityState == EntityState.Detached)
-
The first inconsistency is ApplyPropertyChanges no longer save foreign key constraints. So for example the timesheets entity (above) has a Status (which is a lookup table). If this changes in the Attached model this seems fine, however when you are Detached it is not.
However i found this on the internet, which explains creating an extension method which will update the object for all of the items.
This extension method needs to be placed in a separate static class.
- public static void ApplyReferencePropertyChanges(this ObjectContext context, IEntityWithRelationships newEntity, IEntityWithRelationships oldEntity)
- {
- foreach (var relatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
- {
- var oldReference = relatedEnd as EntityReference;
-
- if (oldReference != null)
- {
- var newReference = newEntity.RelationshipManager.GetRelatedEnd(oldReference.RelationshipName,
- oldReference.TargetRoleName) as EntityReference;
-
- if (newReference != null)
- {
- oldReference.EntityKey = newReference.EntityKey;
- }
- }
- }
- \
The second inconsistency is the object graph is not saved. So you need to iterate through each of the child objects and save them individually. This is a bit long winded and I have a lovely selection of error messages that went with it.
So in my Model the Timesheet has lines. As in an update these might have changed, the first stage is, to loop through each of the items and call a save routine. Which sounds straight forward. Except some are new, so we have to differentiate in our save routine.
The next problem I had was “The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key”
The cause of this error was because the Navigation Properties where confusing the Object Context, so for each of the navigation properties I had to remove the Objects and set the EntityKey to the Reference of the Navigation Property.
- if (timeSheetLine.TimeSheetLineId == 0)
- {
- TimeSheetLine newTimeSheetLine = timeSheetLine.Clone<TimeSheetLine>();
-
- newTimeSheetLine.TimeSheets = null;
- newTimeSheetLine.TimeSheetsReference = null;
- newTimeSheetLine.TimeSheetsReference = new EntityReference<TimeSheet>();
- newTimeSheetLine.TimeSheetsReference.EntityKey = timeSheetLine.TimeSheets.EntityKey;
-
-
- newTimeSheetLine.Projects = null;
- newTimeSheetLine.ProjectsReference = null;
- newTimeSheetLine.ProjectsReference = new EntityReference<Project>();
- newTimeSheetLine.ProjectsReference.EntityKey = timeSheetLine.ProjectsReference.EntityKey;
-
- timesheetsEntities.AddToTimeSheetLines(newTimeSheetLine);
- }
- else
- {
-
- object pObject;
- timesheetsEntities.TryGetObjectByKey(timeSheetLine.EntityKey, out pObject);
- timesheetsEntities.ApplyPropertyChanges(timeSheetLine.EntityKey.EntitySetName, timeSheetLine);
-
- timesheetsEntities.ApplyReferencePropertyChanges(timeSheetLine as IEntityWithRelationships, pObject as IEntityWithRelationships);
-
-
- }
-
The astute of you will notice the Clone method which is not a method of ArrayList again this is another Extension method. Again I found it on the internet but I am afraid I have lost the reference. The object needed to cloned because you cannot change an object in a foreach, but also this created some reasonably elegant code.
- public static T Clone<T>(this T source)
- {
- var dcs = new System.Runtime.Serialization
- .DataContractSerializer(typeof(T));
- using (var ms = new System.IO.MemoryStream())
- {
- dcs.WriteObject(ms, source);
- ms.Seek(0, System.IO.SeekOrigin.Begin);
- return (T)dcs.ReadObject(ms);
- }
- }
- \
Conclusion
Although the Entity Framework does cause some headaches, I think it is a good technology with lots of potential. Hopefully this helps solve some of the problems, I have some ideas on how to deal with dirty reads and writes which I will post later.
No comments:
Post a Comment