ASP.NET。Newtonsoft.Json.JsonSerializationException:'检测到类型'E_Store2021.Models.Product'的自引用循环

Evg*_*y20 4 c# asp.net asp.net-mvc asp.net-core

我试图在 Include(p => p.SubCategory) 之后获取 SubCategory 并收到错误:

“Newtonsoft.Json.JsonSerializationException:‘检测到类型‘E_Store2021.Models.Product’的自引用循环。路径‘[0].Product.SubCategory.Products’”

我需要获取 SubCategoryName。这种情况我该怎么办?没有 Include() 一切正常。公司也会发生同样的情况。

标记:

<tr>
    <td>
        <figure class="itemside align-items-center">
            <div class="aside"><img src="@("~/images/content/" + item.Product.ImagePath)" asp-append-version="true" class="img-sm"/></div>
            <figcaption class="info">
                <a href="#" class="title text-dark" data-abc="true">@item.Product.ProductName</a>
                <p class="text-muted small">Category: <br> Brand: @item?.Product?.Company.CompanyName</p>
                <p class="text-muted small">SubCategory: @item.Product?.SubCategory?.SubCategoryName</p>
            </figcaption>
        </figure>
    </td>
    <td>
        <select class="form-control">
            <option>1</option>
            <option>2</option>
            <option>3</option>
            <option>4</option>
        </select>
    </td>
    <td>
        <div class="price-wrap"> <var class="price">@item.Product.UnitPrice</var> <small class="text-muted"> $9.20 each </small> </div>
    </td>
    <td class="text-right d-none d-md-block"> <a data-original-title="Save to Wishlist" title="" href="" class="btn btn-light" data-toggle="tooltip" data-abc="true"> <i class="fa fa-heart"></i></a> <a href="" class="btn btn-light" data-abc="true"> Remove</a> </td>
</tr>
Run Code Online (Sandbox Code Playgroud)

代码:

namespace E_Store2021.Controllers
{
    public class CartController : Controller
    {
        private ApplicationDbContext _context;

        public CartController(ApplicationDbContext context)
        {
            _context = context;
        }

        [AllowAnonymous]
        public IActionResult Index()
        {
            var cart = SessionHelper.GetObjectFromJson<List<ShoppingCartItem>>(HttpContext.Session, "cart");
            ShoppingCartModel.ShoppingCartItems = cart;
            ShoppingCartModel.Total = cart?.Sum(item => item.Product.UnitPrice * item.Quantity);
            return View();
        }

        [AllowAnonymous]
        public IActionResult Buy(int id)
        {
            if (SessionHelper.GetObjectFromJson<List<ShoppingCartItem>>(HttpContext.Session, "cart") == null)
            {
                List<ShoppingCartItem> cart = new List<ShoppingCartItem>();
                cart.Add(new ShoppingCartItem { Product = _context.Products.Include(p=>p.SubCategory).FirstOrDefault(p => p.ProductID == id), Quantity = 1 });
                SessionHelper.SetObjectAsJson(HttpContext.Session, "cart", cart);
            }
            else
            {
                List<ShoppingCartItem> cart = SessionHelper.GetObjectFromJson<List<ShoppingCartItem>>(HttpContext.Session, "cart");
                int index = IsExist(id);
                if (index != -1)
                    cart[index].Quantity++;
                else
                    cart.Add(new ShoppingCartItem { Product = _context.Products.Include(p => p.SubCategory).FirstOrDefault(p => p.ProductID == id), Quantity = 1 });
                SessionHelper.SetObjectAsJson(HttpContext.Session, "cart", cart);
            }
            return RedirectToAction("Index");
        }

        [AllowAnonymous]
        public IActionResult Remove(int id)
        {
            List<ShoppingCartItem> cart = SessionHelper.GetObjectFromJson<List<ShoppingCartItem>>(HttpContext.Session, "cart");
            int index = IsExist(id);

            cart.RemoveAt(index);

            SessionHelper.SetObjectAsJson(HttpContext.Session, "cart", cart);

            return RedirectToAction("Index");
        }

        [AllowAnonymous]
        private int IsExist(int id)
        {
            List<ShoppingCartItem> cart = SessionHelper.GetObjectFromJson<List<ShoppingCartItem>>(HttpContext.Session, "cart");
            for (int i = 0; i < cart.Count; i++)
            {
                if (cart[i].Product.ProductID.Equals(id))
                    return i;
            }
            return -1;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

会话助手。尝试序列化对象时,此处会发生错误。

public static class SessionHelper
{
    public static void SetObjectAsJson(this ISession session, string key, object value)
    {
        session.SetString(key, JsonConvert.SerializeObject(value));
    }

    public static T GetObjectFromJson<T>(this ISession session, string key)
    {
        var value = session.GetString(key);

        return value == null ? default : JsonConvert.DeserializeObject<T>(value);
    }
}
Run Code Online (Sandbox Code Playgroud)

MrV*_*ype 9

不确定您是否已经解决了这个问题,因为昨天有人问过这个问题,或者为什么没有反应。

当对象结构中存在循环引用时,这是一个经常遇到的问题。最佳实践是首先在 DTO/视图模型中避免这些。

但是,如果您可以从序列化中排除这些循环引用,那么您很幸运,因为您使用的是 Newtonsoft JSON,它支持通过选项配置此行为JsonSerializerSettings.ReferenceLoopHandling

我认为这在这里应该足够了,因为自引用似乎发生在Products.SubCategory和之间SubCategory.Products,而您想要的是SubCategory.Name,它仍然应该被填充。

如果使用此设置不可行,您可以只定义一个 ProductDto 或 ProductViewModel,它们甚至可能会展平层次结构,并且除了其他属性之外还包含一个属性,SubCategoryName而不是包含整个SubCategory实体(或者无论如何都适合您)。

有三种方法可以使用上述选项。

1) 当您在控制器操作中返回对象结果时,将其设为 ASP.NET Core 序列化中的默认设置:

services.AddMvc()
    .AddNewtonsoftJson(options => {
        options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
    });
Run Code Online (Sandbox Code Playgroud)

AddNewtonsoftJson()请记住,如果您使用的是 ASP.NET Core 3+,并且事先配置中没有现有调用,则执行此操作会将 Microsoft 的新System.Text.Json序列化器替换为 Newtonsoft 的序列化器(速度较慢)。

2) 将其设为 Newtonsoft 序列化器的默认全局设置:

JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};

// Then all subsequent manual serialization will be done with this setting.
string json = JsonConvert.SerializeObject (object);
Run Code Online (Sandbox Code Playgroud)

请注意,以这种方式配置它不会更改 ASP.NET Core 控制器操作的行为,因为该序列化似乎使用单独的设置实例。

3)JsonSerializerSettings当您手动序列化时,每次显式传递一个实例:

JsonSerializerSettings settings = new JsonSerializerSettings
{
     ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
};
    
string json = JsonConvert.SerializeObject (object, settings);
Run Code Online (Sandbox Code Playgroud)

因此,对于您的情况,解决方案 2) 或 3) 就是您所需要的。感谢您的编辑;在我原来的回答中,我没有考虑到您正在手动调用SerializeObject().