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」,

是一樣的。

 

參考資料:

ASP.NET Web API 無法輸出 Entity Framework 物件的解法

ScriptIgnoreAttribute Class

Lazy Loading