0
votes

I'm attempting to conditionally include or exclude a navigation property when querying Database using DbSet<T> as follows:

var recordList = await dbContext.DbSet<T>
.Include(i => i.NavigationProp ?? null)
.AsNoTracking()
.ToListAsync();

I would like to exclude the Navigation property if it is null but include it when there is a value in the database. Any attempt to achieve this throws exception:

Message = "The Include property lambda expression 'i => (i.FeedbackImage ?? null)' is invalid. The expression should represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, specify an explicitly typed lambda parameter of the....

Why does this .Include(i => i.NavigationProp ?? null) fail when Include(Expression<Func<T, T>>) method accepts an Expression?

2
How do you know to exclude it when the only way to get to know not to include it is to include it? 😆 – JohnyL
@JohnyL well that is the mystery this question is trying to demystify, is there a way to conditionally include or exclude navigation properties? – yaddly
@JohnyL I'm trying to check if the Navigation property has a value, if true then it must be included else ignored. – yaddly
I can't figure out what you mean by "excluding" the navigation property. Do you want to add an Include statement conditionally (that is, when class T has this property)? Or do you want to exclude it from T instances? The latter wouldn't be necessary because Include would result in 'null` navigation properties when the property is a reference or otherwise empty collections. – Gert Arnold

2 Answers

1
votes

It fails because Include(Expression<Func<T, T>>) gets actually translated to Include(string) internally and eventually Include(string) will need a property name and that's what the error is telling you, that's how entity framework is doing it.

Update:

Certainly not an elegant solution but you could try something like this:

Adding the navigation property as an interface to your models:

public interface IHasNavigationProperty
{
    public NavigationProp NavigationProp { get; set; }
}

Models will implement it:

public class MyModel : IHasNavigationProperty
{
    public NavigationProp NavigationProp { get; set; }
}

And a generic method that will check for that interface and execute the your include if the class implements it:

IList<T> GetRecords<T>() where T : class
{
    var hasNavigationPropertyInterface = typeof(IHasNavigationProperty).IsAssignableFrom(typeof(T));
    var query = _context.Set<T>().AsQueryable();
    if (hasNavigationPropertyInterface)
    {
        var navigationPropertyName = nameof(NavigationProp);
        query = query.Include(navigationPropertyName);
    }
    var recordList = query.AsNoTracking()
        .ToList();
    return recordList;
}

Update 2 :

Thinking about it you can just check for the property name instead of adding an interface:

private IList<T> GetRecords<T>() where T : class
{
    var hasProperty = typeof(T).GetProperty(nameof(NavigationProp)) != null;
    var query = _context.Set<T>().AsQueryable();
    if (hasProperty)
    {
        var navigationPropertyName = nameof(NavigationProp);
        query = query.Include(navigationPropertyName);
    }
    var recordList = query.AsNoTracking()
        .ToList();
    return recordList;
}
1
votes

I'm trying to check if the Navigation property has a value, if true then it must be included else ignored.

So just

var recordList = await dbContext.DbSet<T>
.Include(i => i.NavigationProp)
.AsNoTracking()
.ToListAsync();

The Navigation Property will be loaded, unless the related Foreign Key value is null, in which case it will be Null.