I started to use Repository pattern with my exercise data base and until now everything was fine with this implementation of Get method:
public T Get(int id)
{
return _context.Set<T>().Find(id);
}
This mehtod simply returns an object of given ID. The problem appeared when the returned object contained some navigation properties - using my Get they were alwys null. Using http://www.janholinka.net/Blog/Article/9 I modified my Get a little:
public T Get(int id, params Expression<Func<T, object>>[] includes)
{
IQueryable<T> query = _context.Set<T>();
if (includes != null)
foreach (Expression<Func<T, object>> include in includes)
query = query.Include(include);
return ((DbSet<T>)query).Find(id);
}
Now I have an array of include expressions as an additional parameter, so I should be able to use this method like this (for example):
var data = myRepository.Get(1, n => n.MyProperty1);
Unforutnately I get an exception on the last line of my method:
'Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery`1[MjIot.Storage.Models.EF6Db.Device]' to type 'System.Data.Entity.DbSet`1[MjIot.Storage.Models.EF6Db.Device]'.'
It isn't able to cast DbQuery to DbSet. I tried many different casts, already my method differs a little from what was provided on the janholinka site, since the method given there didn't even compile for me.
Entity Framework is still kind of magical for me with its DbSets, DbQueries, etc.. I wasn't able to come up with a solution for this.
The simplest is to use FirstOrDefaultAsync
or SingleOrDefaultAsync
instead:
model.Item = await db.Items.Include(i => i.ItemVerifications)
.FirstOrDefaultAsync(i => i.Id == id.Value);
The reason you are getting the error is because Find
/ FindAsync
methods are defined for DbSet<T>
, but the result of Include
is IQueryable<T>
.
Another way is to combine FindAsync
with explicit loading:
model.Item = await db.Items.FindAsync(id);
if (model.Item == null)
{
return HttpNotFound();
}
await db.Entry(model.Item).Collection(i => i.ItemVerifications).LoadAsync();
If you are using a generic repository and you don't know the PK at runtime, this approach can help:
public interface IGenericRepository<TEntity> where TEntity : class
{
Task<TEntity> Get(int id, string[] paths = null);
}
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
private readonly ApplicationDbContext _context;
private readonly DbSet<TEntity> _dbSet;
public GenericRepository(ApplicationDbContext context)
{
_context = context;
_dbSet = _context.Set<TEntity>();
}
public async Task<TEntity> Get(int id, string[] paths = null)
{
var model = await _dbSet.FindAsync(id);
foreach (var path in paths)
{
_context.Entry(model).Reference(path).Load();
}
return model;
}
}