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; }
Remember Me
a@href@title, b, blockquote@cite, i, strong, u
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.
E-mail