0

How to use EF Core Fluent API to get inheritance properties?

I have a base class and 2 inherited children classes:

class BaseClass
{
    public string Name { get; set; }
}

class Child1 : BaseClass
{
   public string Foo { get; set; }
}

class Child2 : BaseClass
{
   public string Bar { get; set; }
}

class DBContext
{
   ...
   public List<BaseClass> BaseClasses { get; set; }
   ...

   protected override void OnModelCreating(ModelBuilder modelBuilder)
   {
      modelBuilder.Entity<BaseClass>(entity =>
      {
        entity.HasDiscriminator<string>("Discriminator")
                    .HasValue<Child1>(nameof(Child1))
                    .HasValue<Child2>(nameof(Child2))
                    .IsComplete(false);

        entity.Property("Discriminator")
                .IsUnicode(false)
                .HasMaxLength(60);

      });
   }
}

But the following query:

_dBContext.BaseClasses
          .Where(x => x is Child2 && EF.Property<string>(x, "Bar") == "ABC")
          .ToList();

Throws the following error:

Translation of 'EF.Property(StructuralTypeShaperExpression: BaseClasses
ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False , "Bar")' failed. Either the query source is not an entity type, or the specified property does not exist on the entity type.

It looks the issue is in IsNullable, but I have no idea how to fix it.

Thanks

3
  • 1
    Does using Queryable.OfType<TResult> help you here? E.g. _dbContext.BaseClasses.OfType<Child2>().Where(x => x.Bar == "ABC")) Commented Jun 25 at 16:14
  • How about .Where(x => x is Child2 && ((Child2)x).Bar == "ABC")? Because c# cast is the typical way of accessing derived members in EFC. Or EF.Property<string>((Child2)x, "Bar"). Looks like EFC is trying to bind property Bar to BaseClass which of course doesn't exist. Commented Jun 25 at 19:12
  • You can also expose DbSets of the inherited types, for instance DbSet<Child1> Child1s { get; protected set; } etc. Commented Jun 25 at 20:42

2 Answers 2

1

To get around this, you'll want break apply two distinct filters. The first will get a collection of Child2 types. The second will filter that collection on the Bar value.

_dBContext.BaseClasses // returns a `IQueryable<BaseClass>`
  .OfType<Child2>() // returns a `IQueryable<Child2>`
  .Where(x => x.Bar == "ABC";

Breaking the lambda into its consituent parts would look like this:

IQueryable<BaseClass> baseCollection = _dBContext.BaseClasses;
IQueryable<Child2> child2Collection = baseCollection.OfType<Child2>();
IEnumerable filteredCollection = child2Collection.Where(x => x.Bar == "ABC";

EDIT: Posted before I noticed the comment from nbokmans. He deserves the credit.

Sign up to request clarification or add additional context in comments.

Comments

0

For me this Where filter worked perfectly:

var children = await dbContext.BaseClasses
    .Where(x => x is Child2 && (x as Child2).Bar == "ABC")
    .ToArrayAsync();

You could even define conveniency method WhereChild to simplify this:

public static class Extensions
{
    public static IQueryable<TSource> WhereChild<TSource, TChild>(
        this IQueryable<TSource> source, 
        Expression<Func<TChild, bool>> predicate)
        where TChild : class, TSource
    {
        var parameter = Expression.Parameter(typeof(TSource), "x");

        var isType = Expression.TypeIs(parameter, typeof(TChild)); // x is TChild

        var cast = Expression.TypeAs(parameter, typeof(TChild));   // (TChild)x

        var invokedPredicate = Expression.Invoke(predicate, cast); // predicate((TChild)x)

        var andExpression = Expression.AndAlso(isType, invokedPredicate);

        var lambda = Expression.Lambda<Func<TSource, bool>>(andExpression, parameter);

        return source.Where(lambda);
    }
}

and the usage would be

var children = await dbContext.BaseClasses
    .WhereChild((Child2 c) => c.Bar == "ABC")
    .ToArrayAsync();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.