我正在尝试使用Entity Framework,WebAPI,OData和Angular客户端组合一个简单的玩具项目。一切都运行正常,除了我放在我的一个模型上的导航属性似乎没有工作。当我使用$ expand调用我的API时,返回的实体没有它们的导航属性。
我的课程是狗和老板,看起来像这样:
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);
}
}
我在数据库中添加了一些样本数据。所有狗记录都有一个Owner Ow,它与Owners表中所有者的Id相匹配。
使用此方法查询狗的列表工作正常:
http://localhost:49382/odata/Dogs
我得到一个Dog实体列表,没有Owner导航属性。
使用OData $ expand向其主人查询狗不起作用:
http://localhost:49382/odata/Dogs?$expand=Owner
对于所有Dog实体,我的响应是200,但是它们都没有在JSON中拥有所有者属性。
如果我查询我的元数据,我发现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#中的Owners。我在我的Dog控制器的Get方法中添加了这一行:
var test = db.Dogs.Include("Owner").ToList();
有了这个,我可以调试并看到相关的所有者被包括在内。每只狗都有与此列表相关联的所有者。
在实际返回的内容上使用.Include(“Owner”)无法解决问题 - 属性仍然无法到达客户端。
这似乎意味着导航属性正在运行,但不会被发送回客户端。这似乎是伤口表明OData或WebAPI的问题,我猜,但我不确定是什么。
另外,我在Global.asax文件的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();
}
答案 0 :(得分:10)
我找到了问题的解决方案,最终由三件事引起:
1。)我在我的控制器方法中使用[Queryable]属性,这是不推荐使用的。我需要使用较新的 [EnableQuery] 属性。
2.。)在我的WebApiConfig.cs文件中,我使用默认的config.EnableQuerySupport()启用查询。这已被弃用,并且已删除。
3。)我需要的扩展调用是以$ expand =所有者的形式,但需要采用 $ expand = owner 的形式,因为我在我的ODataConventionModelBuilder上启用了较低的驼峰案例。非常感谢Mark Bennetts,他的回答指出了这一点!
完成所有这些更改后,相关的所有者实体将与Dog实体一起返回。
答案 1 :(得分:6)
这是因为你正在使用
builder.EnableLowerCamelCase();
在ODataConventionModelBuilder设置中。
它不在您的查询选项$ expand子句中识别“Owner”,因为该路径在OData模型中确实不存在,因为它区分大小写。
如果您尝试请求此/狗?$ expand = owner我相信它会起作用,您将在JSON响应中返回狗和他们的所有者。
答案 2 :(得分:1)
我有一个非常类似的问题,我认为这是由完全相同的问题引起的。
我试图创建一些绑定的OData函数,这些函数将返回整个实体图,以便在某些情况下使客户端工作更容易,而不必为所有内容指定$ expand子句。
我在实体框架linq调用中指定了Include语句,并验证了返回数据确实在调试中完全填充,但是,就像你一样,我只返回顶级实体而没有其他任何内容。
问题在于用于OData的序列化
你会发现,如果从Owner类中删除主键,使其基本上成为一个复杂的实体,那么它将包含在OData序列化的JSON结果中,否则它将不会包含OData请求uri包含包含它的$ expand子句。
我试图找到一种方法在代码中插入$ expand子句以使其工作,但不幸的是空白。
希望这有帮助
答案 3 :(得分:0)
查看以下内容是否适合您。我在OData v4中进行了测试,因此您可能需要将[EnableQuery]
调整为[Queryable]
。您的db上下文应返回IQueryable结果,以便可能不需要.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; }