OData和WebAPI: Navigation属性在模型上不存在

本文关键字:模型 不存在 属性 Navigation WebAPI OData | 更新日期: 2023-09-27 17:50:13

我正在尝试使用实体框架、WebAPI、OData和Angular客户端来组合一个简单的玩具项目。一切都很好,除了导航属性,我已经把我的一个模型似乎不工作。当我使用$expand调用API时,返回的实体没有导航属性。

我的类是Dog和Owner,它们是这样的:

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

我还设置了Dog控制器来处理查询:

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

我已经在数据库中植入了一些样本数据。所有狗的记录都有一个OwnerId,该OwnerId与Owners表中一个Owner的Id相匹配。

使用this查询狗的列表可以正常工作:

http://localhost:49382/odata/Dogs

我得到一个Dog实体列表,没有Owner导航属性。

使用OData $expand查询狗与他们的主人不工作:

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

我的响应是包含所有Dog实体的200,但是在JSON中它们都没有Owner属性。

如果我查询我的元数据,我发现OData似乎知道它:

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

我能错过什么是阻止我的导航属性回来与我的模型的其余部分?

编辑

为了进一步隔离问题,我尝试在服务器端包括c#中的所有者。我在Dog控制器的Get方法中添加了这一行:

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

有了这个,我可以调试并看到相关的所有者被包括在内。在这个列表中,每只狗都有与其相关联的主人。

对实际返回的内容使用. include ("Owner")并不能解决问题-属性仍然无法到达客户端。

这似乎意味着导航属性正在工作,但没有被发送回客户端。我猜,这似乎表明OData或WebAPI有问题,但我不确定是什么。

此外,我还在全局中的Application_Start中添加了以下行。一个用于处理循环导航属性的文件:

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

我这样做是为了防止循环引用在某种程度上是罪魁祸首,但这并没有改变什么。

我注意到打电话给

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

。这将检索与该狗相关联的主人。这进一步说明了我的导航属性设置正确,它们只是没有被包含在使用$expand调用的响应中。

更新2

这是我的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();
    }

OData和WebAPI: Navigation属性在模型上不存在

我找到了解决问题的方法,这个问题最终是由三件事引起的:

1)。我在我的控制器方法上使用[Queryable]属性,这是不赞成的。我需要使用新的[EnableQuery]属性。

2)。在我的WebApiConfig.cs文件中,我通过使用默认的config.EnableQuerySupport()启用查询。已被删除

3)。我需要的扩展调用的形式是$expand=Owner,但需要以$expand= Owner 的形式出现,因为我在odatacontionmodelbuilder上启用了小写驼角大小写。非常感谢Mark bennett,他的回答指出了这一点!

在进行所有这些更改之后,相关的Owner实体将与Dog实体一起返回。

这是因为您正在使用

builder.EnableLowerCamelCase();

在您的odatacontiononmodelbuilder设置。

它不能识别你的查询选项$expand子句中的"Owner",因为该路径在OData模型中不存在,因为它是区分大小写的。

如果你尝试请求this/Dogs?我相信这将工作,你会得到狗和他们的主人返回JSON响应。

我有一个非常类似的问题,我相信是由完全相同的问题引起的。

我试图创建一些绑定OData函数,这些函数将返回实体的整个图,以使客户端的工作在某些情况下更容易一些,而不必为所有内容指定$expand子句。

我在实体框架linq调用中指定了Include语句,并验证了返回的数据确实在调试中被完全填充,但是,像你一样,我只获得了顶级实体的返回,没有其他任何内容。

问题在于OData

使用的序列化。

你会发现,如果你从你的Owner类中删除主键,所以它本质上成为一个复杂的实体,那么它将被包含在OData序列化的JSON结果中,否则它不会,除非OData请求uri包含一个包含它的$expand子句。

我试图找到一种在代码中插入$expand子句以使其工作的方法,但不幸的是出现空白。

希望能有所帮助

看看下面的内容是否适合您。我正在OData v4中进行测试,因此您可能需要将[EnableQuery]调整为[Queryable]。你的数据库上下文应该返回一个可查询的结果,这样.AsQueryable()可能不需要。

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

我正在比较你所拥有的和我目前正在做的一个小项目。这可能不是事实,但是我的FK关联的建立有一点不同,也许只是由于某种侥幸,FK的排序是问题所在。我的外键似乎被装饰在导航属性的顶部。

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