'How can I filter on a nullable navigation property in a DTO when using OData with .NET Core?
I'm using OData v8 with .NET 5 over Entity Framework. I have defined an entity Hour on my model and configured it in a pretty cookie-cutter way as follows, on my DbContext:
public class Hour
{
// Removed non-related properties
public Guid? CustomerId { get; set; }
public string CustomerName { get; set; }
public Guid Id { get; set; }
}
public class HourConfiguration : IEntityTypeConfiguration<Hour>
{
public void Configure(EntityTypeBuilder<Hour> builder)
{
builder.ToTable("Hours");
builder.HasPrimaryKey();
}
}
The client however requires a slight change in the model (corporate restrictions), so I'm projecting it using .Select() into a DTO which is very similar. The main difference is, since CustomerId (and thus CustomerName) are optional, I'd like to put these properties together in a DTO, here, simply, IdNameDto:
public class IdNameDto
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class HourDto
{
public Guid Id { get; set; } // <- PK of the Hour entity
public IdNameDto Customer { get; set; } // <- can be null
}
The aforementioned projection happens in an endpoint as follows, you'll see I've enabled the necessary OData querying:
[HttpGet("odata/hours")]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IActionResult Get()
{
IQueryable<Hour> query = readDbContext.Hours
.Select(source =>
new HourDto
{
Id = source.Id,
Customer = source.CustomerId != null
? new IdNameDto
{
Id = source.CustomerId.Value,
Name = source.CustomerName
}
: null
});
return new OkObjectResult(query);
}
Note, that since CustomerId is optional, I'm forced to do a null-check to avoid null-ref exceptions - this is probably the culprit.
And finally, the OData mappings, added in ConfigureServices():
internal static class MvcBuilderExtensions
{
internal static IMvcBuilder AddODataModel(this IMvcBuilder builder, int maxTop = 200)
{
return builder.AddOData(options =>
{
options.EnableQueryFeatures(maxTop);
options.AddRouteComponents("odata", GetEdmModel());
});
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();
builder.EntitySet<HourDto>("hours");
return builder.GetEdmModel();
}
}
I'd like to think this is a pretty standard setup. As long as I don't use any filters, this works fine and both Customers with Id=null and Id=<GUID> are returned correctly.
Now the main problem, when I try to use the $filter clause to select a specific Customer by Id as such:
/odata/hours?$filter(Customer/Id eq <GUID>) (GUID is of course an actual GUID), the following error occurs:
I've added the full stacktrace here: https://pastebin.com/r3YRX3FB
The error is quite cryptic, but I suspect it has something to with OData thinking the underlying key is a Guid instead of a Guid?. But even that is right, I'm not sure how to solve it.
Any help is greatly appreciated as I've been breaking my head over this for some time.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|

