Entity Framework and Creating a Reusable Abstraction#

I am currently working on an open source project called MyCodeCamp with Sean Chambers, Fabio Honigmann, and Esteban Garcia.  We decided to use ASP.NET MVC and Entity Framework (EF).  Although I use SubSonic 2.2 and 3 beta at work, I thought this might be a good time to learn other ORM type frameworks for comparison. I have used LINQ To SQL and NHibernate a little also, but am not an expert by any means.  And since we want this project to be accessible to as many as possible, we thought Entity Framework would be a good choice seeing as Linq2Sql is getting folded into Entity Framework anyway.

So, I don’t really know how most people use EF, but coming from working on SubSonic 3, I have some habits that I would like to keep, particularly, using a Repository<T> like pattern.  After looking at all the code and classes I would have to write if I did the traditional, IClassRepository and ClassRepository, I decided to make Repository<T> work kind of like it does in SubSonic 3 with SubSonicRepository<T>.  This was not as easy as I thought.

First problem is when you want to update the model, you need to open up the .edmx file in the designer which sometimes does not open and you have to reboot Visual Studio.  And if you make major changes to your data model, you are better off deleting the file and recreating it.  Now I don’t think that would go over to well if you actually used it to do your mappings of data to entity models, but I am not using Entity Framework that way.  I think it was their intention to use it more of as a mapper, but I think I have found a much easier and scalable way of mapping data to domain objects by using AutoMapper.  We use AutoMapper at work with SubSonic 3 and we just love it.  I won’t go into everything it does, but it is by far the best project out there for mapping objects to other objects.

So here is an example of how I am getting an entity object, then mapping it to a domain object.

public class Repository<T> : IRepository<T>
    {
        private MyCodeCamp.Data.MyCodeCampEntities context;
        public Repository()
        {
            context = new MyCodeCamp.Data.MyCodeCampEntities();

        }

        public T GetById(Int32 id)
        {
            var tableName = typeof(T).Name;
            var key = new EntityKey("MyCodeCampEntities." + tableName, "Id", id);
            var r = (T)context.GetObjectByKey(key);

            return r;
        }
    }

Notice, since I am using T, I need to use reflection to get the Entity Name, or table name.  Then this data returns to a BaseService<TModel, T>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AutoMapper;
using MyCodeCamp.Data;

namespace MyCodeCamp.Domain.Services
{
     public abstract class BaseService<TModel, T>
        where TModel : new()
        where T : new()
    {
         public Repository<T> _repo;

         protected BaseService()
        {
            _repo = new Repository<T>();
            // Cache maps
            var tm = Mapper.FindTypeMapFor<T, TModel>();
            if (tm == null)
            {
                Mapper.CreateMap<T, TModel>();
            }

            tm = Mapper.FindTypeMapFor<TModel, T>();
            if (tm == null)
            {
                Mapper.CreateMap<TModel, T>();
            }
        }

         public virtual TModel GetById(Int32 id)
         {
             var item = _repo.GetById(id);
             var model = Map(item);
             return model;
         }

        public virtual TModel Map(T d)
        {
            return Mapper.Map<T, TModel>(d);
        }


    
    }
}

Now for the service that derives form the BaseService:

public class VenueService : BaseService<Model.Venue, Data.Venue>
    {
        public VenueService()
        {
           
        }
        
        public List<Model.Venue> GetByCity(string city)
        {
            var result = _repo.Find(v => v.City == city);
            var venues = result.ToList();
            var venueModels = Map(venues);
            return venueModels;
        }

        
    }

As you can see the Code is very clean and simple to read.  What is nice about going down this road with the AutoMapper is that mapping is completely automated and I don’t have to remap anything if I change properties or add them to the data model.  All I need to do is make sure the Property is on the domain model, then it will get mapped.  Also it will map by convention also, so if you have a Complex type as a property, like, Venue.Room.Name it would automatically map to a flattened dto like Venue.RoomName.  There are a ton of other conventions also, but you should go download AutoMapper and look at the tests to see all of the possibilities.

