Skip to content

Need a comprehensive solution for exposing DTOs instead of EntitiesΒ #1516

@anthony-hernandez-aas

Description

@anthony-hernandez-aas

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.

Metadata

Metadata

Assignees

Labels

questionFurther information is requested

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions