webentwicklung-frage-antwort-db.com.de

OData und WebAPI: Navigationseigenschaft im Modell nicht vorhanden

Ich versuche, ein einfaches Spielzeugprojekt zusammenzustellen, das Entity Framework, WebAPI, OData und einen Angular -Client verwendet. Alles funktioniert gut, außer die Navigationseigenschaft, die ich für eines meiner Modelle angelegt habe, scheint nicht zu funktionieren. Wenn ich meine API mit $ expand aufrufen, haben die zurückgegebenen Entitäten keine Navigationseigenschaften.

Meine Klassen sind Hund und Besitzer und sehen so aus: 

    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; } 
}

Ich habe auch meinen Dog-Controller für die Abfrage eingerichtet:

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);
    }

}

Ich habe die Datenbank mit ein paar Beispieldaten versehen. Alle Hundedatensätze haben eine OwnerId, die der Id eines Besitzers in der Tabelle Owners entspricht.

Die Abfrage der Liste der Hunde, die dies verwenden, funktioniert gut:

http://localhost:49382/odata/Dogs

Ich erhalte eine Liste von Dog-Entitäten, ohne die Eigentümer-Navigationseigenschaft.

Das Abfragen der Hunde mit ihren Besitzern mit OData $ expand funktioniert NICHT:

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

Meine Antwort ist eine 200 mit allen Dog-Entitäten, aber keine von ihnen hat eine Besitzereigenschaft in der JSON.

Wenn ich meine Metadaten abfrage, finde ich, dass OData davon zu wissen scheint:

<?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>

Was könnte ich vermissen, um zu verhindern, dass mein Navigationsgerät mit dem Rest meines Modells zurückkommt?

EDIT

Um das Problem weiter zu isolieren, habe ich versucht, die Eigentümer in C # auf der Serverseite aufzunehmen. Ich habe diese Zeile in der Get-Methode meines Dog-Controllers hinzugefügt:

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

Hiermit kann ich debuggen und sehen, dass die zugehörigen Besitzer mit eingeschlossen werden. Jeder Hund hat den Besitzer, der ihm in dieser Liste zugeordnet ist.

Die Verwendung von .Include ("Owner") für das, was tatsächlich zurückgegeben wird, behebt das Problem nicht - die Eigenschaften erreichen den Client immer noch nicht.

Dies scheint zu bedeuten, dass die Navigationseigenschaften funktionieren, aber nicht an den Client zurückgesendet werden. Ich schätze, es scheint, als würde die Verletzung auf ein Problem mit OData oder WebAPI hinweisen, aber ich bin mir nicht sicher, was.

Außerdem habe ich Application_Start in meiner Global.asax-Datei die folgenden Zeilen hinzugefügt, um kreisförmige Navigationseigenschaften zu behandeln:

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

Ich habe das getan, falls ein Zirkelverweis irgendwie der Schuldige war, aber das ändert nichts.

UPDATE

Ich habe bemerkt, dass ich angerufen habe 

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

funktioniert. Dadurch wird der Besitzer dieses Hundes abgerufen. Dies zeigt weiter, dass meine Navigationseigenschaften korrekt eingerichtet sind. Sie werden nur nicht in die Antworten auf Anrufe mit $ expand einbezogen.

UPDATE 2

Hier ist die Registermethode meiner WebApiConfig-Datei:

        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

Ich habe die Lösung für mein Problem gefunden, das letztendlich durch drei Dinge verursacht wurde:

1.) Ich habe das Attribut [Queryable] für meine Controller-Methoden verwendet, die nicht mehr empfohlen werden. Ich musste die neueren Attribute [EnableQuery] verwenden.

2.) In meiner Datei WebApiConfig.cs habe ich die Abfrage mit der Standardeinstellung config.EnableQuerySupport () aktiviert. Dies ist veraltet und wurde entfernt .

3.) Mein erweiterter Aufruf war in der Form $ expand = Owner erforderlich, musste aber in der Form $ expand = owner sein , da ich in meinem ODataConventionModelBuilder untere Kamel-Fälle aktiviere. Vielen Dank an Mark Bennetts, dessen Antwort darauf hingewiesen hat!

Nachdem Sie alle diese Änderungen vorgenommen haben, werden verwandte Eigentümerentitäten mit Dog-Entitäten zurückgegeben.

10
MWinstead

Dies liegt daran, dass Sie verwenden

builder.EnableLowerCamelCase();

in Ihrem ODataConventionModelBuilder-Setup.

Es erkennt "Owner" in Ihrer Abfrageoption $ expand-Klausel nicht, da dieser Pfad wirklich nicht im OData-Modell vorhanden ist, da er die Groß- und Kleinschreibung berücksichtigt.

Wenn Sie versuchen, dies/Dogs anzufordern? $ Expand = owner Ich bin mir sicher, dass dies funktionieren wird, und Sie erhalten sowohl Dogs als auch deren Besitzer in der JSON-Antwort.

5
Mark Bennetts

Ich hatte ein sehr ähnliches Problem, von dem ich glaube, dass es genau das gleiche Problem ist.

Ich habe versucht, einige gebundene OData-Funktionen zu erstellen, die vollständige Diagramme von Entitäten zurückgeben, um den Client-Job in bestimmten Situationen ein wenig einfacher zu machen, als $ expand-Klauseln für alles angeben zu müssen.

Ich habe Include-Anweisungen in den linq-Aufrufen des Entity-Frameworks angegeben und festgestellt, dass die zurückgegebenen Daten tatsächlich vollständig in Debug ausgefüllt waren, aber wie Sie wurde mir immer nur die Entität der obersten Ebene mit nichts anderem zurückgegeben.

Das Problem liegt in der Serialisierung von OData

Wenn Sie den Primärschlüssel aus Ihrer Owner-Klasse entfernen, sodass er im Wesentlichen zu einer komplexen Entität wird, wird er in das serialisierte OData-JSON-Ergebnis aufgenommen. Andernfalls wird dies nicht geschehen, es sei denn, der OData-Anforderungs-URI enthält ein $ expand Klausel, die es enthält.

Ich habe versucht, einen Weg zu finden, um $ expand-Klauseln in den Code einzufügen, damit er funktioniert, aber leider war ich leer.

Hoffe das hilft

1
Mark Bennetts

Sehen Sie, ob das Folgende für Sie funktionieren könnte. Ich teste in OData v4, so dass Sie [EnableQuery] möglicherweise an [Queryable] anpassen müssen. Ihr Datenbank-Kontext sollte ein IQueryable-Ergebnis zurückgeben, sodass .AsQueryable() möglicherweise nicht benötigt wird.

// 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);
}

Ich vergleiche das, was Sie haben, mit einem kleinen Projekt, an dem ich gerade arbeite. Dies ist wahrscheinlich nicht der Fall, aber meine FK-Vereinigung ist etwas anders eingerichtet, und vielleicht ist durch einen Zufall die Bestellung des FK die Frage. Meine Fremdschlüssel scheinen auf den Nav-Eigenschaften verziert zu sein.

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