Self referencing loop detected for property
在開發ASP.NET MVC時,如果專案使用Entity Framework技術的話,
當 Entity 與 Entity 之間包含導覽屬性 (Navigation Property)並配合Json技術時,
則會出現「Self referencing loop detected for property」錯誤,
一解決方法是將所要的東西重新倒進新建的 View Model 來避掉循環參考問題。
而本文章再介紹其他做法,以下重現問題與解法。
先準備資料表
CREATE DATABASE testDB GO USE testDB GO CREATE TABLE [TEST_PERSON]( [id] [tinyint] PRIMARY KEY, [name] [nvarchar](20) NOT NULL, [age] [tinyint] NOT NULL, ) INSERT INTO [TEST_PERSON] (id,name,age) VALUES (1,'Bill',39); INSERT INTO [TEST_PERSON] (id,name,age) VALUES (2,'Mary',20); INSERT INTO [TEST_PERSON] (id,name,age) VALUES (3,'Junefo',10); GO CREATE TABLE [TEST_CELL]( [no] [int] PRIMARY KEY, [cell] [nchar](10) NULL, [id] [tinyint] NULL REFERENCES TEST_PERSON(id) ON UPDATE CASCADE ON DELETE CASCADE, ) INSERT INTO [TEST_CELL] (no,cell,id) VALUES (1,'0911000001',1); INSERT INTO [TEST_CELL] (no,cell,id) VALUES (2,'0911000002',1); INSERT INTO [TEST_CELL] (no,cell,id) VALUES (3,'0922000001',2); INSERT INTO [TEST_CELL] (no,cell,id) VALUES (4,'0922000002',2); INSERT INTO [TEST_CELL] (no,cell,id) VALUES (5,'0933000001',3); INSERT INTO [TEST_CELL] (no,cell,id) VALUES (6,'0933000002',3); GO
於MVC專案使用Entity Framework技術連結資料庫
至於如何使用Entity Framework技術連結資料庫我就不贅述了,請參考DB First
一、使用 Newtonsoft 的處理方法
1、HomeController.cs 內容為
using System.Web.Mvc;
using Newtonsoft.Json;
using WebApplication1.Models;
using System.Collections.Generic;
using System.Linq;
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
public testDBEntities db = new testDBEntities();
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(int no)
{
IEnumerable<TEST_CELL> result = db.TEST_CELL.Where(a => a.no == no);
return Content(JsonConvert.SerializeObject(result), "application/json");
}
}
}
2、Index.cshtml 內容為
@{ ViewBag.Title = "Home Page"; } <span>取第</span> <input type="text" name="no" value="2" /> <span>電話號碼</span> <br> <button>確定</button> @section scripts{ <script> $("button").click(function myfunction() { var postdata = $("input").serialize(); $.ajax({ url: '@Url.Action("Index")', type: 'POST', data: postdata, dataType: "json", async: false, cache: false, success: function (data) { $.each(data, function myfunction(i, val) { alert("cell=" + val.cell); }); } }); }); </script> }
3、執行結果
結果卻發生「Self referencing loop detected with type...」錯誤
4、解決方法
對當初有設定參考的資料表(TEST_CELL),其 Model 裡的 TEST_CELL.cs 檔裡的 metadata 設定 JsonIgnore 屬性,
但記得要 include Newtonsoft.Json 命名空間。
using Newtonsoft.Json;
namespace WebApplication1.Models
{
using System;
using System.Collections.Generic;
public partial class TEST_CELL
{
public int no { get; set; }
public string cell { get; set; }
public Nullable<byte> id { get; set; }
[JsonIgnore]
public virtual TEST_PERSON TEST_PERSON { get; set; }
}
}
執行結果成功
JsonIgnore 屬性加到 TEST_CELL 或 TEST_PERSON 其一,
都可解決循環參考問題,被加的一方,則在吐出 Json 時,
將不會循環參考到另一方。
5、其他 - 使用Partial Class
但只要從Visual Studio異動EDMX內容,自訂修改後的程式碼都會被Visual Studio的程式碼產生器覆蓋你之前的變更,
可以用Partial Class來改善缺點。
using Newtonsoft.Json;
namespace WebApplication1.Models
{
public partial class TEST_CELL
{
[JsonIgnore]
public virtual TEST_PERSON TEST_PERSON { get; set; }
}
}
PartialClass.cs與TEST_CELL.cs對照圖
較正確的方法應該採用MetadataType Model Metadata方法。
二、使用 System.Web.Script.Serialization 的處理方法
資料表的準備承前例
1、針對不想被循環參考的屬性添加 ScriptIgnore metadata
TEST_CELL.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace WebApplication1.Models
{
using System;
using System.Collections.Generic;
public partial class TEST_CELL
{
public int no { get; set; }
public string cell { get; set; }
public Nullable<byte> id { get; set; }
[System.Web.Script.Serialization.ScriptIgnore(ApplyToOverrides = true)]
public virtual TEST_PERSON TEST_PERSON { get; set; }
}
}
2、HomeController.cs 內容為
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using WebApplication1.Models;
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
public testDBEntities db = new testDBEntities();
public ActionResult Index()
{
//IEnumerable<TEST_PERSON> result = db.TEST_PERSON.Where(a => a.id == 1);
IEnumerable<TEST_CELL> result = db.TEST_CELL.Where(a => a.no == 1);
return Json(result, JsonRequestBehavior.AllowGet);
}
}
}
3、執行畫面為
4、說明
ScriptIgnore metadata 不管是加到 TEST_CELL 或是 TEST_PERSON 其一,
都可解決循環參考問題,被加的一方,則在吐出 Json 時,
將不會循環參考到另一方。
三、修改 Entity Framework 的 Edmx 屬性
資料表的準備承前例
1、將 Entity Framework 的 Edmx 屬性的 Lazy Loading Enabled 設為 false
2、HomeController.cs 內容為
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using WebApplication1.Models;
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
public testDBEntities db = new testDBEntities();
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
IEnumerable<TEST_CELL> result = db.TEST_CELL.Where(a => a.no == 1);
return Content(JsonConvert.SerializeObject(result), "application/json");
}
public ActionResult Contact()
{
IEnumerable<TEST_PERSON> result = db.TEST_PERSON.Where(a => a.id == 1);
return Content(JsonConvert.SerializeObject(result), "application/json");
}
}
}
3、About.cshtml 與 Contact.cshtml 執行畫面為
4、說明
將 Entity Framework 的 Edmx 屬性的 Lazy Loading Enabled 設為 false 也可以處理循環參考問題。
其設定相當於對所有的 TEST_CELL 或是 TEST_PERSON 都設定不循環參考了。
四、設定 Context 的 Configuration 的 LazyLoadingEnabled
資料表的準備承前例
1、在 Model1.Context.cs 新增一段程式如下黃色框框
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace WebApplication1.Models
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class testDBEntities : DbContext
{
public testDBEntities()
: base("name=testDBEntities")
{
this.Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<TEST_CELL> TEST_CELL { get; set; }
public virtual DbSet<TEST_PERSON> TEST_PERSON { get; set; }
}
}
2、HomeController.cs 內容為跟第三項範例一樣
3、About.cshtml 與 Contact.cshtml 執行畫面其實也會跟第三項範例一樣
4、說明
「設定 Context 的 Configuration 的 LazyLoadingEnabled 為 false」其效果跟
「將 Entity Framework 的 Edmx 屬性的 Lazy Loading Enabled 設為 false」,
是一樣的。
參考資料: