web-dev-qa-db-fra.com

OData et WebAPI: propriété de navigation non présente sur le modèle

J'essaie de créer un simple projet de jouet à l'aide de Entity Framework, WebAPI, OData et d'un client Angular. Tout fonctionne bien, sauf que la propriété de navigation que j'ai placée sur l'un de mes modèles ne semble pas fonctionner. Lorsque j'appelle mon API avec $ expand, les entités renvoyées n'ont pas leurs propriétés de navigation.

Mes classes sont Dog and Owner et ressemblent à ceci: 

    public class Dog
{
    // Properties
    [Key]
    public Guid Id { get; set; }
    public String Name { get; set; }
    [Required]
    public DogBreed Breed { get; set; }
    public int Age { get; set; }
    public int Weight { get; set; }


    // Foreign Keys
    [ForeignKey("Owner")]
    public Guid OwnerId { get; set; }

    // Navigation
    public virtual Owner Owner { get; set; }
}

    public class Owner
{
    // Properties
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string Phone { get; set; }
    public DateTime SignupDate { get; set; }

    // Navigation
    public virtual ICollection<Dog> Dogs { get; set; } 
}

J'ai également configuré mon contrôleur Dog pour gérer les requêtes:

public class DogsController : ODataController
{
    DogHotelAPIContext db = new DogHotelAPIContext();
    #region Public methods 

    [Queryable(AllowedQueryOptions = System.Web.Http.OData.Query.AllowedQueryOptions.All)]
    public IQueryable<Dog> Get()
    {
        var result =  db.Dogs.AsQueryable();
        return result;
    }

    [Queryable(AllowedQueryOptions = System.Web.Http.OData.Query.AllowedQueryOptions.All)]
    public SingleResult<Dog> Get([FromODataUri] Guid key)
    {
        IQueryable<Dog> result = db.Dogs.Where(d => d.Id == key).AsQueryable().Include("Owner");
        return SingleResult.Create(result);
    }

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }

}

J'ai ensemencé la base de données avec un peu d'exemples de données. Tous les enregistrements de chien ont un identifiant de propriétaire qui correspond à l'identifiant d'un propriétaire dans la table Propriétaires.

Rechercher la liste des chiens utilisant cette méthode fonctionne bien:

http://localhost:49382/odata/Dogs

Je reçois une liste des entités de chien, sans la propriété de navigation Propriétaire.

L'interrogation des chiens avec leurs propriétaires à l'aide d'OData $ expand ne fonctionne PAS:

http://localhost:49382/odata/Dogs?$expand=Owner

Ma réponse est un 200 avec toutes les entités de chien, mais aucune d'entre elles n'a de propriété Propriétaire dans le JSON.

