Entity Framework Model物件之Json.NET還原問題

應用ASP.NET MVC時,透過ActionResult傳回Entity Framework資料物件的JSON格式,接收端試著用Json.NET解析卻發生錯誤! 研究發現,在ASP.NET MVC Action中以return JSON(someObject)方式傳回JSON字串時,會使用JavaScriptSerializer進行序列化。換句話說,問題出在"使用JavaScriptSerializer序列化Entity Framework資料物件,再以Json.NET反序列化還原",以下用範例程式重現問題。

借用EF View Primary Key錯置問題一文的Team類別,程式由資料庫查詢一筆資料,分別使用JavaScriptSerializer及Json.NET序列化,再用JavaScriptSerializer還原Json.NET產生的JSON字串,用Json.NET還原JavaScriptSerializer產生的JSON字串,進行交叉測試。

static void Main(string[] args)
{
    using (var ctx = new LabEntities())
    {
        //由資料庫取得Team物件
        var team = ctx.Team.Single(o => o.TeamName == "Avengers");
        //使用JavaScriptSerializer序列化
        JavaScriptSerializer jss = new JavaScriptSerializer();
        string json1 = jss.Serialize(team);
        Console.WriteLine("JavaScriptSerializer: {0}", json1);
        //使用Json.NET序列化
        string json2 = JsonConvert.SerializeObject(team);
        Console.WriteLine("Json.NET: {0}", json2);
        //使用JavaScriptSerializer還原Json.NET序列化的結果
        try
        {
            var t = jss.Deserialize<Team>(json2);
            Console.WriteLine("JavaScriptSerializer: {0}", t.TeamName);
        }
        catch (Exception ex)
        {
            Console.WriteLine("JavaScriptSerializer Error: {0}", ex.Message);
        }
        //使用Json.NET還原JavaScriptSerializer序列化的結果
        try
        {
            var t = JsonConvert.DeserializeObject<Team>(json1);
            Console.WriteLine("Json.NET: {0}", t.TeamName);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Json.NET Error: {0}", ex.Message);
        }
    }
    Console.Read();
}

執行結果如下:

JavaScriptSerializer: {"TeamId":"T003","TeamName":"Avengers","Country":"USA","En tityState":2,"EntityKey":{"EntitySetName":"Team","EntityContainerName":"LabEntit ies","EntityKeyValues":[{"Key":"TeamId","Value":"T003"}],"IsTemporary":false}}
Json.NET: {"$id":"1","TeamId":"T003","TeamName":"Avengers","Country":"USA","Enti tyKey":{"$id":"2","EntitySetName":"Team","EntityContainerName":"LabEntities","En tityKeyValues":[{"Key":"TeamId","Type":"System.String","Value":"T003"}]}}
JavaScriptSerializer: Avengers
Json.NET Error: Expected JSON property 'Type'.

由JSON字串發現Team除了我們所認知的TeamId、TeamName及Country屬性外,還有EntityState、EntityKey兩個額外EF專用屬性,而由資料庫取出的Team物件EntityState/EntityKey會有內容(註: new Team()產生尚未加入資料庫的則無值),而Json.NET針對EntityKey做了額外處理,序列化後多產生了$id屬性。(在.NET Class中本無此屬性)

JavaScriptSerializer可以還原Json.NET所產生多出"$id"的JSON字串;但Json.NET在還原JavaScriptSerializer所產生JSON字串時,卻冒出Expected JSON property 'Type'.錯誤。

推測Json.NET在還原具有EntityKey屬性的JSON字串時,預期應有$id等自己客製的額外屬性,一旦落空就發生問題。為了印證此點,我將JavaScriptSerializer產生的JSON字串,先還原成動態JObject物件,移去EntityKey欄位後再重新產生JSON字串(其中不含EntityKey),修正後的JSON字中使用Json.NET還原便能正確解析成Team物件。

static void Main(string[] args)
{
    using (var ctx = new LabEntities())
    {
        //由資料庫取得Team物件
        var team = ctx.Team.Single(o => o.TeamName == "Avengers");
        //使用JavaScriptSerializer序列化
        JavaScriptSerializer jss = new JavaScriptSerializer();
        string json = jss.Serialize(team);
        Console.WriteLine("JavaScriptSerializer: {0}", json);
        //試著拆解掉EntityKey
        JObject jo = JsonConvert.DeserializeObject<JObject>(json);
        jo.Remove("EntityKey");
        //產生修正版JSON
        string fixedJson = jo.ToString();
        Console.WriteLine("Fixed JSONr: {0}", fixedJson);
        //使用Json.NET還原修正版JSON
        var t = JsonConvert.DeserializeObject<Team>(fixedJson);
        Console.WriteLine("Json.NET: {0}", t.TeamName);
    }
    Console.Read();
}

以下是執行結果:

JavaScriptSerializer: {"TeamId":"T003","TeamName":"Avengers","Country":"USA","En
tityState":2,"EntityKey": "EntitySetName":"Team","EntityContainerName":"LabEntities","EntityKeyValues":[{"Key":"TeamId","Value":"T003"}],"IsTemporary":false}}
Fixed JSONr: {
  "TeamId": "T003",
  "TeamName": "Avengers",
  "Country": "USA",
  "EntityState": 2
}
Json.NET: Avengers

先解成JObject,移除EntityKey後重新產生JSON再反序列化感覺有點笨拙,爬文找到更優雅的解法,利用Json.NET強大的擴充性,實做一個ExcludeEntityKeyContractResolver,便可指定Json.NET在反序列化時忽略EntityKey,一次到位:

static void Main(string[] args)
{
    using (var ctx = new LabEntities())
    {
        //由資料庫取得Team物件
        var team = ctx.Team.Single(o => o.TeamName == "Avengers");
        //使用JavaScriptSerializer序列化
        JavaScriptSerializer jss = new JavaScriptSerializer();
        string json = jss.Serialize(team);
        Console.WriteLine("JavaScriptSerializer: {0}", json);
        //加入客製ContractResolver,還原時無視EntityKey
        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.ContractResolver = new ExcludeEntityKeyContractResolver();
        //使用Json.NET還原JavaScriptSerializer序列化的結果
        var t = JsonConvert.DeserializeObject<Team>(json, settings);
        Console.WriteLine("Json.NET: {0}", t.TeamName);
    }
    Console.Read();
}
 
public class ExcludeEntityKeyContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(
        Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> properties = 
              base.CreateProperties(type, memberSerialization);
        return properties.Where(p => 
               p.PropertyType != typeof(System.Data.EntityKey)).ToList();
    }
}

測試成功!

JavaScriptSerializer: {"TeamId":"T003","TeamName":"Avengers","Country":"USA","EntityState":2,"EntityKey":{"EntitySetName":"Team","EntityContainerName":"LabEntities","EntityKeyValues":[{"Key":"TeamId","Value":"T003"}],"IsTemporary":false}}
Json.NET: Avengers

歡迎推文分享:
Published 06 July 2012 07:46 PM 由 Jeffrey
Filed under: ,
Views: 9,202



意見

# KKBruce said on 06 July, 2012 10:58 AM

補充一下,在目前 MVC 4 RC 中,ActionResult型別裡的 Json 已經改由 JSON.NET 來實作,未來的 MVC 4 專案中應該不會再出現此情況。(其實 MVC 4 RC 就沒有此情況了)

^_^

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<July 2012>
SunMonTueWedThuFriSat
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication