简体   繁体   English

Web Api JSON中丢失了EF TPH继承

[英]EF TPH inheritance lost in Web Api JSON

I've successfully set up some classes that use TPH EF inheritance , MyBaseClass , MySubClass1 , MySubClass2 etc. 我已成功设置了一些使用TPH EF继承MyBaseClassMySubClass1MySubClass2等的类。

When querying using Linq context.MyBaseClasses.Where(...) , the objects returned all correctly use the subclass specified by the Discriminator field in the database. 使用Linq context.MyBaseClasses.Where(...)查询时,返回的对象都正确使用数据库中Discriminator字段指定的子类。 (So I might end up with a list containing a mix of objects of MySubClass1 , or MySubClass2 .) (所以我最终可能会得到一个包含MySubClass1MySubClass2混合对象的MySubClass2 。)

However, when I pass these objects to a WPF application, via a JSON Web Api call, the objects received are all of MyBaseClass , rather than the correct sub class they started off at. 但是,当我通过JSON Web Api调用将这些对象传递给WPF应用程序时,收到的对象都是MyBaseClass ,而不是它们开始的正确子类。

The object property they are returned via is of type public virtual List<MyBaseClass> MyThings , so I guess it makes sense that they all end up as this type, but I want to retain the correct sub class type for each object. 它们返回的对象属性是public virtual List<MyBaseClass> MyThings ,所以我猜它们最终都是这种类型,但我想为每个对象保留正确的子类类型。

How do I achieve this? 我该如何实现这一目标? Do I need to force the EF Discriminator field to be sent along with all the other data somehow? 我是否需要强制将EF Discriminator字段与所有其他数据一起发送?

Edit 编辑

1. 1。

At the client end, I'm now attempting to deserialize the JSON (including $type data) like so (with no luck, the items are still converted back to their base class) 在客户端,我现在正试图反序列化JSON(包括$类型数据),如此(没有运气,项目仍然转换回他们的基类)

HttpResponseMessage response = GetClient().GetAsync(url).Result;

if (response.IsSuccessStatusCode)
{
    string jsonMessage;
    using (Stream responseStream = response.Content.ReadAsStreamAsync().Result)
    {
        jsonMessage = new StreamReader(responseStream).ReadToEnd();
    }


    List<My.Domain.Models.MyBaseClass> thingsToReturn;

    //new method    
    thingsToReturn = JsonConvert.DeserializeObject<List<My.Domain.Models.MyBaseClass>>(jsonMessage);

    //previous method
    //thingsToReturn = response.Content.ReadAsAsync<List<My.Domain.Models.MyBaseClass>>().Result;

    return thingsToReturn;
}

2. 2。

Following Anish's advice re SerializerSettings.TypeNameHandling , I've now got $type information appearing in my JSON, however this is of type System.Data.Entity.DynamicProxies.SubClass1_5E07A4CE2F037430DC7BFA00593.... is this OK for the client end to deserialize back into SubClass1 successfully? 按照Anish的建议重新使用SerializerSettings.TypeNameHandling ,我现在已经在我的JSON中出现了$ type信息,但是这个类型是System.Data.Entity.DynamicProxies.SubClass1_5E07A4CE2F037430DC7BFA00593....这样客户端可以反序列化回来SubClass1成功了吗?

This is a serialization concern. 这是序列化问题。

You can customize Json.Net's serializer settings to include type information with your json objects. 您可以自定义Json.Net的序列化程序设置,以包含json对象的类型信息。

Add this to your HttpConfiguration : 将此添加到您的HttpConfiguration

config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;

Where config is the HttpConfiguration instance that you use to configure and initialize Asp.Net WebApi . 其中config是用于配置和初始化Asp.Net WebApiHttpConfiguration实例。

This will tell Json.Net to add some type information to each json object that has type ambiguity. 这将告诉Json.Net向每个具有类型歧义的json对象添加一些类型信息。 Such an object would look like this: 这样的对象看起来像这样:

{
    "$type":"MyProjectContainingMyTypes.MySubClass1, MyProjectContainingMyTypes",
    "Name": "Tyrion Lannister",
    "DisplayName": "The Imp",
    "Traits": ["funny", "awesome", "clever"]
}

Json.Net will know how to deal with this when you deserialize this on the WPF side. 当您在WPF端反序列化时, Json.Net将知道如何处理此问题。

This should work, in your WPF app: 这应该适用于您的WPF应用:

var things = JsonConvert.DeserializeObject<List<MyBaseClass>>(jsonString);

Then you can cast the objects in the things list to their respective derived types. 然后,您可以将things列表中的对象强制转换为各自的派生类型。

Of course, your WPF application will need to have a reference to the project where you define MyBaseClass and MySubClass1 . 当然,您的WPF应用程序需要引用您定义MyBaseClassMySubClass1的项目。

Edit 编辑

Thanks Anish, that's almost sorted it. 谢谢Anish,这几乎是它的排序。 I can see the correct $type data in the JSON, I'm just being a dunce, and I'm not sure how to get the WebApi response as a jsonString? 我可以在JSON中看到正确的$ type数据,我只是一个笨蛋,我不知道如何将WebApi响应作为jsonString? At the minute I'm doing response.Content.ReadAsAsync>().Result; 我正在做响应.Content.ReadAsAsync>()。结果; to auto deserialize the data. 自动反序列化数据。

In response to your comment, you can read the content as a string like this: 在回复您的评论时,您可以将内容读取为如下字符串:

var jsonString = response.Content.ReadAsStringAsync().Result;

Edit 2 编辑2

Anish, thanks so much for your input. 阿尼什,非常感谢你的投入。 As you can see, I've managed to get the JSON as a string now, but even using JsonConvert.DeserializeObject I'm still getting the same issue. 正如您所看到的,我现在已经设法将JSON作为字符串,但即使使用JsonConvert.DeserializeObject,我仍然遇到同样的问题。 Do you think it is (as I mention in Edit 2) to do with the $type being returned from the service incorrectly (as an EF proxy type)? 你是否认为(正如我在编辑2中提到的那样)不正确地从服务返回的$类型(作为EF代理类型)?

In response to your comment, yes you will not be able to deserialize the EF proxy type into the type you want. 在回复您的评论时,是的,您将无法将EF代理类型反序列化为您想要的类型。 This issue needs to be resolved on the WebApi side. 需要在WebApi端解决此问题。

The proxy classes are created to allow you to lazy load entities. 创建代理类以允许您延迟加载实体。 Proxies are generally used represent referenced/nested entities. 代理通常用于表示引用/嵌套实体。 This allows the fetching of referenced entities from the database to be deferred until required, if required at all. 这允许从数据库中提取被引用的实体,直到需要时,如果需要的话。

Here is a link to some documentation around lazy and eager loading entities with EF . 这是一个关于使用EF的 懒惰急切加载实体的一些文档的链接

Solution

You want to hydrate the list of objects in your WebApi controller action and return it, this will tell EF to load the entities from the database and new up instances of your classes. 你想滋润的对象列表中您的WebAPI控制器操作,并返回它,这会告诉EF加载从数据库中实体和你的类的新一轮上涨的情况。

You have a few options here: 你有几个选择:

Option 1 选项1

Call ToList() on the query to hydrate the collection: 在查询上调用ToList()来水合集合:

var result = (from t in dbContext.Things select t).ToList();

or 要么

var result = dbContext.Things.ToList();

Naturally, you don't want to return an unbounded result set so add a range: 当然,您不希望返回无界结果集,因此添加范围:

var result = (from t in dbContext.Things select t).Skip(0).Take(10).ToList();

or 要么

var result = dbContext.Things.Skip(0).Take(10).ToList();

Bear in mind that with method you will have to explicitly hydrate nested objects like this: 请记住,使用方法,您必须明确地水合嵌套对象,如下所示:

var result = dbContext
             .Things
             .Include(t => t.SomePropertyThatRepresentsSomeNestedObject) 
             .Skip(0)
             .Take(10)
             .ToList();

Option 2 选项2

Turn off lazy loading for your DbContext. 关闭DbContext的延迟加载

Personally, I'd go with Option 1 , I think it is better to know your entities and have control over when and what you hydrate. 就个人而言,我会选择选项1 ,我认为最好知道你的实体,并控制你何时以及你的水合物。

Many, many thanks to Anish , who's answer set me off on the right track, along with this excellent article . 很多,非常感谢Anish ,他的回答让我走上正轨, 这篇优秀文章也是如此

The steps I had to take to get the types of the inherited objects to pass through the web api service are as follows: 我必须采取的步骤来获取要通过web api服务传递的继承对象的类型,如下所示:

Server Side 服务器端

The key things were to set the JsonFormatter Serializer TypeNameHandling setting to TypeNameHandling.Auto . 关键是将JsonFormatter Serializer TypeNameHandling设置为TypeNameHandling.Auto This can be done in WebApiConfig.Register() , but it will then obviously add a $type property to all JSON objects returned by your web service calls, or, you can simply decorate the property of the object you need the $type for. 这可以在WebApiConfig.Register() ,但它显然会向您的Web服务调用返回的所有JSON对象添加$ type属性,或者,您可以简单地装饰您需要$ type的对象的属性。

WebApiConfig Method WebApiConfig方法

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto;        

Property Decoration Method 物业装修方法

[Newtonsoft.Json.JsonProperty(ItemTypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto)]
public virtual List<MyBaseClass> Things { get; set; }

To get the correct value in the $type property in the JSON, and not the EF proxy class name, I turned off the ProxyCreationEnabled property before performing the Linq query that returned the objects based on MyBaseClass. 为了在JSON中的$ type属性中获取正确的值,而不是EF代理类名,我在执行基于MyBaseClass返回对象的Linq查询之前关闭了ProxyCreationEnabled属性。

dbContext.Configuration.ProxyCreationEnabled = false;
List<MyBaseClass> things = dbContext.MyBaseClasses.Include("This").Include("That").ToList();

Client Side 客户端

I had to add a JsonMediaTypeFormatter with SerializerSettings = { TypeNameHandling = TypeNameHandling.Auto } to the ReadAsAsync() call and then the (correctly typed) objects mapped to their sub class happily. 我不得不添加一个JsonMediaTypeFormatterSerializerSettings = { TypeNameHandling = TypeNameHandling.Auto }ReadAsAsync()调用,然后(输入正确)对象映射到其子类愉快。

HttpResponseMessage response = GetClient().GetAsync(url).Result;

if (response.IsSuccessStatusCode)
{
    //this formatter responds to the $type parameter passed in the JSON to allow us to correctly map object types
    //https://kirmir.wordpress.com/2014/05/16/polymorphic-serialization-using-newton-json-net-in-httpcontent/
    var formatter = new JsonMediaTypeFormatter
    {
        SerializerSettings = { TypeNameHandling = TypeNameHandling.Auto }
    };

    List<MyBaseClass> thingsToReturn;
    thingsToReturn = response.Content.ReadAsAsync<List<MyBaseClass>>(new List<MediaTypeFormatter> { formatter }).Result;
    return productTestsToReturn;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM