วิธีแก้ไขปัญหาการอ้างอิงแบบวงกลมด้วย JSON และ Entity


13

ฉันได้ทำการทดลองกับการสร้างเว็บไซต์ที่ใช้ประโยชน์จาก MVC กับ JSON สำหรับเลเยอร์งานนำเสนอและกรอบงานเอนทิตีสำหรับโมเดลข้อมูล / ฐานข้อมูล ปัญหาของฉันมาพร้อมกับการทำให้วัตถุโมเดลของฉันเป็นอนุกรมลงใน JSON

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

class parent
{
   public List<child> Children{get;set;}
   public int Id{get;set;}

}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId")]
    public parent MyParent{get;set;}
    public string name{get;set;}
 }

เมื่อส่งคืนวัตถุ "พาเรนต์" ผ่าน JsonResult ข้อผิดพลาดการอ้างอิงแบบวงกลมจะถูกส่งออกไปเนื่องจาก "child" มีคุณสมบัติของคลาสพาเรนต์

ฉันได้ลองใช้ ScriptIgnore แล้ว แต่ฉันไม่สามารถดูวัตถุลูกได้ ฉันจะต้องแสดงข้อมูลในมุมมองลูกผู้ปกครองในบางจุด

ฉันพยายามสร้างคลาสพื้นฐานสำหรับทั้งผู้ปกครองและเด็กที่ไม่มีการอ้างอิงแบบวงกลม น่าเสียดายที่เมื่อฉันพยายามส่ง baseParent และ baseChild สิ่งเหล่านี้อ่านโดย JSON Parser เป็นคลาสที่ได้รับมา (ฉันค่อนข้างแน่ใจว่าแนวคิดนี้หนีฉันไป)

Base.baseParent basep = (Base.baseParent)parent;
return Json(basep, JsonRequestBehavior.AllowGet);

วิธีแก้ไขปัญหาเดียวที่ฉันมีคือสร้างโมเดล "ดู" ฉันสร้างรุ่นฐานข้อมูลที่เรียบง่ายซึ่งไม่มีการอ้างอิงไปยังคลาสพาเรนต์ มุมมองโมเดลเหล่านี้แต่ละวิธีมีวิธีส่งคืนเวอร์ชันฐานข้อมูลและตัวสร้างที่ใช้โมเดลฐานข้อมูลเป็นพารามิเตอร์ (viewmodel.name = databasemodel.name) วิธีการนี้ดูเหมือนจะถูกบังคับแม้ว่าจะใช้งานได้

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

เป้าหมายสุดท้ายของฉันคือการมีแอปพลิเคชัน MVC ที่ดีซึ่งใช้ประโยชน์จาก JSON อย่างมากสำหรับการสื่อสารกับเซิร์ฟเวอร์และแสดงข้อมูล ในขณะที่รักษาแบบจำลองที่สอดคล้องกันข้ามชั้น (หรือดีที่สุดเท่าที่ฉันจะทำได้)

คำตอบ:


6

ฉันเห็นสองวิชาที่แตกต่างกันในคำถามของคุณ:

  • วิธีจัดการการอ้างอิงแบบวงกลมเมื่อซีเรียลไลซ์กับ JSON
  • การใช้เอนทิตี้ของ EF เป็นเรื่องที่ปลอดภัยในมุมมองของคุณอย่างไร

ที่เกี่ยวข้องกับการอ้างอิงแบบวงกลมฉันขอโทษที่จะบอกว่าไม่มีวิธีง่ายๆ ก่อนเนื่องจาก JSON ไม่สามารถใช้เพื่อแสดงการอ้างอิงแบบวงกลมได้รหัสต่อไปนี้:

var aParent = {Children : []}, aChild  = {Parent : aParent};
aParent.Children.push(aChild);
JSON.stringify(aParent);

ผลลัพธ์ใน: TypeError: Converting circular structure to JSON

ทางเลือกเดียวที่คุณมีคือเก็บส่วนประกอบคอมโพสิต -> ขององค์ประกอบและทิ้งองค์ประกอบ "การนำทางด้านหลัง" -> คอมโพสิตดังนั้นในตัวอย่างของคุณ:

class parent
{
    public List<child> Children{get;set;}
    public int Id{get;set;}
}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId"), ScriptIgnore]
    public parent MyParent{get;set;}
    public string name{get;set;}
}

ไม่มีอะไรป้องกันคุณจากการแนะนำคุณสมบัติการนำทางนี้ในฝั่งไคลเอ็นต์ของคุณอีกครั้งโดยใช้ jQuery:

$.each(parent.Children, function(i, child) {
  child.Parent = parent;  
})

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

$.each(parent.Children, function(i, child) {
  delete child.Parent;  
})

ขณะนี้มีปัญหาการใช้เอนทิตี EF เป็นเอนทิตีโมเดลการดูของคุณ

ครั้งแรกที่ EF มีแนวโน้มที่จะใช้ Dynamic Proxies ในชั้นเรียนของคุณเพื่อใช้พฤติกรรมเช่นการตรวจจับการเปลี่ยนแปลงหรือการโหลดที่ขี้เกียจคุณต้องปิดการใช้งานหากคุณต้องการซีเรียลเอนทิตีของ EF

ยิ่งไปกว่านั้นการใช้เอนทิตีของ EF ใน UI นั้นมีความเสี่ยงเนื่องจากตัวจัดการค่าเริ่มต้นทั้งหมดจะจับคู่ทุกฟิลด์จากคำขอไปยังฟิลด์เอนทิตีรวมถึงสิ่งที่คุณไม่ต้องการให้ผู้ใช้ตั้งค่า

