2013-06-06 1 views
13

Ich habe Project-Entität und ProjectDTO. Ich versuche, eine WebAPI-Controller-Methode zu erstellen, die ProjectDTOs übernehmen und zurückgeben und OData unterstützen kann.ASP.NET WebApi OData-Unterstützung für DTOs

Das Problem ist, dass ich ORM verwenden, die die Datenbank mit Projekt-Entität nicht Projekt DTO abfragen können. Gibt es eine Möglichkeit, Filterung/Sortierung/Paging von OData basierend auf ProjectDTO auf die Abfrage der Projekteinheit anzuwenden?

public ODataQueryResult<ProjectDTO> GetProjects(ODataQueryOptions<ProjectDTO> query) 
{ 
    var context = new ORM_Context(); 

    var projects = context.Projects; // IQueryable<Project> 
    var projectDtos = query.ApplyTo(projectDTOs)); // <-- I want to achieve something similar here 
    var projectDTOs = 
     projects.Select(
      x => 
      new ProjectDTO 
       { 
        Id = x.Id, 
        Name = x.Name 
       }); 

    var projectsQueriedList = projectDtos.ToList(); 

    var result = new ODataQueryResult<ProjectDTO>(projectsQueriedList, totalCount); 

    return result; 
} 
+0

Was haben Sie am Ende gemacht? – mayu

Antwort

4

Etwas wie dies (ich habe es nicht versucht zu kompilieren)

using(var dataContext = new ORM_Context()) 
{ 
    var projects = dataContext.Projects; // IQueryable<Project> 

    //Create a set of ODataQueryOptions for the internal class 
    ODataModelBuilder modelBuilder = new ODataConventionModelBuilder(); 
    modelBuilder.EntitySet<Project>("Project"); 
    var context = new ODataQueryContext(
     modelBuilder.GetEdmModel(), typeof(Project)); 
    var newOptions = new ODataQueryOptions<Project>(context, Request); 

    var t = new ODataValidationSettings() { MaxTop = 25 }; 
    var s = new ODataQuerySettings() { PageSize = 25 }; 
    newOptions.Validate(t); 
    IEnumerable<Project> internalResults = 
     (IEnumerable<Project>)newOptions.ApplyTo(projects, s); 

    int skip = newOptions.Skip == null ? 0 : newOptions.Skip.Value; 
    int take = newOptions.Top == null ? 25 : newOptions.Top.Value; 

    var projectDTOs = 
      internalResults.Skip(skip).Take(take).Select(x => 
       new ProjectDTO 
        { 
         Id = x.Id, 
         Name = x.Name 
        }); 

    var projectsQueriedList = projectDtos.ToList(); 
    var result = new ODataQueryResult<ProjectDTO>(
     projectsQueriedList, totalCount); 
    return result; 
} 
+1

Vielen Dank für Ihre Antwort! Es sieht auf den ersten Blick sehr vielversprechend aus. Ich werde diese Lösung testen und hoffe, dass sie funktioniert. – kubal5003

+0

Fast perfekt, funktionierte für mich brillant mit einer kleinen Änderung, ODataQueryResult existiert nicht mehr, also gab ich ein IQueryable zurück. Es enthält nicht die Gesamtzahl der Elemente, die für das Paging nicht genial sind. Das ist schade, enthält aber einen Link zur nächsten Seite. – BenCr

+0

@BenCr Es gibt jetzt eine 'PageResult <>' Klasse http://msdn.microsoft.com/en-us/library/jj890608(v=vs.111).aspx – qujck

0

starten:

public object GetProjects(ODataQueryOptions<Project> query) 
    { 
     var context = new ORM_Context(); 

     var projects = query.ApplyTo(context.Projects); 
     var projectDTOs = projects.Select(
       x => 
       new ProjectDTO 
        { 
         Id = x.Id, 
         Name = x.Name 
        }); 

     return new 
     { 
      TotalCount = Request.GetInlineCount(), //before paging 
      Results = projectDTOs.ToList() 
     }; 
    } 

Offenbar ist das Wichtigste hier ist die richtige passieren Typ zu ODataQueryOptions <> und dann führt es seine Magie gut aus. Dies liegt daran, dass dieser spezielle Typ verwendet wird, um den collection/db-Kontext abzufragen, so dass er den Typ haben muss, der tatsächlich in der Sammlung/dem Kontext gespeichert ist, und nicht das, was Sie zurückgeben möchten.

Offensichtlich sollten Ihre DTOs Ihren ORM-Objekten sehr ähnlich sein (und sie tun dies in Ihrem Snippet), oder dies wird aus der Sicht von Benutzer/Client nicht sehr gut funktionieren.

Ich habe nicht versucht, den obigen Code zu kompilieren, weil ich nicht Ihre Klassen und andere Infrastruktur habe, aber es sollte ausreichen, um die Idee zu vermitteln.

2

Ich denke, der einfachste Weg, dies zu tun ist mit AutoMapper. Also, für Ihre DTO

[DataContract(Name = "Products")] 
    public class ProductDTO 
    { 
     [Key] 
     [DataMember] 
     public string MyProductMember1 { get; set; } 

     [DataMember] 
     public string MyProductMember2 { get; set; } 
     ... 
    } 

sollten Sie irgendwo in AutoMapper Konfiguration schreiben:

Mapper.CreateMap<Product, ProductDTO>(); 

und irgendwo im Gebäude IEdmModel für OData:

builder.EntitySet<ProductDTO>("Products"); 

und der Code für Ihren Controller aussehen wie

public class ProductsController : ODataController 
{ 
    [EnableQuery] 
    public IHttpActionResult Get() 
    { 
     var products = context.Products; // IQueryable<Product> 
     return Ok(products.Project().To<ProductDTO>()); 
    } 
} 

Auf diese Weise müssen Sie Ihre ORM-Entitäten nicht direkt verfügbar machen. Sie können OData zum Filtern, Paging, Zählen und sogar zum Erweitern verschachtelter Auflistungen verwenden. Für EF werden sie in entsprechende SQL-Anforderungen mit einer Tabelle übersetzt, der Product zugeordnet ist . Aber Vorsicht: Bei komplizierteren Fällen (beispielsweise verschachtelten Sammlungen) kann es zu einer nicht optimalen SQL-Anfrage kommen.