I implement repository pattern in my current web api project with EF6. Currently, I have the following function (in CustomerRepository) that returns a customer:
public override Customer Get(int id, params Expression<Func<Customer , object>>[] include)
{
if (include.Any())
{
var set = include.Aggregate<Expression<Func<Customer , object>>, IQueryable<Customer >>
(dbset, (current, expression) => current.Include(expression));
return dbset.SingleOrDefault(x => x.Id == id)
}
return dbset.Find(id);
}
This works fine, but I would like to move the above method in my generic repository. The problem here is SingleOrDefault, because Id wouldn't be known for T.
Is there a way to solve this? Do I need to implement an interface?
As a side not, the first property of ALL of my entities is 'int Id'.
This works fine,
Hmmm, are you sure about that:
return dbset.SingleOrDefault(x => x.Id = id)
shouldn't this be:
return dbset.SingleOrDefault(x => x.Id == id)
Anyway, if all your entities have an integer Id property, why not have them all implement an interface:
public interface IEntity
{
int Id { get; }
}
and then you will constrain your generic T parameter to this interface in the base repo and you will know how to write the lambda.
EF6 has a Find() method that sort of does what you need.
http://msdn.microsoft.com/en-us/library/gg696418(v=vs.113).aspx
public T Get(int id)
{
return dbset.Find(id);
}
Don't worry about it loading more joined tables (includes) than needed. It's not that big of a deal for a single record. EDIT: sorry, Find does not actually do any eager loading of its own. Related entities are only returned if they were already tracked by the context.
Related
I've noticed that oData will automatically include variables for collections.
How can I make this work the same way for an oData query that returns one item?
For example, if I add "$expand=Customer" to this call. It will work with out me explicitly doing so in linq (.Include(p => p.Customer)
public IEnumerable<ProjectEntity> Get()
{
return _db.Projects;
}
But this code will not automatically include project with the same syntax:
public ProjectEntity GetProjectById(int id)
{
return _db.Projects.Where(p => p.Id == id).FirstOrDefault();
}
This is often misunderstood about OData, you need to return an IQueryable<T> expression for the $expand query option to be dynamically applied to your response object, there is a special wrapper for this SingleResult<T>.
The key is that you do not want to materialize the expression using .FirstOrDefault() but we still want the response to be a constrained to a single object in the JSON response instead of an array.
[EnableQuery]
public SingleResult<ProjectEntity> GetProjectById(int id)
{
return SingleResult.Create(_db.Projects.Where(p => p.Id == id));
}
If you didn't want to use SingleResult<T> then a hack would be to pre-emptively load the navigation property by Including it in the query.
public ProjectEntity GetProjectById(int id)
{
return _db.Projects.Include(p => p.Customer)
.Where(p => p.Id == id)
.FirstOrDefault();
}
I am using Repository pattern to develop my project using Nhibernate. How to do complex join in Nhibernate. I am using below repository method for fetching data
public virtual TEntity FindBy(Expression<Func<TEntity, bool>> query)
{
try
{
return NHUnitOfWork.Session.Query<TEntity>().Where(query).FirstOrDefault();
}
catch (Exception ex)
{
throw;
}
}
Here I can fetch data without any joins. How to fetch data with joins?
for eg: Menu details are storing in Menu table and Menu Rights are storing to Menu Rights table. How to create a repository for that?
With QueryOver, accepting input Junction instead of Expression to your FindBy method will do.
Following code may be helpful. Remove columnList and top parameters if not needed to you. Please note that my ISession usage is different. You need to replace nhSession with NHUnitOfWork.Session. Another difference is that you are using Query and I am using QueryOver here.
public virtual IList<T> FindBy<T>(ProjectionList columnList, Junction where, int top) where T : BaseEntity
{
IList<T> instance = GetQueryOver<T>(columnList, where).Take(top).List();
return instance;
}
public virtual IQueryOver<T> GetQueryOver<T>(ProjectionList columnList, Junction where) where T : BaseEntity
{
IQueryOver<T> query = null;
if((columnList != null) && (where != null))
{
query = nhSession.QueryOver<T>()
.Select(columnList)
.TransformUsing(Transformers.AliasToBean<T>())
.Where(where);
}
else if((columnList != null) && (where == null))
{
query = nhSession.QueryOver<T>()
.Select(columnList)
.TransformUsing(Transformers.AliasToBean<T>());
}
else if((columnList == null) && (where != null))
{
query = nhSession.QueryOver<T>()
.Where(where);
}
else
{
query = nhSession.QueryOver<T>();
}
return query;
}
And then, you can call this at some other location like:
Junction where = Restrictions.Conjunction();
where.Add(Restrictions.Eq(Projections.Property<MyEntity>(x => x.Field), findValue));
where.Add(Restrictions.................);
where.Add(Restrictions.................);
entityList = Repository.FindBy<MyEntity>(null, where, 100);
If you want to eager load related entities, use Fetch and FetchMany.
public virtual Menu FindBy(Expression<Func<Menu, bool>> query)
{
return NHUnitOfWork.Session.Query<Menu>().Where(query)
.FetchMany(m => m.Rights)
// Required with FetchMany and First, otherwise only one right would be loaded.
.ToList()
.FirstOrDefault();
}
Or if your model has only one right per menu:
public virtual Menu FindBy(Expression<Func<Menu, bool>> query)
{
return NHUnitOfWork.Session.Query<Menu>().Where(query)
.Fetch(m => m.Right)
.FirstOrDefault();
}
But it seems you want to define some "generic repository" encapsulating the NHibernate API.
Maybe then:
public virtual TEntity FindBy<TFetched>(Expression<Func<TEntity, bool>> query,
Expression<Func<TEntity, IEnumerable<TFetched>>> fetchMany)
{
var query = NHUnitOfWork.Session.Query<TEntity>().Where(query);
if (fetchMany != null)
query = query.FetchMany(fetchMany);
return query
// Required with FetchMany and First, otherwise only one right would be loaded.
.ToList()
.FirstOrDefault();
}
Or if your model has only one right per menu:
public virtual TEntity FindBy<TFetched>(Expression<Func<TEntity, bool>> query,
Expression<Func<TEntity, TFetched>> fetch)
{
var query = NHUnitOfWork.Session.Query<TEntity>().Where(query);
if (fetch != null)
query = query.Fetch(fetch);
return query
.FirstOrDefault();
}
But how to do if you need many fetches*? And sub-fetches (ThenFetch/ThenFetchMany)? It looks to me like a cursed path. You may end up coding encapsulation for the whole NHibernate API following that path.
Indeed, exposing publicly an Expression where argument on repositories does not look good to me. Your repository is then no more responsible of defining how the data is queried.
If you want to do that, why not exposing directly the IQueryable? It would prove far less code verbose than trying to encapsulate it in a "repository" while still defining the query outside of this "repository".
*: beware of Cartesian products in case you do more than one FetchMany. To avoid it, either split your query in many (using ToFuture if you wish a single round-trip to DB), or use lazy loading with batching instead of fetching.
I am writing a generic repository to interface with EF using DBContext.
I have a generic Get() method which receives a primary key value and returns the entity:
public class DALRepository<DALEntity> : IDisposable, IGenericRepository<DALEntity> where DALEntity : class
{
private IDbSet<DALEntity> dbSet;
private NWEntities context;
public DALRepository()
{
context = new NWEntities();
context.Configuration.LazyLoadingEnabled = false;
dbSet = context.Set<DALEntity>();
}
Here's a simple get method - just works on the PK - exactly what I want.
public DALEntity Get(string ID)
{
return dbSet.Find(ID);
}
I now want to change this to allow the consumer to pass in a list of includes - so as well as returning just a customer they can request to return the orders as well. Here's where I'm running into trouble. If I do this:
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
}
I can't use find with the IQueryable. And I can't Find() directly because I can't pass includes to it. If I use a where lambda instead on the IQueryable, how do I tell it to use the PK of the entity? I guess I could have a generic constraint that insists the generic type must implement some IPkey interface with a well-defined primary column name such as "ID", but then I can't use the entities generated by DBContext as they woudl have to implement this interface. I can change the T4 to do this if I need - and I've already changed it to emit XML comments so not too averse to that - but does anyone have a simpler way? I suppose what I need is an overloaded find() which accepts a list of includes.
So my question is either how to use Find with includes, or how to write the lambda where it knows the PK? I can't receive such a lambda as a parameter as this will ultimately be consumed by a WCF service.
Kind of wierd answering your own question but in case anyone else has this issue here's what I did. I used the dynamic LINQ stuff and used the string overloaded version of .Where(). I used one of the links mentioned to figure out how to grab the primary key (and mine is from a DBContext as well), and the method now looks like this:
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
var set = ((IObjectContextAdapter)context).ObjectContext.CreateObjectSet<DALEntity>();
var entitySet = set.EntitySet;
string[] keyNames = entitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray();
Debug.Assert(keyNames.Length == 1, "DAL does not work with composite primary keys or tables without primary keys");
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
query = query.Where(keyNames[0] + "= #0", ID);
return query.FirstOrDefault();
}
You could get your Key dinamycally the way it is described here : https://stackoverflow.com/a/10796471/971693
That being said, I can't see a way without using some reflection :
public IEnumerable<DALEntity> Get(params string IDs)
{
var objectSet = objectContext.CreateObjectSet<YourEntityType>();
var keyNames = objectSet.EntitySet.ElementType.KeyMembers.First(k => k.Name);
return dbSet.Where(m => ID.Contains((string)m.GetType().GetProperty(keyNames ).GetValue(m, null));
}
First of, params string IDs will let you pass 1 or more ID and will result in an array of string.
The first part of the function is to dynamically get the name of your primary key.
The second part creates a query to return all elements from your set where the primary key value (obtained through reflection) is contained within the array of IDs received in parameter.
Use Linq's Single or First methods, which allow you to search on IQueryable objects.
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
query = query.Single(x=>x.Id == ID);
}
I have two Entity Framework 5 Get() methods that perform (i) a single entity get by ID, and (ii) a single entity get via a filter with any eager loading bolted on. See below for the code:
internal readonly FallenNovaContext Context;
private readonly DbSet<TEntity> _dbSet;
internal GenericRepository(FallenNovaContext context)
{
Context = context;
_dbSet = context.Set<TEntity>();
}
// (i) Get by ID.
public TEntity GetById(int id)
{
return _dbSet.Find(id);
}
// (ii) Get by filter and optional eager loading includes.
public TEntity Get(
Expression<Func<TEntity, bool>> filter = null,
IEnumerable<string> includePaths = null)
{
IQueryable<TEntity> query = _dbSet;
if (filter != null)
{
query = query.Where(filter);
}
if (includePaths != null)
{
query = includePaths.Aggregate(query, (current, includePath) => current.Include(includePath));
}
return query.SingleOrDefault();
}
All of which works fine now what I'm finding as my application grows is I'm writing a lot of non-generic methods that need a mix of both - more specifically I want a generic get by ID and also be able to eager load related entities.
So the method signature would look something like this:
public TEntity GetById(
int id,
IEnumerable<string> includePaths)
{
// ???
}
Which I could call like this:
User user = UnitOfWork.UserRepository.GetById(117, new List<string>() { "UserRole", "UserStatus" });
Or like this:
Car car = UnitOfWork.CarRepository.GetById(51, new List<string>() { "Make", "Model", "Tyres" });
Any help on the suggestions of how I use Entity Framework 5 to code the logic for the TEntity GetById(int id, IEnumerable includePaths) method would be appreciated.
First, write a base class for entities, which defines the primary key field. Something like the following may work:
public abstract class BaseEntity
{
public int Id {get;set;}
}
Then, write a base class for your repositories; define all generic methods in this base repository. Let this repository have a generic parameter of entity type:
public class RepositoryBase<TEntity> where TEntity : BaseEntity
{
public TEntity GetById(
int id,
params Expression<Func<TEntity, object>>[] includeList)
{
TEntity entity = null;
ObjectQuery<TEntity> itemWithIncludes = context.Set<TEntity>() as ObjectQuery<TEntity>;
foreach (Expression<Func<TEntity, object>> path in includeList)
{
itemWithIncludes = ((IQueryable)itemWithIncludes.Include(path)) as ObjectQuery<T>;
}
IQueryable<TEntity> items = itemWithIncludes.AsQueryable<TEntity>();
entity = items.Where(p => p.Id == id).SingleOrDefault();
return entity;
}
}
Update: #Bern asked whether there is any other way to find primary key than declaring a base class. The following questions refer to this problem.
Entity Framework 4: How to find the primary key?
Entity Framework code first. Find primary key
On the otherhand I do not know if there is any other way in EF 5.
when i try to attach entity to context i get an exception
An object with the same key already
exists in the ObjectStateManager. The
ObjectStateManager cannot track
multiple objects with the same key
This is expected behaviour.
But i would like to know how ObjectStateManager knows that?
I would like to do this check by myself before
If you are using DbContext API (you mentioned ef-code-first) you can simply use:
context.YourEntities.Local.Any(e => e.Id == id);
or more complex
context.ChangeTracker.Entries<YourEntity>().Any(e => e.Entity.Id == id);
In case of ObjectContext API you can use:
context.ObjectStateManager.GetObjectStateEntries(~EntityState.Detached)
.Where(e => !e.IsRelationship)
.Select(e => e.Entity)
.OfType<YourEntity>()
.Any(x => x.Id == id);
Here's an extension method for getting the object from the context without having to worry about whether it is already attached:
public static T GetLocalOrAttach<T>(this DbSet<T> collection, Func<T, bool> searchLocalQuery, Func<T> getAttachItem) where T : class
{
T localEntity = collection.Local.FirstOrDefault(searchLocalQuery);
if (localEntity == null)
{
localEntity = getAttachItem();
collection.Attach(localEntity);
}
return localEntity;
}
Just call:
UserProfile user = dbContext.UserProfiles.GetLocalOrAttach<UserProfile>(u => u.UserId == userId, () => new UserProfile { UserId = userId });
check
entity.EntityState == System.Data.EntityState.Detached
before attaching
Note that if change tracking is disabled on your context, asking the ObjectStateManager or the ChangeTracker might return that the object is not in the ObjectContext even if it is in fact already in there. Therefore, if you try to attach such object it will raise an exception.
context.Set<T>().Local.Any(e => e.Id == id);
works event if change tracking is disabled.
if you do not know the type of the object, there is various approach, either you define a method using reflection or other techniques like this one
int GetIdOf(object entity){...}
Or you define an interface used by your classes like
public interface IMyEntity
{
int Id{get;set;}
}
and use it this way :
context.Set(e.GetType()).Local.Cast<IMyEntity>().Any(e => e.Id == id);
you can query the dbContext with the "Any" extension method:
bool alreadyInDB = dbContext.Entity.Where(a=>a.ID==myEntity.id).Any();
If you have arrived here, as I did, from an EF Core Lazy Loading scenario in which Navigation properties were filled in a data layer via DbSet.Include() clause(s) while the Entity was attached to a DbContext and then that Entity was detached and passed up to a business layer, consider adding something like this to your DbContext.OnConfiguring(DbContextOptionsBuilder optionsBuilder) method:
optionsBuilder.ConfigureWarnings(warn => warn.Ignore(CoreEventId.LazyLoadOnDisposedContextWarning));
The error will be ignored and the values that were originally Include()d will be returned.