FindAsync and Include LINQ statements

70,393

Solution 1

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();    

Solution 2

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;
    }
}

Solution 3

When you program using solid principles and domain design then use generics. The Repository pattern uses a generic class. I pass a lambda express to the GetObjectsQueryable function. I have setup lazy loading to be on, using code first handle bars. However, I am moving away from lazy loading and implement a microservice architecture. The include table is a string and you can use the nameof(xxclass) function to ensure the correct name. The function returns and IQueryable results. The repository class methods can be used by its derived class enhance the method is protected. This is a dotnet.core demonstration.

public class Repository
    where T : class
{
    public IQueryable<T> GetObjectsQueryable(Expression<Func<T, bool>> predicate, string includeTable="")
    {
        IQueryable<T> result = _dbContext.Set<T>().Where(predicate);
        if (includeTable != "")
            result = result.Include(includeTable);

        return result;
    }
}
Share:
70,393
NoWar
Author by

NoWar

[email protected]

Updated on July 08, 2022

Comments

  • NoWar
    NoWar almost 2 years

    The code I have got so far works fine

    public async Task<ActionResult> Details(Guid? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
    
        ItemDetailModel model = new ItemDetailModel();
        model.Item = await db.Items.FindAsync(id);
        if (model.Item == null)
        {
            return HttpNotFound();
        }
    
        return View(model);
    }
    

    But I want to include 1 table more and cannot use FindAsync

    public async Task<ActionResult> Details(Guid? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
    
        ItemDetailModel model = new ItemDetailModel();
        model.Item = await db.Items.Include(i=>i.ItemVerifications).FindAsync(id);
    
        if (model.Item == null)
        {
            return HttpNotFound();
        }           
    
        return View(model);
    }
    

    So I am facing this error

    Severity Code Description Project File Line Suppression State Error CS1061 'IQueryable' does not contain a definition for 'FindAsync' and no extension method 'FindAsync' accepting a first argument of type 'IQueryable' could be found (are you missing a using directive or an assembly reference?)

    Any clue how to fix it?