JSON.NET ตรวจพบข้อผิดพลาดในการอ้างอิงตัวเองสำหรับประเภท


494

ฉันพยายามทำให้เป็นอนุกรม POCO class ที่สร้างขึ้นโดยอัตโนมัติจาก Entity Data Model. edmx และเมื่อฉันใช้

JsonConvert.SerializeObject 

ฉันได้รับข้อผิดพลาดต่อไปนี้:

ตรวจพบข้อผิดพลาดในการอ้างอิงตัวเองสำหรับประเภท System.data.entity

ฉันจะแก้ปัญหานี้ได้อย่างไร



เมื่อคุณใช้ Linq และ MVC: stackoverflow.com/a/38241856
aDDin

เมื่อใช้. NET Core 2: stackoverflow.com/a/48709134/4496145
Dave Skender

2
ข้อผิดพลาดนี้เกิดขึ้นกับฉันเมื่อฉันต้องการทำให้ผลลัพธ์ของasyncการเรียกใช้เมธอด (a Task) เป็นลำดับและลืมนำหน้าawaitคำสั่ง
Uwe Keim

คำตอบ:


485

นั่นเป็นทางออกที่ดีที่สุด https://code.msdn.microsoft.com/Loop-Reference-handling-in-caaffaf7

แก้ไข 1: ละเว้นการอ้างอิงแบบวงกลมทั่วโลก

(ฉันได้เลือก / ลองใช้ตัวนี้เช่นเดียวกับคนอื่น ๆ )

serializer json.net มีตัวเลือกให้ละเว้นการอ้างอิงแบบวงกลม ใส่รหัสต่อไปนี้ในWebApiConfig.csไฟล์:

 config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling 
= Newtonsoft.Json.ReferenceLoopHandling.Ignore; 

การแก้ไขอย่างง่ายจะทำให้ serializer ละเว้นการอ้างอิงซึ่งจะทำให้เกิดลูป อย่างไรก็ตามมันมีข้อ จำกัด :

  • ข้อมูลสูญเสียข้อมูลอ้างอิงการวนซ้ำ
  • การแก้ไขใช้กับ JSON.net เท่านั้น
  • ไม่สามารถควบคุมระดับการอ้างอิงได้หากมีห่วงโซ่การอ้างอิงลึก

หากคุณต้องการใช้การแก้ไขนี้ในโครงการ ASP.NET ที่ไม่ใช่ api คุณสามารถเพิ่มบรรทัดด้านบนGlobal.asax.csแต่เพิ่มครั้งแรก:

var config = GlobalConfiguration.Configuration;

หากคุณต้องการใช้สิ่งนี้ในโครงการ. Net Coreคุณสามารถเปลี่ยนStartup.csเป็น:

  var mvc = services.AddMvc(options =>
        {
           ...
        })
        .AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

แก้ไข 2: รักษาการอ้างอิงแบบวงกลมทั่วโลก

การแก้ไขที่สองนี้คล้ายกับครั้งแรก เพียงเปลี่ยนรหัสเป็น:

config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling 
     = Newtonsoft.Json.ReferenceLoopHandling.Serialize;     
config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling 
     = Newtonsoft.Json.PreserveReferencesHandling.Objects;

รูปร่างข้อมูลจะเปลี่ยนแปลงหลังจากใช้การตั้งค่านี้

[
   {
      "$id":"1",
      "Category":{
         "$id":"2",
         "Products":[
            {
               "$id":"3",
               "Category":{
                  "$ref":"2"
               },
               "Id":2,
               "Name":"Yogurt"
            },
            {
               "$ref":"1"
            }
         ],
         "Id":1,
         "Name":"Diary"
      },
      "Id":1,
      "Name":"Whole Milk"
   },
   {
      "$ref":"3"
   }
]

$ id และ $ ref เก็บการอ้างอิงทั้งหมดและทำให้ระดับกราฟวัตถุคงที่ แต่รหัสลูกค้าจำเป็นต้องรู้การเปลี่ยนแปลงรูปร่างเพื่อใช้ข้อมูลและจะใช้กับ JSON.NET serializer เท่านั้น

แก้ไข 3: ละเว้นและเก็บรักษาแอตทริบิวต์การอ้างอิง

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

 public class Category 
    { 
        public int Id { get; set; } 
        public string Name { get; set; } 

        [JsonIgnore] 
        [IgnoreDataMember] 
        public virtual ICollection<Product> Products { get; set; } 
    } 

JsonIgnore ใช้สำหรับ JSON.NET และ IgnoreDataMember มีไว้สำหรับ XmlDCSerializer วิธีเก็บรักษาข้อมูลอ้างอิง:

 // Fix 3 
        [JsonObject(IsReference = true)] 
        public class Category 
        { 
            public int Id { get; set; } 
            public string Name { get; set; } 

           // Fix 3 
           //[JsonIgnore] 
           //[IgnoreDataMember] 
           public virtual ICollection<Product> Products { get; set; } 
       } 

       [DataContract(IsReference = true)] 
       public class Product 
       { 
           [Key] 
           public int Id { get; set; } 

           [DataMember] 
           public string Name { get; set; } 

           [DataMember] 
           public virtual Category Category { get; set; } 
       }

JsonObject(IsReference = true)]ใช้สำหรับ JSON.NET และ[DataContract(IsReference = true)]สำหรับ XmlDCSerializer โปรดทราบว่า: หลังจากใช้DataContractกับคลาสคุณจะต้องเพิ่มDataMemberคุณสมบัติที่คุณต้องการทำให้เป็นอันดับ

แอ็ตทริบิวต์สามารถใช้กับทั้ง json และ xml serializer และให้การควบคุมเพิ่มเติมในคลาสโมเดล


7
Fix 3 ใช้งานได้สำหรับฉัน เพียงแค่ลบแอตทริบิวต์ DataContract และ DataMember และใส่ JsonObject (IsReference = true) บน DTOs และมันใช้งานได้ ขอบคุณ
เกจิ

1
ลองสิ่งนี้ GlobalConfiguration.Configuration
Bishoy Hanna

1
Fix 3 มีข้อได้เปรียบที่ใช้งานได้บนรหัสลูกค้าที่ไม่มี GlobalConfiguration
dumbledad

1
@BishoyHanna คุณสามารถแก้ไขคำตอบเพื่อให้สามารถใช้งานได้จากแอปพลิเคชัน ASP.NET ปกติหรือไม่ คุณสามารถใช้การแก้ไขที่แนะนำของฉัน: stackoverflow.com/review/suggested-edits/17797683
NH

2
การใช้[JsonIgnore]คุณลักษณะด้านบนใช้งานได้สำหรับฉัน
นาธานเบ็ค

467

ใช้การตั้งค่า JsonSerializer

  • ReferenceLoopHandling.Error(ค่าเริ่มต้น) จะเกิดข้อผิดพลาดหากพบลูปอ้างอิง นี่คือเหตุผลที่คุณได้รับข้อยกเว้น
  • ReferenceLoopHandling.Serialize มีประโยชน์ถ้าวัตถุซ้อนกัน แต่ไม่สิ้นสุด
  • ReferenceLoopHandling.Ignore จะไม่ทำให้เป็นอันดับวัตถุถ้ามันเป็นวัตถุลูกของตัวเอง

ตัวอย่าง:

JsonConvert.SerializeObject(YourPOCOHere, Formatting.Indented, 
new JsonSerializerSettings { 
        ReferenceLoopHandling = ReferenceLoopHandling.Serialize
});

หากคุณต้องทำให้เป็นอันดับวัตถุที่ซ้อนกันไปเรื่อย ๆ คุณสามารถใช้PreserveObjectReferencesเพื่อหลีกเลี่ยงการ StackOverflowException

ตัวอย่าง:

JsonConvert.SerializeObject(YourPOCOHere, Formatting.Indented, 
new JsonSerializerSettings { 
        PreserveReferencesHandling = PreserveReferencesHandling.Objects
});

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

การอ้างอิงhttp://james.newtonking.com/json/help/


66
ฉันพบข้อผิดพลาดเมื่อทำการจัดลำดับข้อมูลได้ ฉันใช้ReferenceLoopHandling = ReferenceLoopHandling.Ignoreเพื่อให้ทำงาน

8
หากมีลูปอ้างอิงในข้อมูลการใช้ReferenceLoopHandling.Serializeจะทำให้ serializer เข้าสู่วนรอบวนซ้ำไม่สิ้นสุดและล้นสแต็ก
Brian Rogers

1
แก้ไข. เนื่องจากคำถามเกี่ยวกับรูปแบบของ EF ก็เป็นสิ่งที่น่ากังวลเช่นกัน แก้ไขเพื่อให้ตัวเลือกที่มีทั้งหมด
DalSoft

1
ฉันได้พบข้อผิดพลาดเดียวกันนี้เมื่อพยายามที่จะเป็นอันดับวัตถุ ... แต่วัตถุที่ไม่ได้มีการอ้างอิงอื่น ๆ กว่าชนิด enum ใด ๆ ..
Marin

1
สำหรับฉัน EF เป็นสาเหตุหลักของปัญหานี้เพราะเอนทิตีที่อ้างอิงตัวเองอยู่ทั่วทุกที่
Teoman shipahi

57

การแก้ไขคือการละเว้นการอ้างอิงแบบวนซ้ำและไม่ให้เป็นแบบอนุกรม พฤติกรรมนี้มีการระบุไว้ในJsonSerializerSettingsลักษณะการทำงานนี้ระบุไว้ใน

ซิงเกิ้ลที่JsonConvertมีการโอเวอร์โหลด:

JsonConvert.SerializeObject(YourObject, Formatting.Indented,
    new JsonSerializerSettings() {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
    }
);

การตั้งค่าส่วนกลางด้วยรหัสApplication_Start()ใน Global.asax.cs:

JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
     Formatting = Newtonsoft.Json.Formatting.Indented,
     ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
};

การอ้างอิง: https://github.com/JamesNK/Newtonsoft.Json/issues/78


ทำไมคุณตั้งค่ารูปแบบการเยื้องเมื่อคุณทำการตั้งค่าส่วนกลาง
Murphybro2

สิ่งที่เราต้องการอย่างยิ่งในการแก้ปัญหานี้ (ค้นพบระหว่างการปรับใช้)! คุณ da man .... ขอบคุณที่ช่วยเราประหยัดเวลา !!
Ryan Eastabrook

ฉันแก้ไข issus ของฉันโดยเพิ่ม "JsonConvert.DefaultSettings" = () => JsonSerializerSettings {.... } ใหม่ในคลาส "Startup.cs"
Beldi Anouar

45

วิธีที่ง่ายที่สุดในการทำเช่นนี้คือการติดตั้งJson.NET จาก nuget และเพิ่ม[JsonIgnore]คุณสมบัติให้กับคุณสมบัติเสมือนในคลาสตัวอย่างเช่น:

    public string Name { get; set; }
    public string Description { get; set; }
    public Nullable<int> Project_ID { get; set; }

    [JsonIgnore]
    public virtual Project Project { get; set; }

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


3
คำตอบที่ดีที่สุดในการใช้ Newton JSON
Aizen

21

ใน. NET Core 1.0 คุณสามารถตั้งค่านี้เป็นการตั้งค่าส่วนกลางในไฟล์ Startup.cs ของคุณ:

using System.Buffers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Newtonsoft.Json;

// beginning of Startup class

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
        {
            options.OutputFormatters.Clear();
            options.OutputFormatters.Add(new JsonOutputFormatter(new JsonSerializerSettings(){
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            }, ArrayPool<char>.Shared));
        });
    }

แต่ในกรณีนี้ถ้าฉันต้องการทราบว่าคุณสมบัตินี้ถูกข้ามไปฉันจะไม่ได้รับข้อยกเว้นใด ๆ
Mayer Spitzer

10

หากคุณใช้. NET Core 2.x ให้อัปเดตส่วน ConfigureServices ใน Startup.cs

https://docs.microsoft.com/en-us/ef/core/querying/related-data#related-data-and-serialization

    public void ConfigureServices(IServiceCollection services)
    {
    ...

    services.AddMvc()
        .AddJsonOptions(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );

    ...
    }

หากคุณใช้. NET Core 3.x โดยไม่มี MVC มันจะเป็น:

services.AddControllers()
  .AddNewtonsoftJson(options =>
      options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
   );

การจัดการลูปอ้างอิงนี้เกือบจะบังคับถ้าคุณใช้ Entity Framework และรูปแบบการออกแบบครั้งแรกของฐานข้อมูล


2
ถ้าฉันไม่ใช้services.AddMvc()ล่ะ
ละคร

2
นี่เป็นการปฏิบัติที่ไม่ดีหรือไม่?
Renan Coelho

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

นอกจากนี้หากคุณใช้รูปแบบการออกแบบฐานข้อมูลเป็นครั้งแรกกับ Entity Framework ขึ้นอยู่กับว่าคุณตั้งค่าคีย์ต่างประเทศของคุณในฐานข้อมูลของคุณมันจะสร้างการอ้างอิงตามวัฏจักรโดยอัตโนมัติดังนั้นคุณต้องใช้การตั้งค่านี้ถ้าคุณ วิศวกรรมย้อนกลับชั้นเรียนของคุณ
Dave Skender

9

ในการทำให้เป็นอันดับเราใน NEWTONSOFTJSON เมื่อคุณมีปัญหาลูปในกรณีของฉันฉันไม่จำเป็นต้องแก้ไข global.asax หรือ apiconfig ฉันใช้ JsonSerializesSettings โดยไม่สนใจการจัดการแบบวนซ้ำ

JsonSerializerSettings jss = new JsonSerializerSettings();
jss.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
var lst = db.shCards.Where(m => m.CardID == id).ToList();
string json = JsonConvert.SerializeObject(lst, jss);

1
ถ้าใครมาที่นี่เพื่อซับหนึ่งไปในหน้าต่างดูดังนั้นจึงเป็นข้อความที่ค้นหาได้:Newtonsoft.Json.JsonConvert.SerializeObject(objToSerialize, new Newtonsoft.Json.JsonSerializerSettings() {ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore});
เกรแฮม

8

เราสามารถเพิ่มสองบรรทัดเหล่านี้ลงในตัวสร้างคลาส DbContext เพื่อปิดใช้งานลูปการอ้างอิงตนเองเช่น

public TestContext()
        : base("name=TestContext")
{
    this.Configuration.LazyLoadingEnabled = false;
    this.Configuration.ProxyCreationEnabled = false;
}

นี้เป็นหนึ่งในหนึ่งง่ายและการทำงานเช่นเสน่ห์ โหวตแล้วขอบคุณมาก ...
Murat Yıldız

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

@ElMac คุณพูดถูก แต่ถ้าเราไม่ต้องการฟีเจอร์นั้นดังนั้นทำไมใช้โซลูชันนี้ไม่ได้?
Sanjay Nishad

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

6

คุณสามารถใช้คุณสมบัตินี้กับอสังหาริมทรัพย์ได้เช่นกัน [JsonProperty( ReferenceLoopHandling = ... )]แอตทริบิวต์เหมาะดีนี้

ตัวอย่างเช่น:

/// <summary>
/// Represents the exception information of an event
/// </summary>
public class ExceptionInfo
{
    // ...code omitted for brevity...

    /// <summary>
    /// An inner (nested) error.
    /// </summary>
    [JsonProperty( ReferenceLoopHandling = ReferenceLoopHandling.Ignore, IsReference = true )]
    public ExceptionInfo Inner { get; set; }

    // ...code omitted for brevity...    
}

หวังว่าจะช่วยได้ Jaans


4

ในการเพิกเฉยการอ้างอิงแบบวนซ้ำและไม่ทำให้เป็นอนุกรมทั่วโลกใน MVC 6 ให้ใช้สิ่งต่อไปนี้ใน startup.cs:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().Configure<MvcOptions>(options =>
        {
            options.OutputFormatters.RemoveTypesOf<JsonOutputFormatter>();
            var jsonOutputFormatter = new JsonOutputFormatter();
            jsonOutputFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            options.OutputFormatters.Insert(0, jsonOutputFormatter);
        });
    }

2

ใช้สิ่งนี้ในWebApiConfig.csชั้นเรียน:

var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);

2

สำหรับฉันฉันต้องไปเส้นทางอื่น แทนที่จะพยายามแก้ไข serializer ของ JSON.Net ฉันต้องดำเนินการตาม Lazy Loading บนดาต้าคอนเท็กซ์ของฉัน

ฉันเพิ่งเพิ่มสิ่งนี้ไปยังที่เก็บฐานของฉัน:

context.Configuration.ProxyCreationEnabled = false;

วัตถุ "บริบท" เป็นพารามิเตอร์ตัวสร้างที่ฉันใช้ในที่เก็บฐานของฉันเพราะฉันใช้การฉีดพึ่งพา คุณสามารถเปลี่ยนคุณสมบัติ ProxyCreationEnabled ได้ทุกที่ที่คุณสร้างอินสแตนซ์ของเอกสารแทน

http://techie-tid-bits.blogspot.com/2015/09/jsonnet-serializer-and-error-self.html


2

ฉันมีข้อยกเว้นนี้และวิธีแก้ปัญหาในการทำงานของฉันนั้นง่ายและง่าย

ละเว้นคุณสมบัติที่อ้างอิงโดยการเพิ่มคุณสมบัติ JsonIgnore เข้ากับมัน:

[JsonIgnore]
public MyClass currentClass { get; set; }

ตั้งค่าคุณสมบัติใหม่เมื่อคุณทำการยกเลิกการซีเรียลไลซ์:

Source = JsonConvert.DeserializeObject<MyObject>(JsonTxt);
foreach (var item in Source)
        {
            Source.MyClass = item;
        }

ใช้ Newtonsoft.Json;


นี่คือเวทย์มนตร์ที่ฉันต้องการ แก้ไขมัน[JsonIgnore]
saviour123

2

ทีม:

ใช้งานได้กับ ASP.NET Core ความท้าทายในด้านบนคือวิธีที่คุณตั้งค่าให้เพิกเฉย มันค่อนข้างท้าทาย นี่คือสิ่งที่ใช้ได้ผลสำหรับฉัน

สิ่งนี้สามารถอยู่ในโมฆะสาธารณะของคุณ ConfigureServices (IServiceCollection services) ส่วน

services.AddMvc().AddJsonOptions(opt => 
        { 
      opt.SerializerSettings.ReferenceLoopHandling =
      Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        });

2

ผู้คนพูดคุยกันแล้วเกี่ยวกับ [JsonIgnore] ที่ถูกเพิ่มเข้ากับคุณสมบัติเสมือนจริงในคลาสตัวอย่างเช่น:

[JsonIgnore]
public virtual Project Project { get; set; }

ฉันจะแชร์ตัวเลือกอื่น [JsonProperty (NullValueHandling = NullValueHandling.Ignore)] ซึ่งละเว้นคุณสมบัติจากการทำให้เป็นอนุกรมเฉพาะเมื่อมันเป็นโมฆะ:

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public virtual Project Project { get; set; }

1

สำหรับ. NET Core 3.0 ให้อัพเดตคลาส Startup.cs ดังที่แสดงด้านล่าง

public void ConfigureServices(IServiceCollection services)
{
...

services.AddControllers()
    .AddNewtonsoftJson(
        options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
    );

...
}

ดู: https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-core-3-0-preview-5/


0

เพียงวางConfiguration.ProxyCreationEnabled = false;ในไฟล์บริบท สิ่งนี้จะช่วยแก้ปัญหา

public demEntities()
    : base("name=demEntities")
{
    Configuration.ProxyCreationEnabled = false;
}

0

ปัญหาของฉันแก้ไขได้ด้วย Custom JsonSerializerSettings:

services.AddMvc(
  // ...
               ).AddJsonOptions(opt =>
                 {
                opt.SerializerSettings.ReferenceLoopHandling =
                    Newtonsoft.Json.ReferenceLoopHandling.Serialize;
                opt.SerializerSettings.PreserveReferencesHandling =
                    Newtonsoft.Json.PreserveReferencesHandling.Objects;
                 });

0

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


0

ฉันกำลังเผชิญปัญหาเดียวกันและฉันพยายามใช้ JsonSetting เพื่อละเว้นข้อผิดพลาดในการอ้างอิงตัวเองมันทำงานได้จนกว่าฉันจะได้เรียนซึ่งการอ้างอิงตัวเองอย่างลึกซึ้งมากและกระบวนการ dot-net ของฉันค้างอยู่กับค่าการเขียน Json

ปัญหาของฉัน

    public partial class Company : BaseModel
{
    public Company()
    {
        CompanyUsers = new HashSet<CompanyUser>();
    }

    public string Name { get; set; }

    public virtual ICollection<CompanyUser> CompanyUsers { get; set; }
}

public partial class CompanyUser
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    public int UserId { get; set; }

    public virtual Company Company { get; set; }

    public virtual User User { get; set; }
}

public partial class User : BaseModel
{
    public User()
    {
        CompanyUsers = new HashSet<CompanyUser>();
    }

    public string DisplayName { get; set; }
    public virtual ICollection<CompanyUser> CompanyUsers { get; set; }

}

คุณสามารถดูปัญหาในระดับผู้ใช้ที่อ้างถึงCompanyUserคลาสซึ่งเป็นการอ้างอิงตนเอง

ตอนนี้ฉันกำลังเรียกวิธี GetAll ซึ่งรวมถึงคุณสมบัติเชิงสัมพันธ์ทั้งหมด

cs.GetAll("CompanyUsers", "CompanyUsers.User");

ในขั้นตอนนี้กระบวนการ DotNetCore ของฉันค้างอยู่ที่การดำเนินการ JsonResult การเขียนมูลค่า ...และไม่เคยมา ใน Startup.cs ของฉันฉันได้ตั้งค่า JsonOption แล้ว ด้วยเหตุผลบางอย่าง EFCore รวมถึงทรัพย์สินที่ซ้อนอยู่ซึ่งฉันไม่ได้ขอให้ Ef ให้

    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

พฤติกรรมที่คาดหวังควรเป็นเช่นนี้

สวัสดี EfCore คุณช่วยกรุณารวมข้อมูล "CompanyUsers" เช่นเดียวกับในชั้น บริษัท ของฉันเพื่อให้ฉันสามารถเข้าถึงข้อมูลได้อย่างง่ายดาย

แล้วก็

สวัสดี EfCore คุณสามารถรวมข้อมูล"CompanyUsers.User"ไว้ด้วยเพื่อที่ฉันจะสามารถเข้าถึงข้อมูลเช่นCompany.CompanyUsers.First ()นี้ได้อย่างง่ายดาย User.DisplayName

ในขั้นตอนนี้ฉันควรได้รับ"Company.CompanyUsers.First () นี้ User.DisplayName" เท่านั้นและไม่ควรให้Company.CompanyUsers.First ()แก่ฉันUser.CompanyUsersซึ่งเป็นสาเหตุของปัญหาการอ้างอิงตนเอง ในทางเทคนิคแล้วมันไม่ควรให้User.CompanyUsersกับฉันเพราะCompanyUsersเป็นคุณสมบัติการนำทาง แต่ EfCore รับตื่นเต้นมากและให้ฉันUser.CompanyUsers

ดังนั้นฉันตัดสินใจที่จะเขียนวิธีการขยายสำหรับคุณสมบัติที่จะถูกแยกออกจากวัตถุ ไม่เพียงแค่ว่ามันจะทำงานในคุณสมบัติอาร์เรย์เช่นกัน ด้านล่างเป็นรหัสที่ฉันจะส่งออกแพ็คเกจ nuget สำหรับผู้ใช้รายอื่น (ไม่แน่ใจว่าสิ่งนี้จะช่วยคนอื่นได้หรือไม่) เหตุผลง่ายเพราะฉันขี้เกียจเขียนเลือก (n => ใหม่ {n.p1, n.p2});ฉันไม่ต้องการเขียนคำสั่ง select เพื่อยกเว้นคุณสมบัติ 1 รายการเท่านั้น!

นี่ไม่ใช่รหัสที่ดีที่สุด (ฉันจะอัปเดตในบางช่วง) เนื่องจากฉันเขียนอย่างเร่งรีบและแม้ว่านี่อาจช่วยคนที่ต้องการยกเว้น (ตั้งค่าว่าง) ในวัตถุที่มีอาร์เรย์ด้วย

    public static class PropertyExtensions
{
    public static void Exclude<T>(this T obj, Expression<Func<T, object>> expression)
    {
        var visitor = new PropertyVisitor<T>();
        visitor.Visit(expression.Body);
        visitor.Path.Reverse();
        List<MemberInfo> paths = visitor.Path;
        Action<List<MemberInfo>, object> act = null;

        int recursiveLevel = 0;
        act = (List<MemberInfo> vPath, object vObj) =>
        {

            // set last propert to null thats what we want to avoid the self-referencing error.
            if (recursiveLevel == vPath.Count - 1)
            {
                if (vObj == null) throw new ArgumentNullException("Object cannot be null");

                vObj.GetType().GetMethod($"set_{vPath.ElementAt(recursiveLevel).Name}").Invoke(vObj, new object[] { null });
                return;
            }

            var pi = vObj.GetType().GetProperty(vPath.ElementAt(recursiveLevel).Name);
            if (pi == null) return;
            var pv = pi.GetValue(vObj, null);
            if (pi.PropertyType.IsArray || pi.PropertyType.Name.Contains("HashSet`1") || pi.PropertyType.Name.Contains("ICollection`1"))
            {
                var ele = (IEnumerator)pv.GetType().GetMethod("GetEnumerator").Invoke(pv, null);

                while (ele.MoveNext())
                {
                    recursiveLevel++;
                    var arrItem = ele.Current;

                    act(vPath, arrItem);

                    recursiveLevel--;
                }

                if (recursiveLevel != 0) recursiveLevel--;
                return;
            }
            else
            {
                recursiveLevel++;
                act(vPath, pv);
            }

            if (recursiveLevel != 0) recursiveLevel--;

        };

        // check if the root level propert is array
        if (obj.GetType().IsArray)
        {
            var ele = (IEnumerator)obj.GetType().GetMethod("GetEnumerator").Invoke(obj, null);
            while (ele.MoveNext())
            {
                recursiveLevel = 0;
                var arrItem = ele.Current;

                act(paths, arrItem);
            }
        }
        else
        {
            recursiveLevel = 0;
            act(paths, obj);
        }

    }

    public static T Explode<T>(this T[] obj)
    {
        return obj.FirstOrDefault();
    }

    public static T Explode<T>(this ICollection<T> obj)
    {
        return obj.FirstOrDefault();
    }
}

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

ตัวสร้างนิพจน์

    internal class PropertyVisitor<T> : ExpressionVisitor
{
    public readonly List<MemberInfo> Path = new List<MemberInfo>();

    public Expression Modify(Expression expression)
    {
        return Visit(expression);
    }


    protected override Expression VisitMember(MemberExpression node)
    {
        if (!(node.Member is PropertyInfo))
        {
            throw new ArgumentException("The path can only contain properties", nameof(node));
        }

        Path.Add(node.Member);
        return  base.VisitMember(node);
    }
}

ประเพณี:

