title: Using DTO's with OData & Web API
body: <p>Using Web API and OData, I have a service which exposes Data Transfer Objects instead of the Entity Framework entities.  </p>
  
  <p>I use AutoMapper to transform the EF Entities into their DTO counter parts using <code>ProjectTo()</code>:</p>
  
  <pre><code>public class SalesOrdersController : ODataController
  {
      private DbContext _DbContext;
  
      public SalesOrdersController(DbContext context)
      {
          _DbContext = context;
      }
  
      [EnableQuery]
      public IQueryable&lt;SalesOrderDto&gt; Get(ODataQueryOptions&lt;SalesOrderDto&gt; queryOptions)
      {
          return _DbContext.SalesOrders.ProjectTo&lt;SalesOrderDto&gt;(AutoMapperConfig.Config);
      }
  
      [EnableQuery]
      public IQueryable&lt;SalesOrderDto&gt; Get([FromODataUri] string key, ODataQueryOptions&lt;SalesOrderDto&gt; queryOptions)
      {
          return _DbContext.SalesOrders.Where(so =&gt; so.SalesOrderNumber == key)
                              .ProjectTo&lt;SalesOrderDto&gt;(AutoMapperConfig.Config);
      }
  }
  </code></pre>
  
  <p>AutoMapper (V4.2.1) is configured as follows, note the <code>ExplicitExpansion()</code> which prevents serialisation auto expanding navigation properties when they are not requested:</p>
  
  <pre><code>cfg.CreateMap&lt;SalesOrderHeader, SalesOrderDto&gt;()                
              .ForMember(dest =&gt; dest.SalesOrderLines, opt =&gt; opt.ExplicitExpansion());
  
  cfg.CreateMap&lt;SalesOrderLine, SalesOrderLineDto&gt;()
              .ForMember(dest =&gt; dest.MasterStockRecord, opt =&gt; opt.ExplicitExpansion())
              .ForMember(dest =&gt; dest.SalesOrderHeader, opt =&gt; opt.ExplicitExpansion());
  </code></pre>
  
  <p><code>ExplicitExpansion()</code> then creates a new problem where the following request throws an error:</p>
  
  <blockquote>
    <p><strong>/odatademo/SalesOrders('123456')?$expand=SalesOrderLines</strong></p>
    
    <p>The query specified in the URI is not valid. The specified type member 'SalesOrderLines' is not supported in LINQ to Entities</p>
  </blockquote>
  
  <p>The navigation property <code>SalesOrderLines</code> is unknown to EF so this error is pretty much what I expected to happen.  The question is, how do I handle this type of request?</p>
  
  <p>The <code>ProjectTo()</code> method does have an overload that allows me to pass in an array of properties that require expansion, I found &amp; modified the extension method <code>ToNavigationPropertyArray</code> to try and parse the request into a string array:</p>
  
  <pre><code>[EnableQuery]
  public IQueryable&lt;SalesOrderDto&gt; Get([FromODataUri] string key, ODataQueryOptions&lt;SalesOrderDto&gt; queryOptions)
  {
      return _DbContext.SalesOrders.Where(so =&gt; so.SalesOrderNumber == key)
              .ProjectTo&lt;SalesOrderDto&gt;(AutoMapperConfig.Config, null, queryOptions.ToNavigationPropertyArray());
  }
  
  public static string[] ToNavigationPropertyArray(this ODataQueryOptions source)
  {
      if (source == null) { return new string[]{}; }
  
      var expandProperties = string.IsNullOrWhiteSpace(source.SelectExpand?.RawExpand) ? new List&lt;string&gt;().ToArray() : source.SelectExpand.RawExpand.Split(',');
  
      for (var expandIndex = 0; expandIndex &lt; expandProperties.Length; expandIndex++)
      {
          // Need to transform the odata syntax for expanding properties to something EF will understand:
  
          // OData may pass something in this form: "SalesOrderLines($expand=MasterStockRecord)";                
          // But EF wants it like this: "SalesOrderLines.MasterStockRecord";
  
          expandProperties[expandIndex] = expandProperties[expandIndex].Replace(" ", "");
          expandProperties[expandIndex] = expandProperties[expandIndex].Replace("($expand=", ".");
          expandProperties[expandIndex] = expandProperties[expandIndex].Replace(")", "");
      }
  
      var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List&lt;string&gt;().ToArray() : source.SelectExpand.RawSelect.Split(',');
  
      //Now do the same for Select (incomplete)          
      var propertiesToExpand = expandProperties.Union(selectProperties).ToArray();
  
      return propertiesToExpand;
  }
  </code></pre>
  
  <p>This works for expand, so now I can handle a request like the following:</p>
  
  <blockquote>
    <p><strong>/odatademo/SalesOrders('123456')?$expand=SalesOrderLines</strong></p>
  </blockquote>
  
  <p>or a more complicated request like:</p>
  
  <blockquote>
    <p><strong>/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($expand=MasterStockRecord)</strong></p>
  </blockquote>
  
  <p>However, more complicated request that try to combine $select with $expand will fail:</p>
  
  <blockquote>
    <p><strong>/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($select=OrderQuantity)</strong></p>
    
    <p>Sequence contains no elements</p>
  </blockquote>
  
  <p>So, the question is: am I approaching this the right way?
  It feels very smelly that I would have to write something to parse and transform the ODataQueryOptions into something EF can understand.</p>
  
  <p>It seems this is a rather popular topic:</p>
  
  <ul>
  <li><a href="https://stackoverflow.com/questions/19323545/odata-expand-dtos-and-entity-framework">odata-expand-dtos-and-entity-framework</a></li>
  <li><a href="https://stackoverflow.com/questions/20462444/how-to-specify-the-shape-of-results-with-webapi2-odata-with-expand">how-to-specify-the-shape-of-results-with-webapi2-odata-with-expand</a></li>
  <li><a href="https://stackoverflow.com/questions/14484692/web-api-queryable-how-to-apply-automapper">web-api-queryable-how-to-apply-automapper</a></li>
  <li><a href="https://stackoverflow.com/questions/26628521/how-do-i-map-an-odata-query-against-a-dto-to-another-entity">how-do-i-map-an-odata-query-against-a-dto-to-another-entity</a></li>
  </ul>
  
  <p>While most of these suggest using <code>ProjectTo</code>, none seem to address serialisation auto expanding properties, or how to handle expansion if <code>ExplictExpansion</code> has been configured.</p>
  
  <p><strong>Classes and Config below:</strong></p>
  
  <p>Entity Framework (V6.1.3) entities:</p>
  
  <pre><code>public class SalesOrderHeader
  {
      public string SalesOrderNumber { get; set; }
      public string Alpha { get; set; }
      public string Customer { get; set; }
      public string Status { get; set; }
      public virtual ICollection&lt;SalesOrderLine&gt; SalesOrderLines { get; set; }
  }
  
  public class SalesOrderLine
  {
      public string SalesOrderNumber { get; set; }
      public string OrderLineNumber { get; set; }        
      public string Product { get; set; }
      public string Description { get; set; }
      public decimal OrderQuantity { get; set; }
  
      public virtual SalesOrderHeader SalesOrderHeader { get; set; }
      public virtual MasterStockRecord MasterStockRecord { get; set; }
  }
  
  public class MasterStockRecord
  {        
      public string ProductCode { get; set; }     
      public string Description { get; set; }
      public decimal Quantity { get; set; }
  }
  </code></pre>
  
  <p>OData (V6.13.0) Data Transfer Objects:</p>
  
  <pre><code>public class SalesOrderDto
  {
      [Key]
      public string SalesOrderNumber { get; set; }
      public string Customer { get; set; }
      public string Status { get; set; }
      public virtual ICollection&lt;SalesOrderLineDto&gt; SalesOrderLines { get; set; }
  }
  
  public class SalesOrderLineDto
  {
      [Key]
      [ForeignKey("SalesOrderHeader")]
      public string SalesOrderNumber { get; set; }
  
      [Key]
      public string OrderLineNumber { get; set; }
      public string LineType { get; set; }
      public string Product { get; set; }
      public string Description { get; set; }
      public decimal OrderQuantity { get; set; }
  
      public virtual SalesOrderDto SalesOrderHeader { get; set; }
      public virtual StockDto MasterStockRecord { get; set; }
  }
  
  public class StockDto
  {
      [Key]
      public string StockCode { get; set; }        
      public string Description { get; set; }        
      public decimal Quantity { get; set; }
  }
  </code></pre>
  
  <p>OData Config:</p>
  
  <pre><code>var builder = new ODataConventionModelBuilder();
  
  builder.EntitySet&lt;StockDto&gt;("Stock");
  builder.EntitySet&lt;SalesOrderDto&gt;("SalesOrders");
  builder.EntitySet&lt;SalesOrderLineDto&gt;("SalesOrderLines");
  </code></pre>