Si j'interroge mes métadonnées, je trouve qu'OData semble en être informé:

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
  <edmx:DataServices>
    <Schema Namespace="DogHotelAPI.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityType Name="Dog">
        <Key>
          <PropertyRef Name="id" />
        </Key>
        <Property Name="id" Type="Edm.Guid" Nullable="false" />
        <Property Name="name" Type="Edm.String" />
        <Property Name="breed" Type="DogHotelAPI.Models.Enums.DogBreed" Nullable="false" />
        <Property Name="age" Type="Edm.Int32" Nullable="false" />
        <Property Name="weight" Type="Edm.Int32" Nullable="false" />
        <Property Name="ownerId" Type="Edm.Guid" />
        <NavigationProperty Name="owner" Type="DogHotelAPI.Models.Owner">
          <ReferentialConstraint Property="ownerId" ReferencedProperty="id" />
        </NavigationProperty>
      </EntityType>
      <EntityType Name="Owner">
        <Key>
          <PropertyRef Name="id" />
        </Key>
        <Property Name="id" Type="Edm.Guid" Nullable="false" />
        <Property Name="name" Type="Edm.String" />
        <Property Name="address" Type="Edm.String" />
        <Property Name="phone" Type="Edm.String" />
        <Property Name="signupDate" Type="Edm.DateTimeOffset" Nullable="false" />
        <NavigationProperty Name="dogs" Type="Collection(DogHotelAPI.Models.Dog)" />
      </EntityType>
    </Schema>
    <Schema Namespace="DogHotelAPI.Models.Enums" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EnumType Name="DogBreed">
        <Member Name="AfghanHound" Value="0" />
        <Member Name="AmericanStaffordshireTerrier" Value="1" />
        <Member Name="Boxer" Value="2" />
        <Member Name="Chihuahua" Value="3" />
        <Member Name="Dachsund" Value="4" />
        <Member Name="GermanShepherd" Value="5" />
        <Member Name="GoldenRetriever" Value="6" />
        <Member Name="Greyhound" Value="7" />
        <Member Name="ItalianGreyhound" Value="8" />
        <Member Name="Labrador" Value="9" />
        <Member Name="Pomeranian" Value="10" />
        <Member Name="Poodle" Value="11" />
        <Member Name="ToyPoodle" Value="12" />
        <Member Name="ShihTzu" Value="13" />
        <Member Name="YorkshireTerrier" Value="14" />
      </EnumType>
    </Schema>
    <Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityContainer Name="Container">
        <EntitySet Name="Dogs" EntityType="DogHotelAPI.Models.Dog">
          <NavigationPropertyBinding Path="owner" Target="Owners" />
        </EntitySet>
        <EntitySet Name="Owners" EntityType="DogHotelAPI.Models.Owner">
          <NavigationPropertyBinding Path="dogs" Target="Dogs" />
        </EntitySet>
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

Qu'est-ce qui pourrait me manquer, c'est-à-dire empêcher ma navigation d'avant de revenir avec le reste de mon modèle?

MODIFIER

Pour isoler davantage le problème, j'ai essayé d'inclure les propriétaires en C # côté serveur. J'ai ajouté cette ligne dans la méthode Get de mon contrôleur Dog:

var test = db.Dogs.Include("Owner").ToList();

Avec cela, je peux déboguer et voir que les propriétaires associés SONT inclus. Chaque chien a le propriétaire qui lui est associé dans cette liste.

L'utilisation de .Include ("Propriétaire") sur ce qui est réellement renvoyé ne résout pas le problème - les propriétés ne parviennent toujours pas au client.

Cela semble signifier que les propriétés de navigation fonctionnent mais ne sont pas renvoyées au client. On dirait que cela semble indiquer un problème avec OData ou WebAPI, mais je ne sais pas quoi.

De plus, j'ai ajouté les lignes suivantes à Application_Start dans mon fichier Global.asax afin de gérer les propriétés de navigation circulaires:

            var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
        json.SerializerSettings.PreserveReferencesHandling =
            Newtonsoft.Json.PreserveReferencesHandling.All;

Je l'ai fait dans le cas où une référence circulaire était en quelque sorte le coupable, mais cela ne change rien.

MISE &AGRAVE; JOUR

J'ai remarqué que faire un appel à 

http://localhost:49382/odata/Dogs(abfd26a5-14d8-4b14-adbe-0a0c0ef392a7)/owner

travaux. Ceci récupère le propriétaire associé à ce chien. Cela montre que mes propriétés de navigation sont correctement configurées. Ne sont pas incluses dans les réponses aux appels utilisant

UPDATE 2

Voici la méthode de registre de mon fichier WebApiConfig:

        public static void Register(HttpConfiguration config)
    {
        //config.Routes.MapHttpRoute(
        //    name: "DefaultApi",
        //    routeTemplate: "api/{controller}/{id}",
        //    defaults: new { id = RouteParameter.Optional }
        //);

        ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
        builder.EnableLowerCamelCase();
        builder.EntitySet<Dog>("Dogs");
        builder.EntitySet<Owner>("Owners");

        config.EnableQuerySupport();

        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: "odata",
            model: builder.GetEdmModel());


        // Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
        // To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
        // For more information, visit http://go.Microsoft.com/fwlink/?LinkId=279712.
        //config.EnableQuerySupport();

        // To disable tracing in your application, please comment out or remove the following line of code
        // For more information, refer to: http://www.asp.net/web-api
        config.EnableSystemDiagnosticsTracing();
    }
12
MWinstead

J'ai trouvé la solution à mon problème, qui a finalement été causé par trois choses:

1.) J'utilisais l'attribut [Queryable] sur mes méthodes de contrôleur, qui sont obsolètes. J'avais besoin d'utiliser les nouveaux [EnableQuery] attributs.

2.) Dans mon fichier WebApiConfig.cs, j'activais l'interrogation à l'aide de config.EnableQuerySupport () par défaut. Ceci est obsolète et a été enlevé .

3.) Mon appel expand nécessaire était sous la forme $ expand = Owner mais devait l'être sous la forme de $ expand = owner puisque j'active le cas de chameau inférieur sur mon ODataConventionModelBuilder. Merci beaucoup à Mark Bennetts, qui a répondu à cette question!

Après avoir effectué toutes ces modifications, les entités propriétaires associées sont renvoyées avec les entités Dog.

10
MWinstead

C'est parce que vous utilisez

builder.EnableLowerCamelCase();

dans votre configuration ODataConventionModelBuilder.

Il ne reconnaît pas le "propriétaire" dans votre clause d'options de requête $ expand car ce chemin n'existe pas dans le modèle OData car il est sensible à la casse.

Si vous essayez de demander ceci/$ chiens = $ expand = propriétaire, je suis sûr que cela fonctionnera et que les deux chiens et leurs propriétaires seront renvoyés dans la réponse JSON.

5
Mark Bennetts

J'ai eu un problème très similaire, qui, je crois, est causé par exactement le même problème.

J'essayais de créer des fonctions OData liées qui renverraient des graphiques entiers d'entités pour faciliter le travail des clients dans certaines situations plutôt que de devoir spécifier des clauses $ expand pour tout.

J'ai spécifié les instructions Include dans les appels linq du framework entity et vérifié que les données de retour étaient bien entièrement remplies dans le débogage, mais, comme vous, je ne faisais que retourner l'entité de niveau supérieur sans rien d'autre.

Le problème réside dans la sérialisation utilisée pour OData

Vous constaterez que si vous supprimez la clé primaire de votre classe Owner pour qu'elle devienne essentiellement une entité complexe, elle sera incluse dans le résultat JSON OData sérialisé. Sinon, elle ne le sera que si la demande OData uri comporte une extension $ clause qui l'inclut.

J'ai essayé de trouver un moyen d'insérer des clauses $ expand dans le code pour que cela fonctionne, mais malheureusement, rien n'a été fait.

J'espère que cela t'aides

1
Mark Bennetts

Voir si le ci-dessous pourrait fonctionner pour vous. Je teste dans OData v4, vous devrez peut-être ajuster [EnableQuery] à [Queryable]. Votre contexte de base de données devrait renvoyer un résultat IQueryable tel que .AsQueryable() ne soit peut-être pas nécessaire.

// GET: odata/Dogs
[EnableQuery]
public IQueryable<Dog> Get()
{
    return db.Dogs;
}

// GET: odata/Dogs(5)/Owner
[EnableQuery]
public IQueryable<Owner> GetOwner([FromODataUri] int key)
{
    return db.Dogs.Where(m => m.ID == key).SelectMany(m => m.Owner);
}

Je compare ce que vous avez à un petit projet sur lequel je travaille actuellement. Ce n'est probablement pas le cas, mais mon association FK est configurée de manière légèrement différente et, peut-être par un coup de chance, la commande du FK est la question. Mes clés étrangères semblent être décorées au sommet des propriétés de navigation.

public int PublicImageID { get; set; }
[ForeignKey("PublicImageID")]
public PublicImage PublicImage { get; set; }

// Foreign Keys    
public Guid OwnerId { get; set; }
[ForeignKey("OwnerId")]
public virtual Owner Owner { get; set; }
0
Pynt