حل مشکل JsonException: A possible object cycle was detected در ASP.Net Core

فریم ورک ASP.NET که از قدیم الایّام در اختیار برنامه نویسان و توسعه دهندگان وجود داشته، ویژگی ها و امکانات کم نظیری دارد. این قابلیت ها موجب می شود تا دردسر ها و مشکلات برنامه نویسی به حداقل برسد و بتوانیم با صرف وقت و انرژی کمتر به اهداف خود دستیابی پیدا کنیم. در مورد زبان مورد استفاده در این فریم ورک نیز – که C# نام دارد – هر چه بگویم حق مطلب را ادا نمی کند. معقولانه بودن syntax و قابل فهم بودن کد ها و آسانی نسبی یادگیری در عین حرفه ای بودن آن تنها چند ویژگی آن است. کافیست syntax جاوا اسکریپت را با آن مقایسه کنید. اما از همه ی این ها که بگذریم، به کارگیری ناصحیح همین ابزار های خوب و کاربردی توسط کاربران نا آشنا می تواند مشکلاتی را به همراه داشته باشد. مشکلی که امروز می خواهیم آن را طرح و برطرف کنیم چگونگی حل مشکل JsonException: A possible object cycle was detected در ASP.Net Core است.

حل مشکل JsonException: A possible object cycle was detected در ASP.Net Core

طرح مشکل

علّت و مقصر برخوردن به چنین مشکلی تنها و تنها خود برنامه نویس است. چیزی که خیلی از وبلاگ ها و وب سایت های دنیا نیز هنوز به آن پی نبرده اند و مایکروسافت را مقصر این مشکل می دانند. آن ها برخوردن به این مشکل را به وجود یک باگ در System.Text.Json نسبت می دهند در صورتی که چنین نیست. حالا بیایید با هم بررسی کنیم چرا چنین مشکلی پیش می آید و علّت واقعی آن چیست. بسیاری از برنامه نویسان از روش Database First استفاده می کنند و ابتدا پایگاه داده ی خود را ساخته و سپس با دستور Scaffold-dbcontext اقدام به ساخت کلاس های مربوط به پایگاه داده و جداول خود می کنند.

این روش به خودی خود هیچ اشکالی به آن وارد نیست و خود من هم در اکثر موارد به دلیل آسان بودن فرایند کار از همین روش استفاده می کنم. امّا مشکل از جایی شروع می شود که در جداول پایگاه داده ی خود به کمک Foreign Key آن ها را به یکدیگر مرتبط کرده باشید. اگر از SQL Server یا MySql استفاده می کنید که تقریباً مطمئنم همینطور هست حتماً می دانید که اصلی ترین مزیت اینگونه پایگاه های داده Relational بودن آن ها و توانایی ربط دادن جداول به یکدیگر است تا از تکرار داده ها در جدول های مختلف جلوگیری شود. اما به محض انجام این کار و برگرداندن داده ها به فرم کلاس های ساخته شده به صورت خودکار، با خطای JsonException: A possible object cycle was detected مواجه می شویم. برای درک بهتر این مشکل به کد های زیر که توسط دستور Scaffold-Dbcontext ساخته شده خوب دقّت کنید.

فرض کنید یک اداره داریم که دارای بخش ها یا دپارتمان های مختلف است. بدیهی است که در این بخش های اداره افرادی کار می کنند. دو کلاس زیر دپارتمان ها و افراد را نمایندگی می کند.


public class Department {
public int DepartmentId {get;set;}
public string DepartmentName {get;set;}
public List <Staff> Staffs {get;set;}
}


public class Staff{
public int StaffId {get;set;}
public int DepartmentId {get;set;}
public List <Department> Departments {get;set;}
}
public async Task<ActionResult<IEnumerable<Staff>>> GetStaff()
{
return await _dbContext.Staff.Include(m => m.Department).ToListAsync();
}

همانطور که در بالا می بینیم در کلاس هر دپارتمان کارمندان وجود دارد. حال سیستم می خواهد این داده ها را به JSON تبدیل کند. بنابرین کلاس دپارتمان ها را دنبال می کند و مشاهده می شود که به کلاس کارمندان یا Staff رجوع داده می شود. سیستم بار دیگر متوجّه می شود کلاس کارمندان به کلاس دپارتمان ها رجوع داده می شود. بنابرین در یک حلقه ی بی نهایت گیر می افتد. به بیان دیگر کارمندان دپارتمان دارند و دپارتمان ها هم کارمند دارند. این حلقه هیچ گاه به پایان نمی رسد پس با خطای JsonException: A possible object cycle was detected مواجه می شویم.

راه حل

حل مشکل JsonException: A possible object cycle was detected در ASP.Net Core بسیار ساده است. راه کار اصولی جلوگیری از این مشکل و مشکلاتی شبیه به این در آینده استفاده از Data Transfer Object ها یا DTO هاست. به این شکل که ابتدا در فایل کلاسی جدا یا در همان کنترلر، کلاس دلخواهی می سازیم. این کلاس چهارچوب آن شیء که قصد ارسالش را داریم برایمان نمایندگی می کند.

public class StaffDTO
{
public int StaffId {get;set;}
public string StaffName {get;set;}
public int DepartmentName {get;set;}
}

حال در کنترلرمان به جای بازگردانی مدل ساخته شده به وسیله ی سیستم، StaffDTO که خودمان ساختیم را باز می گردانیم. در این حالت در حلقه ی ارجاع بی نهایت گرفتار نخواهیم شد.

public async Task<ActionResult<IEnumerable>> GetStaffList()
{
System.Threading.Thread.Sleep(4000);
return await _context.Staff.Include(x => x.Department).Select(b =>
new StaffDTO()
{
StaffId = b.Id,
StaffName = b.StaffName,
DepartmentName = b.DepartmentName
}).ToListAsync();
}

حال با این کار می توانیم بدون خطا به لیستی از افراد شاغل در اداره با جزئیات آی دی، نام و نام دپارتمانشان دسترسی داشته باشیم. مزیت دیگر DTO ها این است که می توانیم داده های دلخواه خود را بازگردانی کنیم. برای مثال، آی دی را حذف کرده و تنها نام و نام دپارتمان هر یک از پرسنل را بازگردانی کنیم. با این کار از بار پایگاه داده نیز می کاهیم. برای دریافت اطلاعات بیشتر در مورد DTO ها می توانید این این صفحه را مطالعه کنید.

درباره ی حمیدرضا

حمیدرضا هستم فارغ التحصیل رشته الکترونیک. رشته ام و کامپیوترها و موبایل و اینترنت و گیمینگ گوشه ای از علائق من هستن. زنده باد مایکروسافت!

این پست هم توصیه می شود:

How to use Angular renderer2 to toggle a class or style

I needed to toggle a simple style for a <div> and it’s children, so I …

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد.