ตรวจพบการอ้างอิงแบบวงกลมในขณะที่ซีเรียลไลซ์วัตถุประเภท 'SubSonic.Schema .DatabaseColumn'


170

ฉันกำลังพยายามส่งคืน JSON แบบง่าย ๆ แต่ฉันมีปัญหาฉันมีดังต่อไปนี้

public JsonResult GetEventData()
{
    var data = Event.Find(x => x.ID != 0);
    return Json(data);
}

ฉันได้รับ HTTP 500 โดยมีข้อยกเว้นตามที่แสดงในชื่อของคำถามนี้ ฉันก็ลอง

var data = Event.All().ToList()

นั่นทำให้เกิดปัญหาเดียวกัน

นี่เป็นข้อบกพร่องหรือการใช้งานของฉันหรือไม่?


1
ดูอันนี้ มีวิธีแก้ไขโดยใช้ScriptIgnoreแอตทริบิวต์ stackoverflow.com/questions/1193857/subsonic-3-0-0-2-structs-tt
freddoo

นี่เป็นทางออกที่ดีที่สุดสำหรับฉัน ฉันมีเกม> ทัวร์นาเมนต์> เกม> ทัวร์นาเมนต์> เกม ฯลฯ ฉันวางScriptIgnoreคุณลักษณะไว้บนคุณสมบัติของ Tournament.Game และทำงานได้ดี :)
eth0

ในกรณีที่มีคนต้องการโซลูชัน "อัตโนมัติ" (ไม่ใช่วิธีปฏิบัติที่ดีที่สุด) สำหรับปัญหานี้ซึ่งไม่จำเป็นต้องมีรหัสเพิ่มเติมให้ตรวจสอบ QA นี้: อย่าทำให้เป็นลำดับอ้างอิงคลาส Entity Framework ใน JSON (ไลบรารี ServiceStack.Text)
nikib3ro

คำตอบ:


175

ดูเหมือนว่ามีการอ้างอิงแบบวงกลมในลำดับชั้นวัตถุของคุณซึ่งไม่สนับสนุนโดย serializer JSON คุณต้องการคอลัมน์ทั้งหมดหรือไม่ คุณสามารถเลือกได้เฉพาะคุณสมบัติที่คุณต้องการในมุมมอง:

return Json(new 
{  
    PropertyINeed1 = data.PropertyINeed1,
    PropertyINeed2 = data.PropertyINeed2
});

สิ่งนี้จะทำให้วัตถุ JSON ของคุณจางลงและเข้าใจง่ายขึ้น หากคุณมีคุณสมบัติมากมายAutoMapperสามารถใช้ในการแมประหว่างวัตถุ DTO และวัตถุมุมมองโดยอัตโนมัติ


ฉันคิดว่าบางทีการเลือกสิ่งที่ฉันต้องการอาจใช้งานได้ฉันคิดว่าการอ้างอิงแบบวงกลมนั้นเป็นเพราะในกรณีที่ฉันมี IQueryable <หมวดหมู่> ซึ่งจะมี IQueryable <Event>
Jon

7
Automapper ไม่รับประกันว่าคุณจะไม่ได้รับปัญหานี้ ฉันมาที่นี่เพื่อค้นหาคำตอบและจริง ๆ แล้วฉันใช้ automapper
กัปตัน Kenpachi

1
ดูคำตอบจาก @ClayKaboom ตามที่อธิบายว่าทำไมมันอาจเป็นวงกลม
PandaWood

106

ฉันมีปัญหาเดียวกันและแก้ไขโดย using Newtonsoft.Json;

var list = JsonConvert.SerializeObject(model,
    Formatting.None,
    new JsonSerializerSettings() {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
});

return Content(list, "application/json");

3
รหัสอินไลน์นี้ใช้งานได้ดีสำหรับฉัน สิ่งเดียวกันในการกำหนดค่าทั่วโลกตามที่กล่าวถึงโดย kravits88 ไม่ทำงานสำหรับฉัน นอกจากนี้ลายเซ็นของเมธอดควรได้รับการอัพเดตเพื่อส่งคืน ContentResult สำหรับโค้ดนี้
BiLaL

6
สิ่งนี้ควรถูกทำเครื่องหมายเป็นคำตอบที่ดีที่สุดเนื่องจากครอบคลุมกรณีที่คุณไม่สามารถใช้เวลาหลายชั่วโมงในการแปลงออบเจ็กต์ของคุณให้เป็นรูปแบบอื่น ๆ เช่นเดียวกับในคำตอบที่ทำเครื่องหมายว่ายอมรับแล้ว
Renan

56

สิ่งนี้เกิดขึ้นจริงเนื่องจากวัตถุที่ซับซ้อนเป็นสิ่งที่ทำให้วัตถุ json ที่เกิดขึ้นล้มเหลว และมันล้มเหลวเพราะเมื่อวัตถุถูกแมปมันจะแมปเด็ก ๆ ซึ่งแมปผู้ปกครองของพวกเขาทำให้การอ้างอิงแบบวงกลมเกิดขึ้น Json จะใช้เวลาไม่ จำกัด ในการทำให้เป็นอนุกรมดังนั้นจึงป้องกันปัญหาด้วยข้อยกเว้น

การแมปเอนทิตีกรอบยังก่อให้เกิดพฤติกรรมเดียวกันและวิธีการแก้ปัญหาคือการละทิ้งคุณสมบัติที่ไม่พึงประสงค์ทั้งหมด

เพียงอธิบายคำตอบสุดท้ายรหัสทั้งหมดจะเป็น:

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           new {
                Result = (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
               }
           , JsonRequestBehavior.AllowGet
           );
}

อาจเป็นไปได้ในกรณีที่คุณไม่ต้องการวัตถุภายในResultคุณสมบัติ:

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
           , JsonRequestBehavior.AllowGet
           );
}

1
+1 สำหรับสิ่งที่ชัดเจนและเข้าใจง่ายขอบคุณ @Clay ฉันชอบคำอธิบายของคุณเกี่ยวกับแนวคิดที่อยู่เบื้องหลังข้อผิดพลาด
Ajay2707

14

เพื่อสรุปสิ่งต่าง ๆ มีวิธีแก้ปัญหา 4 ข้อนี้:

โซลูชันที่ 1: ปิด ProxyCreation สำหรับ DBContext และคืนค่าในท้ายที่สุด

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        bool proxyCreation = db.Configuration.ProxyCreationEnabled;
        try
        {
            //set ProxyCreation to false
            db.Configuration.ProxyCreationEnabled = false;

            var data = db.Products.ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
        finally
        {
            //restore ProxyCreation to its original state
            db.Configuration.ProxyCreationEnabled = proxyCreation;
        }
    }

