We, my teammates and I, have been playing with creating a generic repository for Linq to Sql using AutoMapper. We haven’t used it in production or anything, but I figured I’d blog about it and see if anyone has any comments about it. It’s fun just to toy around sometimes.
A couple of posts ago, I blogged about Func<> and that’s going to play an important part in what I’m about to show you. So basically we started off coding what we would do without generics and then converted that into a generic method. The ending interface looks like this:
public interface ILinq2SqlRepository<TDataContext, TDomainModel, TLinqModel>
where TDataContext : DataContext
{
IList<TDomainModel> GetAll(Func<TDataContext, IQueryable<TLinqModel>> func);
TDomainModel GetById(Func<TDataContext, TLinqModel> func);
void Save(TDomainModel model, Func<TDataContext, TLinqModel> func);
}
So there is the GetAll and the GetById methods. I’m also working on the Save, but it’s a little more tricky because I believe there’s a bug in Linq to Sql Attach(entity, original) when the primary key is a Guid, but that’s just me :) My Save method right now keeps returning a cannot insert duplicate key error and I’ve Googled it, but to no avail. I’m also not sure how to handle the using(DataContext)…just know it’s a work in progress! Anyhow…
The implementation looks like this:
public class Linq2SqlRepository<TDataContext, TDomainModel, TLinqModel>
: ILinq2SqlRepository<TDataContext, TDomainModel, TLinqModel>
where TDataContext: DataContext
{
private readonly TDataContext DataContext;
public Linq2SqlRepository(TDataContext dataContext)
{
DataContext = dataContext;
}
public IList<TDomainModel> GetAll(Func<TDataContext, IQueryable<TLinqModel>> func)
{
var list = Mapper.Map<TLinqModel[], TDomainModel[]>(func.Invoke(DataContext).ToArray());
return list.ToList();
}
public TDomainModel GetById(Func<TDataContext, TLinqModel> func)
{
return Mapper.Map<TLinqModel, TDomainModel>(func.Invoke(DataContext));
}
public void Save(TDomainModel model, Func<TDataContext, TLinqModel> func)
{
using (DataContext)
{
var original = func.Invoke(DataContext);
var entity = Mapper.Map<TDomainModel, TLinqModel>(model);
DataContext.GetTable(typeof(TLinqModel)).Attach(entity, original);
DataContext.SubmitChanges();
}
}
}
Well here’s the base implementation and it looks a lot easier if you have a good understanding of .Net generics. You can see the common AutoMapper Mapper.Map<> method in there, which is what we’re using to do the left to right mapping and basically we’re just passing in simple Linq commands. So we still have to do our AutoMapper configuration, but my hope is that maybe this post will strike up a good idea in someone else’s head…hopefully!
I thought it’d work best to implement this into a repository by just setting it as a private field like this:
public class SampleRepository : IRepository<DomainPerson>
{
private readonly ILinq2SqlRepository<SampleDataContext, DomainPerson, DataPerson> _repository;
public SampleRepository(string connstring)
{
_repository = new Linq2SqlRepository<SampleDataContext, DomainPerson, DataPerson>(
new SampleDataContext(connstring));
}
public IList<DomainPerson> GetAll()
{
return _repository.GetAll(x => x.DataPeople);
}
public DomainPerson GetById(Guid id)
{
return _repository.GetById(x => x.DataPeople.SingleOrDefault(y => y.Id == personId));
}
public void Save(DomainPerson person)
{
_repository.Save(person, x => x.DataPeople.SingleOrDefault(y => y.Id == person.Id));
}
}
I went the inheritance route and didn’t really care for it, mainly because it was much uglier in the implementation. So you’ll notice this inherits from a generic IRepository, which could’ve been IDomainPersonRepository that inherits from the generic or was just a plain ole repository. Anyhow, it looks like this:
public interface IRepository<T>
{
IList<T> GetAll();
T GetById(Guid id);
void Save(T model);
}
I wanted to show prettier things in the public side of the API like GetById with just Guid instead of pass me an ugly lambda :) (Even though I love the lambdas, a lot of devs don’t care for them) So the users of this API would just reference the IRepository<T>, but I’d actually probably end up renaming to a friendlier name like this:
public interface IDomainPersonRepository : IRepository<DomainPerson> {}
Then I’d go ahead and inherit my SampleRepository from IDomainPersonRepository…I should really plan these posts out more than I do before typing them so the post is better structured. As I get more practice in, I’ll try to do that for you!
So that’s my thinking on some type of generic repository for Linq to Sql with AutoMapper. Please keep in mind that this was a fly by thing we came up with this afternoon and I thought it was pretty cool. It could definitely use more testing & refactoring!
Let me know if you have any thoughts, questions, or comments.
Thanks for reading!