json 在ASP.NET Web API中处理带有Locale的实体的无限序列化

new9mtju  于 2023-08-08  发布在  .NET
关注(0)|答案(1)|浏览(78)

我目前正在使用API,遇到了一个与实体序列化相关的问题。我有一个Car类,它引用了其他类,其中一些类包含它们自己的区域设置,例如Color和Transmission。
下面是我的Car类的结构:

public class Car : IEntity
{
    public int Id { get; set; }
    public int? OwnerId { get; set; }
    public int BrandId { get; set; }
    public int ModelId { get; set; }
    public int ReleaseYearId { get; set; }
    public int ColorId { get; set; }
    public int MarketId { get; set; }
    public int? RegionId { get; set; }
    public int GearTypeId { get; set; }
    public int CategoryId { get; set; }
    public int FueltypeId { get; set; }
    public int CurrencyId { get; set; }
    public int? AutoSalonId { get; set; }
    public int TransmissionId { get; set; }
    public ushort EngineVolume { get; set; }
    public ushort HorsePower { get; set; }
    public DateTime LastUpdated { get; set; }
    public string? Description { get; set; }
    public int Mileage { get; set; }
    public int MileageTypeId { get; set; }
    public int Price { get; set; }
    public ushort? SeatCount { get; set; }
    public bool CreditAvailable { get; set; }
    public bool BarterAvailable { get; set; }
    public List<Image> Images { get; set; } = new();
    public List<Feature> Features { get; set; } = new();

    public User? Owner { get; set; }
    public Brand? Brand { get; set; }
    public Model? Model { get; set; }
    public Year? Year { get; set; }
    public Color? Color { get; set; }
    public Region? Region { get; set; }
    public Market? Market { get; set; }
    public GearType? GearType { get; set; }
    public Category? Category { get; set; }
    public FuelType? Fueltype { get; set; }
    public Currency? Currency { get; set; }
    public MileageType? MileageType { get; set; }
    public Transmission? Transmission { get; set; }
    public AutoSalon? AutoSalon { get; set; }

    public Car() { }
}

字符串
我的Color和Transmission类的结构如下:

public class Color : IEntity
{
    public int Id { get; set; }

    public List<ColorLocale>? ColorLocales { get; set; } 

    public Color() { }
}

public class ColorLocale : IEntity
{
    public int Id { get; set; }
    public int LanguageId { get; set; }
    public string Name { get; set; } = string.Empty;

    public Color? Color { get; set; }
    public Language? Language { get; set; }

    public ColorLocale() { }
} 

public class Transmission : IEntity
{
    public int Id { get; set; }

    public List<TransmissionLocale>? TransmissionLocales { get; set; }

    public Transmission() { }
}

public class TransmissionLocale : IEntity
{
    public int Id { get; set; }
    public int LanguageId { get; set; }
    public string Name { get; set; } = string.Empty;

    public Language? Language { get; set; }
    public Transmission? Transmission { get; set; }

    public TransmissionLocale() { }
}


当我试图从我的数据库(我已经确认是在那里,因为我已经调试它),并返回它从我的控制器,该进程似乎挂起,由于序列化没有完成。
以下是我的控制器的相关部分:

[HttpPost("getcars")]
    public async Task<ActionResult<List<Car>>> GetCars(int pageNumber = 1, int pageSize = 20)
    {
        var carList = await _carService.GetCarsWithPagination("", pageNumber, pageSize);
        return carList;
    }


我尝试过几种方法来解决这个问题,比如将Newtonsoft的ReferenceLoopHandling设置为Ignore,或者将JsonSerializerOptions.ReferenceHandler设置为Preserve,但都没有成功:

builder.Services.AddControllersWithViews()
            .AddNewtonsoftJson(options =>
                options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );
builder.Services.AddControllersWithViews().AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
        });


我想知道是否有人以前遇到过这个问题,并提供了一个解决方案。是否有任何其他设置,我可以调整或替代的方式来构建我的类,以避免这个问题?
任何帮助都非常感谢。
谢谢你,谢谢

yqyhoc1h

yqyhoc1h1#

最好的方法是 * 不 * 尝试序列化实体。实体用作数据域表示,不适合数据传输。将这些关注点分开,构造一个DTO来表示您实际想要发送的数据结构,然后使用EF将实体投影到该DTO。像Automapper这样的Map器有钩子通过ProjectTo<TDTO>()与EF的IQueryable一起工作,以构建高效的查询来填充这些类,而无需将整个实体图加载到内存中。如果您将DTO带回以更新实体,则可以使用Map(src, dest)方法将允许的值复制回新加载的实体。
当涉及到实体本身时,我建议默认使用单向引用,而不是双向引用。尝试做类似序列化实体的事情的一个问题是,其中Transmission具有对一个或多个TransmissionLocale的引用,这些TransmissionLocale具有对Transmission的引用。同样,我不建议为了数据传输而序列化实体,但是经常有这样的情况,我确实想序列化实体,比如记录所讨论的实体的快照,比如记录异常时,或者记录审计日志快照。在大多数情况下,这些引用是不必要的,并且可以减少/消除序列化陷入循环的情况。在大多数情况下,使用单向引用可以很好地解决查询。例如,如果您希望所有传输都用于特定区域设置:
双向:

var transmissions = _context.TransmissionLocales
     .Where(x => x.LocaleId == localeId)
     .Select(x => x.Transmission)
     .ToList();

字符串
单向:

var transmissions = _context.Transmissions
     .Where(x => x.Locales.Any(l => l.LocaleId == localeId)
     .ToList();


就我个人而言,我发现使用单向引用更直接,因为当选择特定类型的实体时,我会去他们的DbSet,而不是链接表,如果我得到与特定顶级实体相关的项目,我会通过该顶级实体。例如,如果我希望所有汽车都具有特定区域的变速器:

var cars = _context.Cars
     .Where(x => x.Transmissions.Any(t => t.Locales.Any(l => l.LocaleId == localeId)
     .ToList();


通常情况下,条件可以通过AND或OR跨关注点组合,因此如果我正在寻找汽车,我希望从汽车查询到汽车的组成部分,以便我可以组合条件,而不是试图从查询其组件中选择汽车。例如,如果我想要一辆带有特定区域的变速器或齿轮类型的汽车。

相关问题