โซลูชันที่ 2: การใช้ JsonConvert โดยการตั้งค่า ReferenceLoopHandling เพื่อละเว้นการตั้งค่า serializer

    //using using Newtonsoft.Json;

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.ToList();

            JsonSerializerSettings jss = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
            var result = JsonConvert.SerializeObject(data, Formatting.Indented, jss);

            return Json(result, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

การแก้ปัญหาสองวิธีต่อไปนี้เหมือนกัน แต่การใช้แบบจำลองจะดีกว่าเพราะมันพิมพ์ได้ดี

โซลูชันที่ 3: ส่งคืนแบบจำลองซึ่งรวมถึงคุณสมบัติที่จำเป็นเท่านั้น

    private DBEntities db = new DBEntities();//dbcontext

    public class ProductModel
    {
        public int Product_ID { get; set;}

        public string Product_Name { get; set;}

        public double Product_Price { get; set;}
    }

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new ProductModel
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

โซลูชันที่ 4: ส่งคืนวัตถุไดนามิกใหม่ซึ่งรวมถึงคุณสมบัติที่จำเป็นเท่านั้น

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

7

JSON เช่น xml และรูปแบบอื่น ๆ เป็นรูปแบบการทำให้เป็นอันดับแบบต้นไม้ มันจะไม่รักคุณถ้าคุณมีการอ้างอิงแบบวงกลมในวัตถุของคุณเป็น "ต้นไม้" จะเป็น:

root B => child A => parent B => child A => parent B => ...

มักจะมีวิธีการปิดการใช้งานการนำทางตามเส้นทางที่แน่นอน เช่นกับที่คุณอาจทำเครื่องหมายคุณสมบัติผู้ปกครองเป็นXmlSerializer XmlIgnoreฉันไม่รู้ว่าสิ่งนี้เป็นไปได้ด้วย json serializer ในคำถามหรือไม่ว่าDatabaseColumnมีเครื่องหมายที่เหมาะสม ( ไม่น่าเป็นไปได้มากเพราะมันจะต้องอ้างอิงทุก ๆ serialization API)


4

เป็นเพราะเทมเพลต DbContext T4 ใหม่ที่ใช้สำหรับสร้างเอนทิตี EntityFramework เพื่อให้สามารถติดตามการเปลี่ยนแปลงเทมเพลตนี้ใช้รูปแบบพร็อกซีโดยห่อ POCO ที่ดีของคุณไว้กับพวกเขา นี่จะทำให้เกิดปัญหาเมื่อทำการซีเรียลไลซ์ด้วย JavaScriptSerializer

ดังนั้นคำตอบ 2 ข้อคือ:

  1. ไม่ว่าคุณจะเป็นอันดับและกลับคุณสมบัติที่คุณต้องการในไคลเอนต์
  2. คุณสามารถปิดการสร้างพร็อกซีอัตโนมัติโดยการตั้งค่าในการกำหนดค่าบริบท

    context.Configuration.ProxyCreationEnabled = false;

อธิบายได้ดีมากในบทความด้านล่าง

http://juristr.com/blog/2011/08/javascriptserializer-circular-reference/


4

ใช้ Newtonsoft.Json: ในวิธี Global.asax Application_Start ของคุณเพิ่มบรรทัดนี้:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

1
เห็นได้ชัดว่าตรงไปตรงมามาก แต่ไม่ได้ผลสำหรับฉัน
BiLaL


4

หลีกเลี่ยงการแปลงวัตถุตารางโดยตรง หากมีการตั้งค่าความสัมพันธ์ระหว่างตารางอื่น ๆ อาจทำให้เกิดข้อผิดพลาดนี้ได้ แต่คุณสามารถสร้างคลาสโมเดลกำหนดค่าให้กับอ็อบเจ็กต์คลาสจากนั้นทำให้เป็นอนุกรม


3

คำตอบที่ให้นั้นดี แต่ฉันคิดว่าพวกเขาสามารถปรับปรุงได้โดยการเพิ่มมุมมอง "สถาปัตยกรรม"

ตรวจสอบ

MVC's Controller.Jsonฟังก์ชั่นกำลังทำงาน แต่มันแย่มากที่ให้ข้อผิดพลาดที่เกี่ยวข้องในกรณีนี้ โดยการใช้Newtonsoft.Json.JsonConvert.SerializeObjectข้อผิดพลาดระบุสิ่งที่เป็นคุณสมบัติที่ก่อให้เกิดการอ้างอิงแบบวงกลม สิ่งนี้มีประโยชน์อย่างยิ่งเมื่อซีเรียลไลซ์ของลำดับชั้นวัตถุที่ซับซ้อนมากขึ้น

สถาปัตยกรรมที่เหมาะสม

เราไม่ควรพยายามสร้างโมเดลข้อมูลให้เป็นอนุกรม (เช่นรุ่น EF) เนื่องจากคุณสมบัติการนำทางของ ORM นั้นเป็นเส้นทางสู่ความหายนะเมื่อมาถึงการทำให้เป็นอนุกรม การไหลของข้อมูลควรเป็นดังนี้:

Database -> data models -> service models -> JSON string 

สามารถขอรับแบบจำลองบริการได้จากตัวแบบข้อมูลโดยใช้ตัวทำแผนที่อัตโนมัติ (เช่นAutomapper ) แม้ว่าสิ่งนี้จะไม่รับประกันการขาดการอ้างอิงแบบวงกลม แต่การออกแบบที่เหมาะสมควรทำ: รูปแบบการบริการควรมีสิ่งที่ผู้ใช้บริการต้องการ (เช่นคุณสมบัติ)

ในกรณีที่หายากเหล่านั้นเมื่อลูกค้าร้องขอลำดับชั้นที่เกี่ยวข้องกับประเภทของวัตถุที่เหมือนกันในระดับที่แตกต่างกันบริการสามารถสร้างโครงสร้างเชิงเส้นที่มีความสัมพันธ์ผู้ปกครองลูก> (ใช้เพียงตัวระบุไม่อ้างอิง)

แอปพลิเคชั่นที่ทันสมัยมักจะหลีกเลี่ยงการโหลดโครงสร้างข้อมูลที่ซับซ้อนในคราวเดียวและโมเดลบริการควรมีขนาดเล็ก เช่น:

  1. เข้าถึงกิจกรรม - โหลดข้อมูลส่วนหัวเท่านั้น (ตัวระบุชื่อวันที่ ฯลฯ ) ถูกโหลด -> service model (JSON) ที่มีข้อมูลส่วนหัวเท่านั้น
  2. รายการผู้เข้าร่วมที่ได้รับการจัดการ - เข้าถึงป๊อปอัพและขี้เกียจโหลดรายการ -> service model (JSON) ที่มีเฉพาะรายการของผู้เข้าร่วม

1

ฉันกำลังใช้การแก้ไขเนื่องจากการใช้การทำให้ล้มลงในมุมมอง MVC5

ในการดำเนินการ

return Json(ModelHelper.GetJsonModel<Core_User>(viewModel));

ฟังก์ชัน

   public static TEntity GetJsonModel<TEntity>(TEntity Entity) where TEntity : class
    {
        TEntity Entity_ = Activator.CreateInstance(typeof(TEntity)) as TEntity;
        foreach (var item in Entity.GetType().GetProperties())
        {
            if (item.PropertyType.ToString().IndexOf("Generic.ICollection") == -1 && item.PropertyType.ToString().IndexOf("SaymenCore.DAL.") == -1)
                item.SetValue(Entity_, Entity.GetPropValue(item.Name));
        }
        return Entity_;  
    }

0

คุณสามารถสังเกตเห็นคุณสมบัติที่ทำให้เกิดการอ้างอิงแบบวงกลม จากนั้นคุณสามารถทำสิ่งที่ชอบ:

private Object DeCircular(Object object)
{
   // Set properties that cause the circular reference to null

   return object
}

-1
//first: Create a class as your view model

public class EventViewModel 
{
 public int Id{get;set}
 public string Property1{get;set;}
 public string Property2{get;set;}
}
//then from your method
[HttpGet]
public async Task<ActionResult> GetEvent()
{
 var events = await db.Event.Find(x => x.ID != 0);
 List<EventViewModel> model = events.Select(event => new EventViewModel(){
 Id = event.Id,
 Property1 = event.Property1,
 Property1 = event.Property2
}).ToList();
 return Json(new{ data = model }, JsonRequestBehavior.AllowGet);
}

นี่ไม่ได้ตอบคำถาม
Dane ฉัน

-1

ทางเลือกที่ง่ายกว่าในการแก้ปัญหานี้คือส่งคืนสตริงและจัดรูปแบบสตริงนั้นเป็น json ด้วย JavaScriptSerializer

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

เป็นสิ่งสำคัญส่วน "เลือก" ซึ่งเลือกคุณสมบัติที่คุณต้องการในมุมมองของคุณ วัตถุบางอย่างมีการอ้างอิงสำหรับผู้ปกครอง หากคุณไม่ได้เลือกคุณลักษณะการอ้างอิงแบบวงกลมอาจปรากฏขึ้นหากคุณใช้ตารางโดยรวม

อย่าทำอย่างนี้:

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.toList();
   return j.Serialize(entityList );
}

ทำเช่นนี้แทนหากคุณไม่ต้องการตารางทั้งหมด:

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

สิ่งนี้จะช่วยให้การแสดงผลมีข้อมูลน้อยลงเพียงแค่มีคุณลักษณะที่คุณต้องการและทำให้เว็บของคุณทำงานได้เร็วขึ้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.