คลาสของโมเดล

    public class Person
{
    public string Name { get; set; }
    public Address AddressDetail { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public Country CountryDetail { get; set; }
    public Country[] CountryDetail2 { get; set; }
}

public class Country
{
    public string CountryName { get; set; }
    public Person[] CountryDetail { get; set; }
}

ข้อมูลจำลอง

           var p = new Person
        {
            Name = "Adeel Rizvi",
            AddressDetail = new Address
            {
                Street = "Sydney",
                CountryDetail = new Country
                {
                    CountryName = "AU"
                }
            }
        };

        var p1 = new Person
        {
            Name = "Adeel Rizvi",
            AddressDetail = new Address
            {
                Street = "Sydney",
                CountryDetail2 = new Country[]
                {
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A1" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A2" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A3" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A4" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A5" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A6" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A7" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A8" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A9" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A1" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A2" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A3" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A4" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A5" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A6" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A7" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A8" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A9" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },

                }
            }
        };

กรณี:

กรณีที่ 1: ยกเว้นคุณสมบัติเท่านั้นโดยไม่มีอาร์เรย์ใด ๆ

p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);

กรณีที่ 2: ยกเว้นคุณสมบัติที่มี 1 อาร์เรย์

p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);

กรณีที่ 3: ยกเว้นคุณสมบัติที่มี 2 อาร์เรย์ซ้อนกัน

p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);

กรณีที่ 4: คำถามเกี่ยวกับการรับ GetAll ของ EF

var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;

คุณสังเกตเห็นว่าExplode ()วิธีการมันยังเป็นวิธีการขยายเพียงสำหรับการสร้างการแสดงออกของเราที่จะได้รับทรัพย์สินจากคุณสมบัติอาร์เรย์ เมื่อใดก็ตามที่มีการใช้งานคุณสมบัติอาร์เรย์.Explode (). YourPropertyToExclude หรือ .Explode โค้ดด้านบนช่วยให้ฉันสามารถหลีกเลี่ยงการอ้างอิงตนเองได้อย่างลึกล้ำฉันต้องการ ตอนนี้ฉันสามารถใช้ GetAll และยกเว้นคุณสมบัติที่ฉันไม่ต้องการได้!

ขอบคุณสำหรับการอ่านโพสต์ใหญ่นี้!


-1

สำหรับการไม่วนซ้ำสิ่งนี้ได้ผลกับฉัน -
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,

ฉันแก้ไขมันได้แล้วที่นี่ - การจัดลำดับเด็ก Entity Framework ด้วย. Net Core 2 WebAPI https://gist.github.com/Kaidanov/f9ad0d79238494432f32b8407942c606

จะขอบคุณข้อสังเกตใด ๆ บางทีบางคนสามารถใช้งานได้ในบางครั้ง


-1

รหัส C #:

            var jsonSerializerSettings = new JsonSerializerSettings
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
            };

            var jsonString = JsonConvert.SerializeObject(object2Serialize, jsonSerializerSettings);

            var filePath = @"E:\json.json";

            File.WriteAllText(filePath, jsonString);

นี่เป็นแนวทางเดียวกับที่เสนอในคำตอบที่ได้รับการจัดอันดับอย่างสูงของ @ DalSoft เมื่อแปดปีก่อน แต่มีคำอธิบายน้อยกว่ามาก
Jeremy Caney

หวังว่ามันจะแก้ปัญหาได้ แต่โปรดเพิ่มคำอธิบายของรหัสของคุณด้วยเพื่อที่ผู้ใช้จะได้รับความเข้าใจที่สมบูรณ์แบบซึ่งเขา / เธอต้องการจริงๆ
Jaimil Patel

-2

ฉันชอบวิธีแก้ปัญหาที่ทำได้จากApplication_Start()คำตอบที่นี่

เห็นได้ชัดว่าฉันไม่สามารถเข้าถึงวัตถุ json ใน JavaScript โดยใช้การกำหนดค่าภายในฟังก์ชั่นของฉันเช่นเดียวกับคำตอบของ DalSoft เนื่องจากวัตถุที่ส่งคืนมี "\ n \ r" อยู่ทั่ว (คีย์ val) ของวัตถุ

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

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