ดังนั้นหากคุณต้องการให้แอป MVC ได้รับการออกแบบอย่างเหมาะสมฉันขอแนะนำให้ใช้รูปแบบมุมมองเฉพาะเพื่อป้องกัน "ความกล้า" ของรูปแบบธุรกิจภายในของคุณจากการสัมผัสกับลูกค้าดังนั้นฉันขอแนะนำรูปแบบมุมมองเฉพาะ


มีวิธีแฟนซีด้วยเทคนิคเชิงวัตถุที่ฉันสามารถแก้ไขทั้งการอ้างอิงแบบวงกลมและปัญหาของ EF
DanScan

มีวิธีแฟนซีด้วยเทคนิคเชิงวัตถุที่ฉันสามารถแก้ไขทั้งการอ้างอิงแบบวงกลมและปัญหาของ EF ได้หรือไม่? เช่นเดียวกับ BaseObject สืบทอดโดย entityObject และโดย viewObject ดังนั้น entityObject จะมีการอ้างอิงแบบวงกลม แต่ viewObject จะไม่มีการอ้างอิงแบบวงกลม ฉันได้รับสิ่งนี้โดยการสร้าง viewObject จาก entityObject (viewObject.name = entityObject.name) แต่ดูเหมือนว่าจะเสียเวลา ฉันจะแก้ไขปัญหานี้ได้อย่างไร
DanScan

พวกเขาเป็นอย่างมาก คำอธิบายของคุณชัดเจนและเข้าใจง่าย
Nick

2

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

ในการทำเช่นนี้คุณสามารถตั้งค่า DataContractSerializer และตั้งค่าคุณสมบัติDataContractSerializer.PreserveObjectReferencesเป็น 'false' ในตัวสร้างคลาสคลาสข้อมูลของคุณ สิ่งนี้ระบุว่าการอ้างอิงวัตถุไม่ควรถูกเก็บรักษาไว้ในการตอบสนอง HTTP ตามลำดับ

ตัวอย่าง:

รูปแบบ Json:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.None;

รูปแบบ XML:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ false, null);
xml.SetSerializer<Employee>(dcs);

ซึ่งหมายความว่าถ้าคุณดึงรายการที่มีวัตถุลูกอ้างอิงวัตถุลูกจะไม่ต่อเนื่องกัน

ดูเพิ่มเติมคลาสDataContractsSerializer


1

JSON serializer ที่เกี่ยวข้องกับการอ้างอิงแบบวงกลม

นี่คือตัวอย่างของแจ็คสันที่กำหนดเองJSONSerializerที่เกี่ยวข้องกับการอ้างอิงแบบวงกลมโดยจัดลำดับเหตุการณ์ที่เกิดขึ้นครั้งแรกและจัดเก็บ * referenceเป็นเหตุการณ์แรกในเหตุการณ์ที่ตามมาทั้งหมด

การจัดการกับการอ้างอิงแบบวงกลมเมื่อทำการจัดลำดับวัตถุด้วยแจ็คสัน

ตัวอย่างโค้ดบางส่วนที่เกี่ยวข้องจากบทความด้านบน:

private final Set<ObjectName> seen;

/**
 * Serialize an ObjectName with all its attributes or only its String representation if it is a circular reference.
 * @param on ObjectName to serialize
 * @param jgen JsonGenerator to build the output
 * @param provider SerializerProvider
 * @throws IOException
 * @throws JsonProcessingException
 */
@Override
public void serialize(@Nonnull final ObjectName on, @Nonnull final JsonGenerator jgen, @Nonnull final SerializerProvider provider) throws IOException, JsonProcessingException
{
    if (this.seen.contains(on))
    {
        jgen.writeString(on.toString());
    }
    else
    {
        this.seen.add(on);
        jgen.writeStartObject();
        final List<MBeanAttributeInfo> ais = this.getAttributeInfos(on);
        for (final MBeanAttributeInfo ai : ais)
        {
            final Object attribute = this.getAttribute(on, ai.getName());
            jgen.writeObjectField(ai.getName(), attribute);
        }
        jgen.writeEndObject();
    }
}

0

วิธีแก้ไขปัญหาเดียวที่ฉันมีคือสร้างโมเดล "ดู" ฉันสร้างรุ่นฐานข้อมูลที่เรียบง่ายซึ่งไม่มีการอ้างอิงไปยังคลาสพาเรนต์ มุมมองโมเดลเหล่านี้แต่ละเมธอดจะส่งคืนเวอร์ชันฐานข้อมูลและตัวสร้างที่ใช้โมเดลฐานข้อมูลเป็นพารามิเตอร์ (viewmodel.name = databasemodel.name) วิธีการนี้ดูเหมือนจะถูกบังคับแม้ว่าจะใช้งานได้

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


ต้องใช้เวลาในการประมวลผลมากขึ้นเมื่อคุณพูดถึงข้อมูลขนาดใหญ่เพราะตอนนี้คุณต้องแปลงทุกอย่างเป็นสองเท่า
David van Dugteren

-2

.Include(x => x.TableName ) ไม่ส่งคืนความสัมพันธ์ (จากตารางหลักไปยังตารางที่ขึ้นต่อกัน) หรือส่งคืนข้อมูลหนึ่งแถวเท่านั้นแก้ไขที่นี่:

/programming/43127957/include-not-working-in-net-core-returns-one-parent

นอกจากนี้ใน Startup.cs ต้องแน่ใจว่าคุณมีสิ่งนี้อยู่ด้านบน:

using Microsoft.EntityFrameworkCore; 
using Newtonsoft.Json; 
using Project_Name_Here.Models;

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