When projecting database models to DTOs with inheritance before applying OData queries, expressions are generated to determine type that are not compatible with Entity Framework 6.
Assemblies affected
Microsoft.AspnetCore.OData (7.4.0)
Reproduce steps
Database models and API DTOs are defined as follows:
public class Animal
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class Cat : Animal { }
public class Dog : Animal { }
public class CatDTO : AnimalDTO { }
public class DogDTO : AnimalDTO { }
public class AnimalDTO
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
Startup is configured like so:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddNewtonsoftJson();
services.AddOData();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
endpoints.Select()
.Filter()
.OrderBy()
.Count()
.MaxTop(10);
});
}
IEdmModel GetEdmModel()
{
var odataBuilder = new ODataConventionModelBuilder();
odataBuilder.EntitySet<AnimalDTO>("Animal");
odataBuilder.EntitySet<CatDTO>("Cat")
.EntityType.DerivesFrom<AnimalDTO>();
odataBuilder.EntitySet<DogDTO>("Dog")
.EntityType.DerivesFrom<AnimalDTO>();
return odataBuilder.GetEdmModel();
}
Controller is defined like:
public class AnimalController : ODataController
{
[HttpGet]
[EnableQuery]
public IQueryable<AnimalDTO> Get()
{
var context = new MyContext();
var animals= context.Animals.Select(a => new AnimalDTO
{
Id = a.Id,
Name = a.Name
});
return animals;
}
}
System.NotSupportedException: The 'TypeIs' expression with an input of type 'WebApplication.AnimalDTO' and a check of type 'WebApplication.DogDTO' is not supported. Only entity types and complex types are supported in LINQ to Entities queries.
Additional detail
I understand that the underlying cause is the OData package generating TypeIs expressions based on the inheritance structure as defined in the Edm, which would work fine if the entities were the Enitity Framework database entities and not DTOs.
I also understand that EF limitations prevent the items from being materialized as separate more derived DTO types and that there seems to be no way to get EF to include type information without using TypeIs expressions.
What I am wondering is whether it would be possible to somehow override Odata's type determination expressions to allow me to use my own mapped discriminator field or some other logic to determine the more derived types so that this error is not thrown and the "@odata.type"field can be properly included.
For example in this case a discriminator field could be computed like this during the projection:
context.Animals.Select(a => new AnimalDTO
{
Id = a.Id,
Name = a.Name,
Discriminator = a as Cat != null ? "Cat" : a as Dog != null ? "Dog" : null
});
Could it be possible to configure the OData library to use this Discriminator field to compute "@odata.type" instead of attempting to use the TypeIs expressions or reflection after materialization? How feasible would it be to allow for this configuration?
When projecting database models to DTOs with inheritance before applying OData queries, expressions are generated to determine type that are not compatible with Entity Framework 6.
Assemblies affected
Microsoft.AspnetCore.OData (7.4.0)
Reproduce steps
Database models and API DTOs are defined as follows:
Startup is configured like so:
Controller is defined like:
A request is made to endpoint:
/odata/animal?$select=name
Expected result
Response should have body like:
Actual result
The following exception is thrown:
System.NotSupportedException: The 'TypeIs' expression with an input of type 'WebApplication.AnimalDTO' and a check of type 'WebApplication.DogDTO' is not supported. Only entity types and complex types are supported in LINQ to Entities queries.
Additional detail
I understand that the underlying cause is the OData package generating TypeIs expressions based on the inheritance structure as defined in the Edm, which would work fine if the entities were the Enitity Framework database entities and not DTOs.
I also understand that EF limitations prevent the items from being materialized as separate more derived DTO types and that there seems to be no way to get EF to include type information without using TypeIs expressions.
What I am wondering is whether it would be possible to somehow override Odata's type determination expressions to allow me to use my own mapped discriminator field or some other logic to determine the more derived types so that this error is not thrown and the
"@odata.type"
field can be properly included.For example in this case a discriminator field could be computed like this during the projection:
Could it be possible to configure the OData library to use this Discriminator field to compute
"@odata.type"
instead of attempting to use the TypeIs expressions or reflection after materialization? How feasible would it be to allow for this configuration?