OData $ expand, DTO et Entity Framework

J'ai une base WebApi service de l'installation avec une première base de données EF DataModel mis en place. Je suis en cours d'exécution les nightly builds de WebApi, EF6, et la WebApi OData paquets. (WebApi: 5.1.0-alpha1, EF: 6.1.0-alpha1, WebApi OData: 5.1.0-alpha1)

La base de données comporte deux tableaux: le Produit et le Fournisseur. Un Produit peut avoir un seul Fournisseur. Un Fournisseur peut avoir plusieurs Produits.

J'ai également créé deux DTO classes:

public class Supplier
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public virtual IQueryable<Product> Products { get; set; }
}

public class Product
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }
}

J'ai configuré mon WebApiConfig comme suit:

public static void Register(HttpConfiguration config)
{
    ODataConventionModelBuilder oDataModelBuilder = new ODataConventionModelBuilder();

    oDataModelBuilder.EntitySet<Product>("product");
    oDataModelBuilder.EntitySet<Supplier>("supplier");

    config.Routes.MapODataRoute(routeName: "oData",
        routePrefix: "odata",
        model: oDataModelBuilder.GetEdmModel());
}

J'ai mis en place mes deux contrôleurs comme suit:

public class ProductController : ODataController
{
    [HttpGet]
    [Queryable]
    public IQueryable<Product> Get()
    {
        var context = new ExampleContext();

        var results = context.EF_Products
            .Select(x => new Product() { Id = x.ProductId, Name = x.ProductName});

        return results as IQueryable<Product>;
    }
}

public class SupplierController : ODataController
{
    [HttpGet]
    [Queryable]
    public IQueryable<Supplier> Get()
    {
        var context = new ExampleContext();

        var results = context.EF_Suppliers
            .Select(x => new Supplier() { Id = x.SupplierId, Name = x.SupplierName });

        return results as IQueryable<Supplier>;
    }
}

Voici les métadonnées qui revient. Comme vous pouvez le voir, les propriétés de navigation sont correctement définis:

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<Schema Namespace="StackOverflowExample.Models" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityType Name="Product">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="Name" Type="Edm.String" />
</EntityType>
<EntityType Name="Supplier">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="Name" Type="Edm.String" />
<NavigationProperty Name="Products" Relationship="StackOverflowExample.Models.StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartner" ToRole="Products" FromRole="ProductsPartner" />
</EntityType>
<Association Name="StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartner">
<End Type="StackOverflowExample.Models.Product" Role="Products" Multiplicity="*" />
<End Type="StackOverflowExample.Models.Supplier" Role="ProductsPartner" Multiplicity="0..1" />
</Association>
</Schema>
<Schema Namespace="Default" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityContainer Name="Container" m:IsDefaultEntityContainer="true">
<EntitySet Name="product" EntityType="StackOverflowExample.Models.Product" />
<EntitySet Name="supplier" EntityType="StackOverflowExample.Models.Supplier" />
<AssociationSet Name="StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartnerSet" Association="StackOverflowExample.Models.StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartner">
<End Role="ProductsPartner" EntitySet="supplier" />
<End Role="Products" EntitySet="product" />
</AssociationSet>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

C'est pourquoi le tableau de odata des requêtes fines: /odata/produit?$filtre=Nom+eq+'Produit1" et /odata/fournisseur?$sélectionnez=Id par exemple tous les beaux travaux.

Le problème est que lorsque je tente de travailler avec $expand. Si je devais faire /odata/fournisseur?$expand=Produits, bien entendu, je reçois une erreur:

"Le type spécifié membre de "Produits" n'est pas pris en charge dans LINQ to entities. Seulement les initialiseurs, les membres de l'entité et l'entité propriétés de navigation sont pris en charge."

Mise à jour:
Je reçois les mêmes questions, donc je suis l'ajout de plus d'informations. Oui, les propriétés de navigation sont correctement configurés comme on peut le voir dans les informations de métadonnées que j'ai posté ci-dessus.

