-
Notifications
You must be signed in to change notification settings - Fork 178
Description
The #1 drawback of implementing OData is that it exposes the underlying raw data model. Particularly when working with EF, you are effectively exposing direct database access. The entities themselves usually encapsulate business logic and other behavior that aren't relevant for an API. It is usually beset-practice to expose DTOs through the API endpoint. Sometimes these DTOs are 1:1 pass-through to the entities and other times they are modified views of multiple entities. Most modern-day solutions implement some kind of DTO mapping layer through tools like AutoMapper or homegrown interfaces.
Given these concerns and the popularity of Entity <-> DTO mappings in APIs, Microsoft should extend the OData ecosystem to allow for configuring entities and DTOs side-by-side and allow for OData API endpoints to seamlessly work with the existing ecosystem. As of right now, the existing "solution" doesn't work very well in terms of supporting all OData functionality and performing server-side (DB) operations. Example:
IMap:
public interface IMap<TSource, TDestination>
{
TDestination Map(TSource source);
IQueryable<TDestination> Map(IEnumerable<TSource> source);
TSource Map(TDestination source);
IQueryable<TSource> Map(IEnumerable<TDestination> source);
}
Models:
public class Order
{
[Key]
public int Id { get; set; }
public int AccountId { get; set; }
public string Type { get; set; }
public IList<OrderItems> OrderItems { get; set; }
...
}
[Select, Filter, OrderBy]
[Page(MaxTop = 5, PageSize = 1)]
[Count(Disabled = true)]
[OrderBy("Type", Disabled = true)]
public class OrderDto
{
public int Id { get; set; }
public int AccountId { get; set; }
public string Type { get; set; }
public IList<OrderItems> OrderItems { get; set; }
...
}
Orders IModelConfiguration:
public void Apply(ODataModelBuilder builder, ApiVersion apiVersion, string? routePrefix)
{
builder.EntitySet<OrderDto>("Orders");
}
Orders controller:
[EnableQuery]
public IQueryable<OrderDto> Get()
{
// ********************************************************************************
// This may be a more complex base query against one or more entities.
IQueryable<Order> query = _ordersDbContext.GetOpenOrdersByCompany();
// ********************************************************************************
var mapper = new OrderMapper<Order, OrderDto>();
IQueryable<OrderDto> results = mapper.Map(query);
return results;
}
<other OData supported methods>
In the above example, the expected behavior would be that all OData features would be passed-through to the underlying DbContext in EF so that there is proper server-side processing.