I have an Entity Framework DbContext where some of the entities are just a single class, others are members of a class hierarchy.
I also have a type named Status, for which I defined a ValueConverter convention in DbContext. Both the single class entity, and one of classes in the hierarchy have properties of type Status.
When I query the DbSet for the single class, and specify a Where clause that references the property that should be value converted, everything’s fine.
But, if I query the DbSet for the class hierarchy, specifying OfType<>() and Where(), and referencing the property that should be value converted, the generated MQL ignores the value conversion, and the query fails. Proof of concept below; I can provide an entire, running .NET project if you need it.
Using .NET 8, MongoDB.EntityFrameworkCore 8.2.0, MongoDB Community 7.0.14
public class CarBrand
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public Status Status { get; set; } = Status.UNKNOWN;
}
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
}
public class Teacher : Person
{
public int NumberOfStudents { get; set; }
}
public abstract class Doctor : Person
{
public string LicenseNumber { get; set; } = string.Empty;
public Status Status { get; set; } = Status.UNKNOWN;
}
public class SpecialistDoctor : Doctor
{
public string Speciality { get; set; } = string.Empty;
}
public class TraineeDoctor : Doctor
{
public string MentorName { get; set; } = string.Empty;
}
public class Status
{
public static readonly Status UNKNOWN = new(nameof(UNKNOWN));
public static readonly Status INACTIVE = new(nameof(INACTIVE));
public static readonly Status ACTIVE = new(nameof(ACTIVE));
public static IEnumerable<Status> GetAll()
{
yield return UNKNOWN;
yield return INACTIVE;
yield return ACTIVE;
}
public static Status FromName(string name)
{
foreach (Status status in GetAll())
{
if (status.Name == name)
{
return status;
}
}
throw new InvalidOperationException($"No such status '{name}'");
}
private Status(string name)
{
Name = name;
}
public string Name { get; }
}
public class StatusConverter : ValueConverter<Status, string>
{
public StatusConverter() : base(s => s.Name, s => Status.FromName(s))
{
}
}
public class MyDbContext(DbContextOptions options) : DbContext(options)
{
public DbSet<CarBrand> CarBrands { get; set; }
public DbSet<Person> Persons { get; init; }
public static MyDbContext Create(IMongoDatabase database)
{
var options = new DbContextOptionsBuilder<MyDbContext>()
.UseMongoDB(database.Client, database.DatabaseNamespace.DatabaseName)
.EnableSensitiveDataLogging()
.LogTo(Console.WriteLine)
.Options;
return new MyDbContext(options);
}
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
base.ConfigureConventions(configurationBuilder);
configurationBuilder.Properties<Status>().HaveConversion<StatusConverter>();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<CarBrand>()
.ToCollection(nameof(CarBrands))
.HasKey(c => c.Id);
modelBuilder.Entity<Person>()
.ToCollection(nameof(Persons))
.HasKey(p => p.Id);
modelBuilder.Entity<Teacher>();
modelBuilder.Entity<Doctor>();
modelBuilder.Entity<TraineeDoctor>();
modelBuilder.Entity<SpecialistDoctor>();
}
}
// Find active car brands - works correctly
var activeBrands = dbContext.CarBrands.Where(d => d.Status == Status.ACTIVE).ToList();
// Generated MQL query (correct):
// myDatabase.CarBrands.aggregate([{ "$match" : { "Status" : "ACTIVE" } }])
// Find active doctors - FAILS
var activeDoctors = dbContext.Persons.OfType<Doctor>().Where(d => d.Status == Status.ACTIVE).ToList();
// Generated MQL query (WRONG) - notice the Status match:
// myDatabase.Persons.aggregate([{ "$match" : { "_t" : { "$in" : ["Doctor", "SpecialistDoctor", "TraineeDoctor"] } } }, { "$match" : { "Status" : { } } }])