问题
I'm new to Entity Framework and joined a project that uses a generic repository as shown below. Are there any disadvantages to the way the repository is set up? I've noticed that most tutorials describe creating multiple repositories based on a generic base repository rather than having one generic repository that handles everything.
To give some background this code is part of an ASP.NET MVC 3 website and we're using unity as an IOC container. All of the components in the business layer inherit from a base class that has has the IEntityRepository injected via the constructor.
This is the generic repository class
public class MyRepository
{
private const string containerName = "myEntities";
private readonly ObjectContext context;
private readonly Hashtable objectSets;
// Track whether Dispose has been called.
private bool disposed;
public MyRepository()
{
string connectionString = ConfigurationManager.ConnectionStrings[containerName].ConnectionString;
context = new ObjectContext(connectionString) {DefaultContainerName = containerName};
context.ContextOptions.LazyLoadingEnabled = true;
context.ContextOptions.ProxyCreationEnabled = true;
objectSets = new Hashtable();
}
private ObjectSet<TEntity> GetObjectSet<TEntity>() where TEntity : class
{
ObjectSet<TEntity> objectSet;
var type = typeof (TEntity);
if (objectSets.ContainsKey(type))
{
objectSet = objectSets[type] as ObjectSet<TEntity>;
}
else
{
objectSet = context.CreateObjectSet<TEntity>();
objectSets.Add(type, objectSet);
}
return objectSet;
}
public IQueryable<TEntity> GetAll<TEntity>(params string[] entities) where TEntity : class
{
ObjectQuery<TEntity> objectQuery = GetObjectSet<TEntity>();
foreach (var entity in entities)
{
objectQuery = objectQuery.Include(entity);
}
return objectQuery;
}
public void Insert<TEntity>(TEntity entity) where TEntity : class
{
ObjectSet<TEntity> objectSet = GetObjectSet<TEntity>();
objectSet.AddObject(entity);
}
public void Update<TEntity>(TEntity entity) where TEntity : class
{
ObjectSet<TEntity> objectSet = GetObjectSet<TEntity>();
EntityKey key = objectSet.Context.CreateEntityKey(objectSet.EntitySet.Name, entity);
object originalItem;
if (objectSet.Context.TryGetObjectByKey(key, out originalItem))
{
objectSet.ApplyCurrentValues(entity);
}
else
{
objectSet.Attach(entity);
objectSet.ApplyCurrentValues(entity);
}
}
public void Delete<TEntity>(TEntity entity) where TEntity : class
{
ObjectSet<TEntity> objectSet = GetObjectSet<TEntity>();
objectSet.DeleteObject(entity);
}
public void SaveChanges()
{
try
{
context.SaveChanges();
}
catch (Exception ex)
{
ex.ToString();
throw ex;
}
}
public void Dispose()
{
Dispose(true);
// Take yourself off the Finalization queue
// to prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called
if (!disposed)
{
if (disposing)
{
context.Dispose();
}
}
disposed = true;
}
~MyRepository()
{
Dispose(false);
}
}
This is the interface used to expose the methods:
public interface IEntityRepository : IDisposable
{
void Delete<TEntity>(TEntity entity) where TEntity : class;
IQueryable<TEntity> GetAll<TEntity>(params string[] entities) where TEntity : class;
void Insert<TEntity>(TEntity entity) where TEntity : class;
void Update<TEntity>(TEntity entity) where TEntity : class;
void SaveChanges();
}
回答1:
The main disadvantage of using a generic repository instead of having a concrete repository implementation per Aggregate root, is that you cannot create specific methods for specific usages.
Having all your repositories inherit from a base repository let you create methods like: GetProductsInsSock() or things like UpdateOnlyProductsThatSatisfySomething().
But some workarounds are available! ;-)
Keep on using your generic repository if it suits to your team, the only thing you might have to add is a method that accept a Specification as parameter. As Eric Evans and Martin Fowler say:
The central idea of Specification is to separate the statement of how to match a candidate, from the candidate object that it is matched against.
In your case it could work as a filter to retrieve the right entities without having the need to create a specific method.
You could simply add this to your IRepository interface:
IEnumerable<T> Find(Specification<T> predicate);
The method implementation in your Repository class would be like this:
public class Repository<T> : IRepository<T> where T : class
{
public Repository(IDbContext context)
{
_context = context;
_dbset = context.Set<T>();
}
// some code...
public IEnumerable<T> Find(Specification<T> specification)
{
return _dbset.Where(specification.Predicate);
}
// some code...
}
The Specification class could look like this:
public class Specification<T>
{
public Specification(Expression<System.Func<T, bool>> predicate)
{
_predicate = predicate;
}
internal Expression<System.Func<T, bool>> Predicate
{
get { return _predicate; }
}
private readonly Expression<System.Func<T, bool>> _predicate;
}
A call example:
var specification = ProductSpecification.InStock();
var product = Repository.Find(specification).FirstOrDefault();
And finally, the ProductSpecification class:
internal static class ActionSpecification
{
internal static Specification<Product> InStock()
{
return new Specification<Product>(p => p.IsInStock == true);
}
}
来源:https://stackoverflow.com/questions/17224015/entity-framework-4-1-generic-repository-set-up