Reputation: 427
I recently implemented OData in my ASP .NET Core web API. I have found success as long as I am returning the database models directly. I run into trouble, however, as soon as I attempt to return domain models instead.
The underlying issue involves mapping a data class to a domain class while maintaining the IQueryable return type. While I have found partial success using AutoMapper's MapTo extension method, I find that I am unsuccessful when using the $extend method to expand a collection of entities that are also domain objects.
I have created a sample project to illustrate this issue. You may view or download the full project on github here. See the description below.
Given the following two database classes:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Order> Orders { get; set; }
public Product() {
Orders = new Collection<Order>();
}
}
public class Order
{
public int Id { get; set; }
public Double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
And the following domain models...
public class ProductEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<OrderEntity> Orders { get; set; }
}
public class OrderEntity
{
public int Id { get; set; }
public Double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
And the Products Controller
public class ProductsController
{
private readonly SalesContext context;
public ProductsController(SalesContext context) {
this.context = context;
}
[EnableQuery]
public IQueryable<ProductEntity> Get() {
return context.Products
.ProjectTo<ProductEntity>()
.AsQueryable();
}
}
All the following OData queries Pass:
The following query, however, does not pass:
An HTTP response is never returned. The only failure message I get comes from the console:
System.InvalidOperationException: Sequence contains no matching element at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predicate)
Finally, here is a reference to the mapping profile:
public static class MappingProfile
{
public static void RegisterMappings() {
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Order, OrderEntity>();
cfg.CreateMap<Product, ProductEntity>();
});
}
}
I can solve the issue by simply returning a List instead of an IEnumerable in the controller, but this of course would trigger a large query against the database that would be performance intensive.
As stated above, you can find a link to the full project on Github here. Let me know if you find any answers!
Upvotes: 5
Views: 988
Reputation: 3178
I was able to get this working with a few small revisions.
Updating the domain models:
public class ProductEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class OrderEntity
{
public int Id { get; set; }
public double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
Manually enabling expansion on the route builder:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, SalesModelBuilder modelBuilder)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(routeBuilder =>
{
routeBuilder.Expand().Select();
routeBuilder.MapODataServiceRoute("ODataRoutes", "odata",
modelBuilder.GetEdmModel(app.ApplicationServices));
});
}
Using the following Queries:
Upvotes: 2