The next big issue I ran into was that the data was not getting updated when I would pass a detached object to the EF context.  Since I was creating the object that needed to get passed in outside of the ObjectContext scope, I needed to get the EF ObjectStateManager to recognize that the Entity Object had changed and to persist it.  I again had to use a little bit of reflection, but after I understood how EF handled things, it made more sense.  It was quite frustrating for a few hours trying to figure out why EF was not persisting updates and only inserts.  As you can see with the code below I needed to get the value of the Entity Key and the hydrate the entity object so that the ObjectStateManager knew that the data had been changed.

public int Update(T o)
       {
           var tableName = typeof(T).Name;

           // Need to get object out of ObjectStateManager, then set it so the dbcontext knows to persist changes
           var Id = GetPrimaryKeyValue(o);
           T currentObject = GetById(Id);
           currentObject = o;

           context.ApplyPropertyChanges(tableName, currentObject);

           return context.SaveChanges();
       }

       private int GetPrimaryKeyValue(T o)
       {
           int Id = 0;
           var properties = typeof(T).GetProperties();
           foreach (PropertyInfo p in properties)
           {
               if (string.Compare(p.Name, "Id", true) == 0)
               {
                   var r = p.GetValue(o, null).ToString();
                   Id = int.Parse(r);
                   break;
               }
           }
           return Id;
       }
All in all, I don’t mind EF for the Data Access Mapping, and the way it is currently abstracted in our project, it could easily be switched out for Linq To Sql, Subsonic, or any other ORM that uses LINQ to access its data.  I think the best way to use Data Mapping technologies in the future is control your own mapping by using tools such as AutoMapper so that you are not locked in to one way of getting your data out of your database. 
Monday, May 18, 2009 4:39:27 AM UTC #    Comments [6]  |  Trackback

 

Friday, May 29, 2009 6:25:39 PM UTC
Jim,

This is Brian Anderson from Tourney Logic. We tried to respond to your email but your email account is returning blocked error messages. Can you please send us an email with your phone #.

Thanks,

Brian
Saturday, September 19, 2009 4:08:44 AM UTC
Hello,

When you get an object as follows:

public T GetById(Int32 id) {
String table = typeof(T).Name;
EntityKey key = new EntityKey(_context.DefaultContainerName + "." + table, "Id", id);
return (T)_context.GetObjectByKey(key);
} // GetById

How can you get all the objects related to it through an Many-to-Many relationships or any other relationships?

Usually Include is used but in this case inside this method I don't know the tables to include.

Do you know how to solve this?

I looked everywhere but I couldn't find a solution.

Thank You,
Miguel
Friday, June 11, 2010 9:05:47 AM UTC
Great Post.I like the link.Now expecting some good ideas from your upcoming post.
Friday, June 11, 2010 9:06:00 AM UTC
Hi Like Your Post.Great stuff and new ideas.So keep it up.
Monday, August 23, 2010 11:59:05 AM UTC
Best canon Coffee Mugs! Funny, Cute, & Humorous Unique designs. Also find Travel Mugs, Coffee Cups also, or Create Photo Personalized Mugs & Drinkware
canon mugs
canon lens mugs
Nikon Mugs
Canon Coffee Lens Mug
Nikon Coffee Lens Mug
anon coffee mug,canon lens mug,canon mug,canon coffee cup,canon thermos travel mug
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, b, blockquote@cite, i, strong, u)  

Enter the code shown (prevents robots):

 

All content © 2010, Jim Zimmerman
Book
New Book
Links to me
On this page
Sponsors
Calendar
<September 2010>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789
Archives
Tags
Blogroll OPML
Technorati
Favorite Links
Disclaimer

Powered by: newtelligence dasBlog 1.9.6264.0

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Send mail to the author(s) E-mail