I got a comment on my first post on this pattern that pointed out unnecessary use of reflection as well as a way to make my sample cleaner. I took an opportunity to go ahead and update the sample. I added a bit of new functionality to illustrate how you can also pass in where clause and order by clause usage. I added a couple of functions to my base repository class to illustrate how to do this. Here is the function that returns filtered and sorted data:
/// <summary>
/// Select data from database using a where clause
/// </summary>
/// <typeparam name="TItem">Type of data to select</typeparam>
/// <param name="whereClause">Where clause / function</param>
/// <param name="orderBy">Order by clause</param>
/// <returns></returns>
public IOrderedQueryable<TItem> Select<TItem>(
Expression<Func<TItem, bool>> whereClause,
Expression<Func<TItem, object>> orderBy)
where TItem : class, new()
{
IOrderedQueryable<TItem> data = context.Set<TItem>().Where(whereClause).OrderBy(orderBy);
return data;
}
Here is how I would call this function:
var activeOrderedProducts =
repository.Select<Product>(one => one.IsActive, one => one.ProducName).ToList();
Pretty simple so far. Now I am going to take it a step further and extract an interface from my repository. Ideally, I would create an interface first, but this is an experiment. Interface would look as follows:
using System;
using System.Linq.Expressions;
using System.Linq;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
namespace RepositoryEFCodeFirst
{
public interface IEFRepository<TContext> : IDisposable
where TContext : DbContext, IObjectContextAdapter, new()
{
void Delete<TItem>(TItem item) where TItem : class, new();
TItem Insert<TItem>(TItem item) where TItem : class, new();
IQueryable<TItem> Select<TItem>() where TItem : class, new();
IQueryable<TItem> Select<TItem>(Expression<Func<TItem, bool>> whereClause) where TItem : class, new();
IOrderedQueryable<TItem> Select<TItem>(Expression<Func<TItem, bool>> whereClause, Expression<Func<TItem, object>> orderBy)
where TItem : class, new();
TItem Update<TItem>(TItem item) where TItem : class, new();
}
}
The last step would be to configure dependency injection and use an interface to resolve repository. Ordinarily, you would configure unity container in global.asax file for example for WCF service or a web application. Code would look as following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity;
namespace RepositoryEFCodeFirst
{
class Program
{
static UnityContainer container = new UnityContainer();
static void Main(string[] args)
{
container.RegisterType<IEFRepository<ProductContext>, EFRepository<ProductContext>>(
new ContainerControlledLifetimeManager());
Now my code is even cleaner – I use interfaces everywhere and my “business code” does not deal with data access at all, considering that I would register my types elsewhere, not in my business layer.
Here is the final program code that uses repository:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity;
namespace RepositoryEFCodeFirst
{
class Program
{
static UnityContainer container = new UnityContainer();
static void Main(string[] args)
{
container.RegisterType<IEFRepository<ProductContext>, EFRepository<ProductContext>>(
new ContainerControlledLifetimeManager());
using (var repository = container.Resolve<IEFRepository<ProductContext>>())
{
Console.WriteLine("Total products in DB : " +
repository.Select<Product>().Count().ToString());
Product newProduct = new Product();
newProduct.ProducName = "Plates";
newProduct.ProducNumber = "001";
newProduct.Notes = "SOme notes for plates";
newProduct.IsActive = true;
newProduct = repository.Insert(newProduct);
var ordered = repository.Select<Product>()
.Where(one => one.IsActive == true)
.OrderBy(one => one.ProducNumber).ToList();
Console.WriteLine("New id is " + newProduct.ProductId.ToString());
var aProduct = repository.Select<Product>().Where(one => one.IsActive).First();
var activeProducts = repository.Select<Product>(one => one.IsActive).ToList();
var activeOrderedProducts =
repository.Select<Product>(one => one.IsActive, one => one.ProducName).ToList();
aProduct.ProducName = "Updated Plates";
aProduct = repository.Update(aProduct);
aProduct = repository.Select<Product>()
.Where(one => one.ProductId == newProduct.ProductId).First();
Console.WriteLine("Update name is: " + aProduct.ProducName.ToString());
repository.Delete(aProduct);
}
}
}
}
I am also pasting in entire repository class (updated):
using System;
using System.Configuration;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.Practices.Unity;
namespace RepositoryEFCodeFirst
{
/// <summary>
/// Repository base class used with DbContext
/// </summary>
/// <typeparam name="TContext">Type of DdContext that this repository operates on</typeparam>
public class EFRepository<TContext> : IDisposable, IEFRepository<TContext>
where TContext : DbContext, IObjectContextAdapter, new()
{
private TContext context;
/// <summary>
/// Create new instance of repository
/// </summary>
/// <param name="connectionStringName">Connection string name from .config file</param>
public EFRepository(string connectionStringName)
{
context = new TContext();
context.Database.Connection.ConnectionString =
ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
}
[InjectionConstructor]
public EFRepository()
{
context = new TContext();
context.Database.Connection.ConnectionString =
ConfigurationManager.ConnectionStrings["ProductConnection"].ConnectionString;
}
/// <summary>
/// Dispose repository
/// </summary>
public void Dispose()
{
if (context != null)
{
context.Dispose();
context = null;
}
}
/// <summary>
/// Select data from database
/// </summary>
/// <typeparam name="TItem">Type of data to select</typeparam>
/// <returns></returns>
public IQueryable<TItem> Select<TItem>()
where TItem : class, new()
{
DbSet<TItem> set = context.Set<TItem>();
return set;
}
/// <summary>
/// Select data from database using a where clause
/// </summary>
/// <typeparam name="TItem">Type of data to select</typeparam>
/// <param name="whereClause">Where clause / function</param>
/// <returns></returns>
public IQueryable<TItem> Select<TItem>(Expression<Func<TItem, bool>> whereClause)
where TItem : class, new()
{
IQueryable<TItem> data = context.Set<TItem>().Where(whereClause);
return data;
}
/// <summary>
/// Select data from database using a where clause
/// </summary>
/// <typeparam name="TItem">Type of data to select</typeparam>
/// <param name="whereClause">Where clause / function</param>
/// <param name="orderBy">Order by clause</param>
/// <returns></returns>
public IOrderedQueryable<TItem> Select<TItem>(
Expression<Func<TItem, bool>> whereClause,
Expression<Func<TItem, object>> orderBy)
where TItem : class, new()
{
IOrderedQueryable<TItem> data = context.Set<TItem>().Where(whereClause).OrderBy(orderBy);
return data;
}
/// <summary>
/// Insert new item into database
/// </summary>
/// <typeparam name="TItem">Type of item to insert</typeparam>
/// <param name="item">Item to insert</param>
/// <returns>Inserted item</returns>
public TItem Insert<TItem>(TItem item)
where TItem : class, new()
{
DbSet<TItem> set = context.Set<TItem>();
set.Add(item);
context.SaveChanges();
return item;
}
/// <summary>
/// Update an item
/// </summary>
/// <typeparam name="TItem">Type of item to update</typeparam>
/// <param name="item">Item to update</param>
/// <returns>Updated item</returns>
public TItem Update<TItem>(TItem item)
where TItem : class, new()
{
DbSet<TItem> set = context.Set<TItem>();
set.Attach(item);
context.Entry(item).State = System.Data.EntityState.Modified;
context.SaveChanges();
return item;
}
/// <summary>
/// Delete an item
/// </summary>
/// <typeparam name="TItem">Type of item to delete</typeparam>
/// <param name="item">Item to delete</param>
public void Delete<TItem>(TItem item)
where TItem : class, new()
{
DbSet<TItem> set = context.Set<TItem>();
var entry = context.Entry(item);
if (entry != null)
{
entry.State = System.Data.EntityState.Deleted;
}
else
{
set.Attach(item);
}
context.Entry(item).State = System.Data.EntityState.Deleted;
context.SaveChanges();
}
}
}
You can download entire solution here.