Entity Framework - Repository check (Big text)
I'm making a full repository in C#/ASP.NET with the Entity framework but at the moment I'm afraid I'm overlooking something like disposing my ObjectContexts. In the following lines of codes you'll see my full repository (atleast what is needed for you guys to understand my problem) and I hope someone is kind enough to look through it and tell me if I made some mistakes.
This project is very very important for me but I'm new to the repository/EF models.
Global.asax
public class Global : System.Web.HttpApplication
{
private WebObjectContextStorage _storage;
public override void Init()
{
base.Init();
_storage = new WebObjectContextStorage(this);
}
protected void Application_Start(object sender, EventArgs e)
{
}
protected void Session_Start(object sender, EventArgs e)
{
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
ObjectContextInitializer.Instance().InitializeObjectContextOnce(() =>
{
ObjectContextManager.InitStorage(_storage);
});
}
protected void Application_EndRequest(object sender, EventArgs e)
{
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
}
protected void Application_Error(object sender, EventArgs e)
{
}
protected void Session_End(object sender, EventArgs e)
{
}
protected void Application_End(object sender, EventArgs e)
{
}
}
ObjectContextManager
public static class ObjectContextManager
{
public static void InitStorage(IObjectContextStorage storage)
{
if (storage == null)
{
throw new ArgumentNullException("storage");
}
if ((Storage != null) && (Storage != storage))
{
throw new ApplicationException("A storage mechanism has already been configured for this application");
}
Storage = storage;
}
/// <summary>
/// The default connection string name used if only one database is being communicated with.
/// </summary>
public static readonly string DefaultConnectionStringName = "TraceConnection";
/// <summary>
/// Used to get the current object context session if you're communicating with a single database.
/// When communicating with multiple databases, invoke <see cref="CurrentFor()" /> instead.
/// </summary>
public static ObjectContext Current
{
get
{
return CurrentFor(DefaultConnectionStringName);
}
}
/// <summary>
/// Used to get the current ObjectContext associated with a key; i.e., the key
/// associated with an object context for a specific database.
///
/// If you're only communicating with one database, you should call <see cref="Current" /> instead,
/// although you're certainly welcome to call this if you have the key available.
/// </summary>
public static ObjectContext CurrentFor(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key");
}
if (Storage == null)
{
throw new ApplicationException("An IObjectContextStorage has not been initialized");
}
ObjectContext context = null;
lock (_syncLock)
{
context = Storage.GetObjectContextForKey(key);
if (context == null)
{
context = ObjectContextFactory.GetTraceContext(key);
Storage.SetObjectContextForKey(key, context);
}
}
return context;
}
/// <summary>
/// This method is used by application-specific object context storage implementations
/// and unit tests. Its job is to walk thru existing cached object context(s) and Close() each one.
/// </summary>
public static void CloseAllObjectContexts()
{
foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
{
if (ctx.Connection.State == System.Data.ConnectionState.Open)
ctx.Connection.Close();
}
}
/// <summary>
/// An application-specific implementation of IObjectContextStorage must be setup either thru
/// <see cref="InitStorage" /> or one of the <see cref="Init" /> overloads.
/// </summary>
private static IObjectContextStorage Storage { get; set; }
private static object _syncLock = new object();
}
ObjectContextInitializer
public class ObjectContextInitializer
{
private static readonly object syncLock = new object();
private static ObjectContextInitializer instance;
protected ObjectContextInitializer() { }
private bool isInitialized = false;
public static ObjectContextInitializer Instance()
{
if (instance == null)
{
lock (syncLock)
{
if (instance == null)
{
instance = new ObjectContextInitializer();
}
}
}
return instance;
}
/// <summary>
/// This is the method which should be given the call to intialize the ObjectContext; e.g.,
/// ObjectContextInitializer.Instance().InitializeObjectContextOnce(() => InitializeObjectContext());
/// where InitializeObjectContext() is a method which calls ObjectContextManager.Init()
/// </summary>
/// <param name="initMethod"></param>
public void InitializeObjectContextOnce(Action initMethod)
{
lock (syncLock)
{
if (!isInitialized)
{
initMethod();
isInitialized = true;
}
}
}
}
ObjectContextFactory
public static class ObjectContextFactory
{
/// <summary>
/// Gets the TraceContext
/// </summary>
/// <param name="connectionString">Connection string to use for database queries</param>
/// <returns>The TraceContext</returns>
public static TraceContext GetTraceContext(string configName)
{
string connectionString = ConfigurationManager.ConnectionStrings[configName].ConnectionString;
return new TraceContext(connectionString);
}
}
WebObjectContextStorage
public class WebObjectContextStorage : IObjectContextStorage
{
public WebObjectContextStorage(HttpApplication app)
{
app.EndRequest += (sender, args) =>
{
ObjectContextManager.CloseAllObjectContexts();
HttpContext.Current.Items.Remove(HttpContextObjectContextStorageKey);
};
}
public ObjectContext GetObjectContextForKey(string key)
{
ObjectContextStorage storage = GetObjectContextStorage();
return storage.GetObjectContextForKey(key);
}
public void SetObjectContextForKey(string factoryKey, ObjectContext session)
{
ObjectContextStorage storage = GetObjectContextStorage();
storage.SetObjectContextForKey(factoryKey, session);
}
public IEnumerable<ObjectContext> GetAllObjectContexts()
{
ObjectContextStorage storage = GetObjectContextStorage();
return storage.GetAllObjectContexts();
}
private ObjectContextStorage GetObjectContextStorage()
{
HttpContext context = HttpContext.Current;
ObjectContextStorage storage = context.Items[HttpContextObjectContextStorageKey] as ObjectContextStorage;
if (storage == null)
{
storage = new ObjectContextStorage();
context.Items[HttpContextObjectContextStorageKey] = storage;
}
return storage;
}
private static readonly string HttpContextObjectContextStorageKey = "HttpContextObjectContextStorageKey";
}
ObjectContextStorage
public class ObjectContextStorage : IObjectContextStorage
{
private Dictionary<string, ObjectContext> storage = new Dictionary<string, ObjectContext>();
/// <summary>
/// Initializes a new instance of the <see cref="SimpleObjectContextStorage"/> class.
/// </summary>
public ObjectContextStorage() { }
/// <summary>
/// Returns the object context associated with the specified key or
/// null if the specified key is not found.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
public ObjectContext GetObjectContextForKey(string key)
{
ObjectContext context;
if (!this.storage.TryGetValue(key, out context))
return null;
return context;
}
/// <summary>
/// Stores the object context into a dictionary using the specified key.
/// If an object context already exists by the specified key,
/// it gets overwritten by the new object context passed in.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="objectContext">The object context.</param>
public void SetObjectContextForKey(string key, ObjectContext objectContext)
{
this.storage.Add(key, objectContext);
}
/// <summary>
/// Returns all the values of the internal dictionary of object contexts.
/// </summary>
/// <returns></returns>
public IEnumerable<ObjectContext> GetAllObjectContexts()
{
return this.storage.Values;
}
}
GenericRepository
public class GenericRepository : IRepository
{
private readonly string _connectionStringName;
private ObjectContext _objectContext;
private readonly PluralizationService _pluralizer = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en"));
private bool _usePlurazation;
/// <summary>
/// Initializes a new instance of the <see cref="GenericRepository<TEntity>"/> class.
/// </summary>
public GenericRepository()
: this(string.Empty, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GenericRepository<TEntity>"/> class.
/// </summary>
/// <param name="connectionStringName">Name of the connection string.</param>
public GenericRepository(string connectionStringName, bool usePlurazation)
{
this._connectionStringName = connectionStringName;
this._usePlurazation = usePlurazation;
}
/// <summary>
/// Initializes a new instance of the <see cref="GenericRepository"/> class.
/// </summary>
/// <param name="objectContext">The object context.</param>
public GenericRepository(ObjectContext objectContext, bool usePlurazation)
{
if (objectContext == null)
throw new ArgumentNullException("objectContext");
this._objectContext = objectContext;
this._usePlurazation = usePlurazation;
}
public TEntity GetByKey<TEntity>(object keyValue) where TEntity : class
{
EntityKey key = GetEntityKey<TEntity>(keyValue);
object originalItem;
if (ObjectContext.TryGetObjectByKey(key, out originalItem))
{
return (TEntity)originalItem;
}
return default(TEntity);
}
public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
{
var entityName = GetEntityName<TEntity>();
return ObjectContext.CreateQuery<TEntity>(entityName).OfType<TEntity>();
}
public IQueryable<TEntity> GetQuery<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
return GetQuery<TEntity>().Where(predicate);
}
public IQueryable<TEntity> GetQuery<TEntity>(ISpecification<TEntity> specification) where TEntity : class
{
return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>());
}
public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
if (sortOrder == SortOrder.Ascending)
{
return GetQuery<TEntity>开发者_运维技巧;().OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
return GetQuery<TEntity>().OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
if (sortOrder == SortOrder.Ascending)
{
return GetQuery<TEntity>().Where(predicate).OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
return GetQuery<TEntity>().Where(predicate).OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
public IEnumerable<TEntity> Get<TEntity>(ISpecification<TEntity> specification, Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
if (sortOrder == SortOrder.Ascending)
{
return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>()).OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>()).OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
public TEntity Single<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().SingleOrDefault<TEntity>(criteria);
}
public TEntity Single<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntityFrom(GetQuery<TEntity>());
}
public TEntity First<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
return GetQuery<TEntity>().FirstOrDefault(predicate);
}
public TEntity First<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>()).FirstOrDefault();
}
public void Add<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
ObjectContext.AddObject(GetEntityName<TEntity>(), entity);
}
public void Attach<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
ObjectContext.AttachTo(GetEntityName<TEntity>(), entity);
}
public void Delete<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
ObjectContext.DeleteObject(entity);
}
public void Delete<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
IEnumerable<TEntity> records = Find<TEntity>(criteria);
foreach (TEntity record in records)
{
Delete<TEntity>(record);
}
}
public void Delete<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
IEnumerable<TEntity> records = Find<TEntity>(criteria);
foreach (TEntity record in records)
{
Delete<TEntity>(record);
}
}
public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class
{
return GetQuery<TEntity>().AsEnumerable();
}
public void Update<TEntity>(TEntity entity) where TEntity : class
{
var fqen = GetEntityName<TEntity>();
object originalItem;
EntityKey key = ObjectContext.CreateEntityKey(fqen, entity);
if (ObjectContext.TryGetObjectByKey(key, out originalItem))
{
ObjectContext.ApplyCurrentValues(key.EntitySetName, entity);
}
}
public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().Where(criteria);
}
public TEntity FindOne<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().Where(criteria).FirstOrDefault();
}
public TEntity FindOne<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntityFrom(GetQuery<TEntity>());
}
public IEnumerable<TEntity> Find<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>());
}
public int Count<TEntity>() where TEntity : class
{
return GetQuery<TEntity>().Count();
}
public int Count<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().Count(criteria);
}
public int Count<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>()).Count();
}
public IUnitOfWork UnitOfWork
{
get
{
if (unitOfWork == null)
{
unitOfWork = new UnitOfWork(this.ObjectContext);
}
return unitOfWork;
}
}
private ObjectContext ObjectContext
{
get
{
if (this._objectContext == null)
{
if (string.IsNullOrEmpty(this._connectionStringName))
{
this._objectContext = ObjectContextManager.Current;
}
else
{
this._objectContext = ObjectContextManager.CurrentFor(this._connectionStringName);
}
}
return this._objectContext;
}
}
private EntityKey GetEntityKey<TEntity>(object keyValue) where TEntity : class
{
var entitySetName = GetEntityName<TEntity>();
var objectSet = ObjectContext.CreateObjectSet<TEntity>();
var keyPropertyName = objectSet.EntitySet.ElementType.KeyMembers[0].ToString();
var entityKey = new EntityKey(entitySetName, new[] { new EntityKeyMember(keyPropertyName, keyValue) });
return entityKey;
}
private string GetEntityName<TEntity>() where TEntity : class
{
// WARNING! : Exceptions for inheritance
if (_usePlurazation)
{
return string.Format("{0}.{1}", ObjectContext.DefaultContainerName, _pluralizer.Pluralize(typeof(TEntity).Name));
}
else
{
return string.Format("{0}.{1}", ObjectContext.DefaultContainerName, typeof(TEntity).Name);
}
}
private IUnitOfWork unitOfWork;
}
I know it will take some time to read through the code, but it would help me miles is somebody looks at it and gives tips on what to do better or where I do not dispose an object.
Also I got a little question: "I would like to put a business layer above this repository, that would keep things like global.asax the same I guess but would need static classes(right?) like a BookProvider which gives me all the data about my book-entities?
Thanks in advance!
The only concrete remark I can give is about disposing the context:
foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
{
if (ctx.Connection.State == System.Data.ConnectionState.Open)
ctx.Connection.Close();
}
ObjectContext
implements IDisposable
, so the standard way would be in my opinion:
foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
ctx.Dispose();
As far as I know ObjectContext.Dispose()
just closes the connection, so it does the same what you are doing. But I'd consider this as an internal implementation detail which might potentially change between EF releases.
Your generic repository is one as there are many of this kind. A few points which came to my mind when looking at the methods:
Since you are exposing
IQueryable
inpublic IQueryable<TEntity> GetQuery<TEntity>(...)
why do you need most of the other methods likeSingle
,First
,Count
, etc.? (Why notAny
, etc.?) You get all this from yourIQueryable
.Your
Update
method only works for scalar properties. But this is a common problem with generic repositories. There is no easy solution or no solution at all for updating entities in a generic way.What is your goal you want to reach by using the repository pattern at all? If you have unit testability with an in-memory data store in mind, you cannot expose
IQueryable
because LINQ to Entities and LINQ to Objects are not the same. To test if your IQueryables work you need integration tests and the real database your application is supposed to work with in production. But if you don't exposeIQueryable
your repository needs a lot of business specific methods which return the results as POCOs, POCO collections or DTOs of projected/selected properties and hide the internal query specifications so that you can mock these methods with in-memory data to test your business logic. But this is the point where a generic repository is not sufficient anymore. (How are you going to write, for instance, a LINQJoin
where more than one Entity/ObjectSet is involved in a repository which has only one entity type as generic parameter?)
If you ask ten people how their repositories are architectured, you'll get ten different answers. And none is really wrong or the best because it depends on the application you are going to build with this repository. I believe that nobody can tell you what your repository is really worth. You will see it in practice when you start writing an application. For some applications it might be over-architectured (which I consider as most dangerous because managing and keeping a meaningless architecture under control is expensive and a waste of time you lose for writing real application content). And for other needs you will likely have to extend the repository. For example:
How do you handle explicit loading or queries on navigation properties of an entity (with
CreateSourceQuery
orDbEntityEntry.Collection/Reference
in EF 4.1)? If your application never needs explicite loading: Fine. If it's necessary you need to extend your repo.How do you control eager loading? Sometimes perhaps you just want a parent entity. Sometimes you want to
Includ
the children1 collection and sometimes the child2 reference.How do you set the entity state of an entity manually? Perhaps you never must. But in the next application it might be very helpful.
How do you detach an entity from the context manually?
How do you control loading behaviour (with or without tracking of entities by the context)?
How do you control manually lazy loading behaviour and creation of change tracking proxies?
How do you create an entity proxy manually? You might need it in some situations when you work with lazy loading or change tracking proxies.
How do you load entities into the context without building a result collection? Yet another repository method, perhaps... or perhaps not. Who knows in advance what your application logic will require.
And so on and so forth...
精彩评论