Ce n'est pas lié à des méthodes manquantes sur le contrôleur. Si je devais créer une classe qui implémente IODataRoutingConvention, /odata/fournisseur(1)/produit serait analysé comme "~/entityset/clé/navigation" juste fine.

Si je devais contourner mon Otd complètement et il suffit de retourner le EF classes générées, $développez fonctionne hors de la boîte.

Mise à jour 2:
Si je change de Produit classe à la suivante:

public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual Supplier Supplier { get; set; }
}

et puis changer le ProductController:

public class ProductController : ODataController
{
[HttpGet]
[Queryable]
public IQueryable<Product> Get()
{
var context = new ExampleContext();
return context.EF_Products
.Select(x => new Product() 
{ 
Id = x.ProductId, 
Name = x.ProductName, 
Supplier = new Supplier() 
{
Id = x.EF_Supplier.SupplierId, 
Name = x.EF_Supplier.SupplierName 
} 
});
}
}

Si je devais appeler /odata/produit je voudrais revenir à ce que j'attendais. Une gamme de Produits avec le Fournisseur champ retourné dans la réponse. La requête sql générée rejoint et sélectionne à partir de la table Fournisseurs, ce qui serait logique pour moi si ce n'est pour la prochaine résultats de la requête.

Si je devais appeler /odata/produit?$sélectionnez=Id, je voudrais revenir à ce que j'allais attendre. Mais $select se traduit par une requête sql qui ne fait pas partie de la table fournisseurs.

/odata/produit?$expand=Produit échoue avec une erreur différents:

"L'argument de DbIsNullExpression doit se référer à un primitif, de l'énumération ou le type de référence."

Si je change de Contrôleur de Produit à la suivante:

public class ProductController : ODataController
{
[HttpGet]
[Queryable]
public IQueryable<Product> Get()
{
var context = new ExampleContext();
return context.EF_Products
.Select(x => new Product() 
{ 
Id = x.ProductId, 
Name = x.ProductName, 
Supplier = new Supplier() 
{
Id = x.EF_Supplier.SupplierId, 
Name = x.EF_Supplier.SupplierName 
} 
})
.ToList()
.AsQueryable();
}
}

/odata/produit /odata/produit?$sélectionnez=Id, et /odata/produit?$expand=Fournisseur retour des résultats corrects, mais, évidemment, la .ToList() l'emporte sur l'objectif un peu.

Je peux essayer de modifier le Contrôleur de Produit pour appeler uniquement .ToList() lorsqu'un $étendre la requête est transmise, comme suit:

    [HttpGet]
public IQueryable<Product> Get(ODataQueryOptions queryOptions)
{
var context = new ExampleContext();
if (queryOptions.SelectExpand == null)
{
var results = context.EF_Products
.Select(x => new Product()
{
Id = x.ProductId,
Name = x.ProductName,
Supplier = new Supplier()
{
Id = x.EF_Supplier.SupplierId,
Name = x.EF_Supplier.SupplierName
}
});
IQueryable returnValue = queryOptions.ApplyTo(results);
return returnValue as IQueryable<Product>;
}
else
{
var results = context.EF_Products
.Select(x => new Product()
{
Id = x.ProductId,
Name = x.ProductName,
Supplier = new Supplier()
{
Id = x.EF_Supplier.SupplierId,
Name = x.EF_Supplier.SupplierName
}
})
.ToList()
.AsQueryable();
IQueryable returnValue = queryOptions.ApplyTo(results);
return returnValue as IQueryable<Product>;
}
}
}

Malheureusement, quand je l'appelle /odata/produit?$sélectionnez=Id ou /odata/produit?$expand=Fournisseur il jette à un sérialisation erreur car returnValue ne peut pas être lancée à IQueryable. J'ai peut être coulé bien que si je l'appelle /odata/produit.

Qu'est-ce que le travail ici? Dois-je juste avoir à sauter en essayant d'utiliser mon propre Otd ou peut/doit je roule ma propre implémentation de $développez et $select?

source d'informationauteur Schandlich