About two years ago have written a post and an article in codeproject about the repository pattern implementation with EF V1. After that I got a lot of requests in my articles to make it compatible for EF4 POCO support. So here I am going to make that. In this implementation of repository, I am going to add some new methods which has helped me to regularly while working with EF4 POCO support. Hope it will be helpful to you.
Here BaseContext
is inherited from ObjectContext
and BaseDataContract
is base class of all my entities as POCO.
public enum State{Unchanged, Added, Modified, Deleted}
[DataContractAttribute(IsReference = true)]
public abstract class BaseDataContract
{
public BaseDataContract()
{
}
[DataMember]
public State ObjectState { get; set; }
}
Take a look Repository
class first and I will explain these methods later :
public class Repository<E, C> : IRepository<E, C>, IDisposable
where E : BaseDataContract
where C : BaseContext
{
private C _ctx;
private string _KeyProperty = "ID";
public string KeyProperty
{
get
{
return _KeyProperty;
}
set
{
_KeyProperty = value;
}
}
public C Session
{
get { return _ctx; }
set { _ctx = value; }
}
public Repository(C session)
{
_ctx = session;
}
public Repository(C session, string keyProperty) : this(session)
{
_KeyProperty = keyProperty;
}
#region IRepository<E,C> Members
public int Save()
{
return _ctx.SaveChanges();
}
public ObjectQuery<E> DoQuery(string entitySetName)
{
_ctx.ContextOptions.LazyLoadingEnabled = true;
return _ctx.CreateQuery<E>("[" + entitySetName + "]");
}
public ObjectQuery<E> DoQuery()
{
_ctx.ContextOptions.LazyLoadingEnabled = true;
return _ctx.CreateQuery<E>("[" + typeof(E).Name + "]");
}
public ObjectQuery<E> DoQuery(string entitySetName, ISpecification<E> where)
{
_ctx.ContextOptions.LazyLoadingEnabled = true;
return
(ObjectQuery<E>)_ctx.CreateQuery<E>("[" + entitySetName + "]")
.Where(where.EvalPredicate);
}
public ObjectQuery<E> DoQuery(ISpecification<E> where)
{
_ctx.ContextOptions.LazyLoadingEnabled = true;
return
(ObjectQuery<E>)_ctx.CreateQuery<E>("[" + typeof(E).Name + "]")
.Where(where.EvalPredicate);
}
public ObjectQuery<E> DoQuery(int maximumRows, int startRowIndex)
{
_ctx.ContextOptions.LazyLoadingEnabled = true;
return (ObjectQuery<E>)_ctx.CreateQuery<E>("[" +
typeof(E).Name + "]").Skip<E>(startRowIndex).Take(maximumRows);
}
public ObjectQuery<E> DoQuery(Expression<Func<E, object>> sortExpression)
{
if (null == sortExpression)
{
return ((IRepository<E, C>)this).DoQuery();
}
return (ObjectQuery<E>)((IRepository<E, C>)this).DoQuery().OrderBy<E, object>(sortExpression);
}
public ObjectQuery<E> DoQuery(Expression<Func<E, object>> sortExpression, int maximumRows, int startRowIndex)
{
if (sortExpression == null)
{
return ((IRepository<E, C>)this).DoQuery(maximumRows, startRowIndex);
}
return (ObjectQuery<E>)((IRepository<E, C>)this).DoQuery(sortExpression).Skip<E>(startRowIndex).Take(maximumRows);
}
public ObjectQuery<E> SelectAll(string entitySetName)
{
return DoQuery(entitySetName);
}
public ObjectQuery<E> SelectAll()
{
try
{
return DoQuery();
}
catch (Exception)
{
throw;
}
}
public ObjectQuery<E> SelectAll(string entitySetName, ISpecification<E> where)
{
return DoQuery(entitySetName, where);
}
public ObjectQuery<E> SelectAll(ISpecification<E> where)
{
return DoQuery(where);
}
public ObjectQuery<E> SelectAll(int maximumRows, int startRowIndex)
{
return DoQuery(maximumRows, startRowIndex);
}
public ObjectQuery<E> SelectAll(Expression<Func<E, object>> sortExpression)
{
if (null == sortExpression)
{
return DoQuery(sortExpression);
}
return DoQuery(sortExpression);
}
public ObjectQuery<E> SelectAll(Expression<Func<E,
object>> sortExpression, int maximumRows, int startRowIndex)
{
if (sortExpression == null)
{
return DoQuery(maximumRows, startRowIndex);
}
return DoQuery(sortExpression, maximumRows, startRowIndex);
}
public E SelectByKey(string Key)
{
_ctx.ContextOptions.LazyLoadingEnabled = true;
var xParam = Expression.Parameter(typeof(E), typeof(E).Name);
MemberExpression leftExpr = MemberExpression.Property(xParam, this._KeyProperty);
Expression rightExpr = Expression.Constant(Key);
BinaryExpression binaryExpr = MemberExpression.Equal(leftExpr, rightExpr);
Expression<Func<E, bool>> lambdaExpr = Expression.Lambda<Func<E,
bool>>(binaryExpr, new ParameterExpression[] { xParam });
ObjectQuery<E> resultCollection = ((IRepository<E, C>)this).SelectAll(new Specification<E>(lambdaExpr));
if (null != resultCollection && resultCollection.Count() > 0)
{
return resultCollection.First<E>();
}
return null;
}
public bool TrySameValueExist(string fieldName, object fieldValue, string key)
{
var xParam = Expression.Parameter(typeof(E), typeof(E).Name);
MemberExpression leftExprFieldCheck = MemberExpression.Property(xParam, fieldName);
Expression rightExprFieldCheck = Expression.Constant(fieldValue);
BinaryExpression binaryExprFieldCheck = MemberExpression.Equal(leftExprFieldCheck, rightExprFieldCheck);
MemberExpression leftExprKeyCheck = MemberExpression.Property(xParam, this._KeyProperty);
Expression rightExprKeyCheck = Expression.Constant(key);
BinaryExpression binaryExprKeyCheck = MemberExpression.NotEqual(leftExprKeyCheck, rightExprKeyCheck);
BinaryExpression finalBinaryExpr = Expression.And(binaryExprFieldCheck, binaryExprKeyCheck);
Expression<Func<E, bool>> lambdaExpr = Expression.Lambda<Func<E,
bool>>(finalBinaryExpr, new ParameterExpression[] { xParam });
return ((IRepository<E, C>)this).TryEntity(new Specification<E>(lambdaExpr));
}
public bool TryEntity(ISpecification<E> selectSpec)
{
return _ctx.CreateQuery<E>("[" + typeof(E).Name + "]").Any<E>(selectSpec.EvalPredicate);
}
public int GetCount()
{
return _ctx.CreateQuery<E>("[" + typeof(E).Name + "]").Count();
}
public int GetCount(ISpecification<E> selectSpec)
{
return _ctx.CreateQuery<E>("[" + typeof(E).Name + "]")
.Where(selectSpec.EvalPredicate).Count();
}
public void Delete(E entity)
{
_ctx.DeleteObject(entity);
}
public void Delete(object entity)
{
_ctx.DeleteObject(entity);
}
public void Add(E entity)
{
_ctx.AddObject(entity.GetType().Name, entity);
}
public List<String> AddOrAttach(E entity, EntityKey key = null)
{
try
{
_ctx.ContextOptions.LazyLoadingEnabled = false;
ObjectStateEntry entry = null;
if (null == key )
{
var entitySet = _ctx.GetEntitySet(entity.GetType());
key = _ctx.CreateEntityKey(entitySet.Name, entity);
}
if( null != key )
_ctx.ObjectStateManager.TryGetObjectStateEntry(key, out entry);
if (entry == null)
{
_ctx.AddObject(typeof(E).Name, entity);
_ctx.ObjectStateManager.ChangeObjectState(entity, StateHelpers.GetEquivelantEntityState(entity.ObjectState));
}
else
{
_ctx.ApplyCurrentValues(typeof(E).Name, entity);
}
List<String> changedCols = new List<string>();
var ose = (entry != null)? entry : _ctx.ObjectStateManager.GetObjectStateEntry(entity);
foreach (var propName in ose.GetModifiedProperties())
{
string pNameValue = propName;
pNameValue += "|" + ose.OriginalValues[propName] + "|" + ose.CurrentValues[propName];
changedCols.Add(pNameValue);
}
return changedCols;
}
catch (Exception)
{
throw;
}
}
public DbTransaction BeginTransaction()
{
if (_ctx.Connection.State != ConnectionState.Open)
{
_ctx.Connection.Open();
}
return _ctx.Connection.BeginTransaction();
}
public void ChangeStateOfNavigationObject(E entity, State state)
{
if (null != entity)
{
var navigationProperties = _ctx.GetNavigationProperty<C, E>();
if (null != navigationProperties)
{
foreach (var navigationProperty in navigationProperties)
{
var info = entity.GetType().GetProperty(navigationProperty.Name);
var value = info.GetValue(entity, null);
if (value != null)
{
if (((AssociationType)navigationProperty.RelationshipType).IsForeignKey
&&
navigationProperty.FromEndMember.RelationshipMultiplicity !=
RelationshipMultiplicity.Many)
{
Type t = value.GetType();
Type baseType;
if (!t.IsGenericType)
{
baseType = t.BaseType;
if (baseType.BaseType.Equals(typeof(BaseDataContract)))
{
MethodInfo method = this.GetType().GetMethod("ChangeObjectStatetoAttach");
MethodInfo generic = method.MakeGenericMethod(baseType);
generic.Invoke(this, new object[] { value, state });
}
}
else
{
Type[] genericBases = t.GetGenericArguments();
baseType = genericBases[0];
if (baseType.BaseType.Equals(typeof(BaseDataContract)))
{
MethodInfo method =
this.GetType().GetMethod("ChangeCollectionObjectStatetoAttach");
MethodInfo generic = method.MakeGenericMethod(baseType);
generic.Invoke(this, new object[] { value, state });
}
}
}
else
{
info.SetValue(entity, null, null);
}
}
}
}
}
}
public void ChangeObjectStatetoAttach<T>(T entity, State state) where T : BaseDataContract
{
if (null != entity)
{
var dataContract = entity as BaseDataContract;
dataContract.ObjectState = state;
EntityKey entityKey = _ctx.CreateEntityKey(
_ctx.GetEntitySet(dataContract.GetType()).Name, dataContract);
ObjectStateEntry storeEntity;
if (_ctx.ObjectStateManager.TryGetObjectStateEntry(entityKey, out storeEntity))
{
_ctx.ApplyCurrentValues(_ctx.GetEntitySet(typeof(T)).Name, entity);
}
else
{
_ctx.AddObject(_ctx.GetEntitySet(dataContract.GetType()).Name, dataContract);
}
_ctx.ObjectStateManager.ChangeObjectState(dataContract,
StateHelpers.
GetEquivelantEntityState
(dataContract.ObjectState));
}
}
public void ChangeCollectionObjectStatetoAttach<T>(FixupCollection<T> entities, State state)
where T : BaseDataContract
{
if (null != entities)
{
for (int i = 0; i < entities.Count; i++)
{
ChangeObjectStatetoAttach<T>(entities[i], state);
}
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
if (null != _ctx)
{
if (_ctx.Connection.State == ConnectionState.Open)
_ctx.Connection.Close();
_ctx.Dispose();
}
}
#endregion
}
Here those methods responsible to do queries, return the result in <a href="http://msdn.microsoft.com/en-us/library/bb345303.aspx">ObjectQuery</a>
which have been used for a special reason and
that is - ObjectQuery.EnablePlanCaching
Property -indicates whether the query plan should be cached. Plan-caching caches information which is computed as part of putting
together the query itself. By caching this, a subsequent execution of the same query (even if you change parameter
values) will run much faster than the first one. This information is cached per app-domain
so
you will generally benefit from the query cache across multiple client requests
to the same web app and the like. Here all DoQuery methods are responsible to
queries and other query method like SelectAll or Select methods internally use
these DoQuery methods with various parameters.
SelectByKey is
creating a LINQ expression using Expression
tree for Primary key and During
creating the repository on an entity you can supply this Primary Key property
as string. While using the POCO as you entity you can set attribute programming
to serve this kind of job.
TrySameValueExist is
doing the same job by allow you to set customize field and value comparison
there. What will do is create the E
xpression
for you and also add the
PrimaryKey comparison so that it excludes the object that you are querying for
(and where PrimaryKey != currentObjectPrimaryKey).
Add and Delete
method simply respectively call the AddObject and DeleteObject Method of ObjectContext
.
AddOrAttach method is for special situation where you don’t know whether object
is already added or not. It is expensive since it will do query to database to
check the existence.This method uses a class called StateHelpers have been used for Retrieving the Object State that compatible with Entity Framework.
public static class StateHelpers
{
public static EntityState GetEquivelantEntityState(State state)
{
switch (state)
{
case State.Added:
return EntityState.Added;
case State.Modified:
return EntityState.Modified;
case State.Deleted:
return EntityState.Deleted;
default:
return EntityState.Unchanged;
}
}
}
In ChangeObjectStatetoAttach
is a method which update the state of an entity and also go for all its navigation/related child object
to change their state in object-tree traversing way. To find out the navigation object I
need to add extension method of ObjectContext
.
public static List<NavigationProperty> GetNavigationProperty<TObjectContext, T>(
this ObjectContext context)
where TObjectContext : ObjectContext
{
var containerName = context.DefaultContainerName;
var model = DataSpace.CSpace;
var workspace = context.MetadataWorkspace;
var container = workspace.GetEntityContainer(containerName, model);
EntitySetBase entitySet = context.GetEntitySet(typeof (T));
if (entitySet == null)
return null;
var navigationProps = entitySet.ElementType.Members
.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty
)
.Cast<NavigationProperty>()
.ToList();
return navigationProps;
}
public static EntitySetBase GetEntitySet(this ObjectContext context, Type entityType)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (entityType == null)
{
throw new ArgumentNullException("entityType");
}
EntityContainer container = context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
if (container == null)
{
return null;
}
EntitySetBase entitySet = container.BaseEntitySets.Where(item => item.ElementType.Name.Equals(entityType.Name))
.FirstOrDefault();
return entitySet;
}
Here I need to create some extension of ObjectContext
to get all navigation properties of an
entity by manipulating the <code>metadata
information. In GetNavigationProperty method we need to retrieve the entity set name (that’s why we need the
GetEntitySet method) and from its type members it check for nevigationProperty
as BuiltInTypeKind
.
Here I check the
navigation property type whether that is generic type or not. In EF, Generic
navigation type is used for Collection of entity in case of Many End and on the
other hand normal property type for One or Zero End..This has been checked to
determine whether it is database object or not. To call a Generic method –
MethodInfo method = this.GetType().GetMethod("ChangeObjectStatetoAttach");
MethodInfo generic = method.MakeGenericMethod(baseType);
generic.Invoke(this, new object[] { value, state });
And ChangeCollectionObjectStatetoAttach method has been called in the same way with the parameter of parent, child/navigation property-value and state. In this method I simply call the ChangeObjectStatetoAttach method for all entity in the collection.
Repository is kind
of mediator to connect business layer with data access. So your business layer
should be aware of this repository class. I would also like to suggest that You
also can do some refactor on my repository class by introducing Unit of Work as umbrella
over multiple repositories and shared by all repository in place of the object
context directly and method like save, transaction can be switched over there.
Hope its help. Good luck.