During research to useful design-patterns for my Wcf Data Services, I came across the following which I’d like to share with you. In this case, it’s not about OData. For this, I’m still working to build a (rather abstract) Business Layer, first start at the Entity Framework – level.
One of the common methods to build a Business Component on top of your (EF) DAL is creating a facade to it using the Repository-Pattern. That is, simply define an interface:
public interface IRepository<T>
{
T GetById(int id);
IEnumerable<T> GetAll();
IQueryable<T> Query(Expression<Func<T, bool>> filter);
}
and then implementing it:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AdventureWorksDAL;
namespace AdventureWorksBC
{
public class ProductRepos : IRepository<Product>, IDisposable
{
private AdventureWorksEntities _context;
public ProductRepos()
{
_context = new AdventureWorksEntities();
var types = AdventureWorksEntities.GetKnownProxyTypes();
}
#region IRepository<Product> Members
public Product GetById(int id)
{
return _context.Product.Where(p => p.ProductID == id).FirstOrDefault();
}
public IEnumerable<Product> GetAll()
{
return _context.Product;
}
public IQueryable<Product> Query(System.Linq.Expressions.Expression<Func<Product, bool>> filter)
{
return _context.Product.Where(filter);
}
#endregion
#region IDisposable Members
public void Dispose()
{
if (_context != null)
{
_context.Dispose();
}
GC.SuppressFinalize(this);
}
#endregion
}
}
Ever since I’m lazy…. I want to take this to a higher abstract level. With the preceding solution, you always have to implement the Interface for every object in the datacontext. Of course you want to have BusinessComponents where you can leave specific rules, but those GetAll, GetById – methods are pretty common. Therefore we’ll have to write a base-class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects.DataClasses;
using System.Data.Metadata.Edm;
using AdventureWorksDAL;
using System.Data.Objects;
using System.Data;
using System.Linq.Expressions;
namespace AdventureWorksBC
{
public class RepositoryBase<T> : IDisposable where T : EntityObject, new()
{
//private IQueryable<T> _entity;
private AdventureWorksEntities _context;
private ObjectSet<T> _query;
public RepositoryBase()
{
_context = new AdventureWorksEntities();
_query = _context.CreateObjectSet<T>();
}
public T GetById(int id)
{
foreach (T entity in _query)
{
if ((int)entity.EntityKey.EntityKeyValues[0].Value == id)
{
return entity;
}
}
return null;
}
public IEnumerable<T> GetAll()
{
return _query;
}
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return _query.Where(filter);
}
#region IDisposable Members
public void Dispose()
{
if (_context != null)
{
_context.Dispose();
}
GC.SuppressFinalize(this);
}
#endregion
}
}
Well that’s our baseclass. Let me short explain;
public class RepositoryBase<T> : IDisposable where T : EntityObject, new()
As you see we’ve replaced the specific object (like “Product” or “Customer”) with a generic “T”. After this you see the constraint: “it must be of type EntityObject, and have a parameterless constructor”.
Then in the constructor we’re going to initialize our fields_: context and _query. _query now returns an ObjectSet of type T (also it grabs all of those entities from the context).
The methods “GetAll” and “Query” are somehow self explainable I think, so leave them for now.
What really is kind of tricky is “GetById(int id). Honestly I have not figured out yet a better way to do this, but the end justifies the means…
Well now look at a demo app to show what you could do with this base-class:
First define a few childclasses:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AdventureWorksDAL;
namespace AdventureWorksBC
{
public class ContactRepository : RepositoryBase<Contact>
{
public ContactRepository()
{
}
}
public class ProductRepository : RepositoryBase<Product>
{
public ProductRepository()
{
}
}
}
Now call our entities through our new BusinessComponent:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using AdventureWorksDAL;
using AdventureWorksBC;
using System.Data.Metadata.Edm;
namespace DemoEFBusinessComponent
{
class Program
{
static void Main(string[] args)
{
var productRepos = new ProductRepository();
Product product = productRepos.GetById(319);
Console.WriteLine(string.Format("productId: {0}",product.ProductID));
Console.WriteLine(string.Format("Name: {0}", product.Name));
Console.WriteLine(string.Format("Model: {0}", product.ProductNumber));
productRepos.Dispose();
Console.Read();
var contactRepos = new ContactRepository();
var contacts = contactRepos.GetAll();
foreach (Contact contact in contacts)
{
Console.WriteLine(string.Format("contactId: {0}", contact.ContactID));
Console.WriteLine(string.Format("LastName: {0}", contact.LastName));
Console.WriteLine(string.Format("Email: {0}", contact.EmailAddress));
Console.WriteLine("");
}
contactRepos.Dispose();
Console.Read();
var productCategoryRepos = new RepositoryBase<ProductCategory>();
var specificCategory = productCategoryRepos.Query(pc => pc.Name.StartsWith("co"));
foreach (ProductCategory category in specificCategory)
{
Console.WriteLine("productCategoryId: {0}", category.ProductCategoryID);
Console.WriteLine("productCategoryName: {0}", category.Name);
}
productCategoryRepos.Dispose();
Console.Read();
}
}
}
This turns out rather cool, since you don’t have to define childclasses like “public class ProductRepository : RepositoryBase” for instance. Obvious, but let me mention: It’s really up to business whether you need them.
If you try this at home please forgive me for the endless “adventureworks” contacts list!
Thank you:Gil Fink