为什么实体框架在MVC应用中的预期行为,而在WebAPI中完全不同?

发布于 2025-02-07 14:03:58 字数 7088 浏览 2 评论 0原文

我已经与Entity Framework 6合作了一段时间,今天我从一个新的Web API项目开始,并且一旦执行包括其他实体的查询,我会收到这样的错误:

system.text.json.jsonexception:检测到可能的对象周期。这可能是由于一个周期引起的,或者如果对象深度大于最大允许的深度32。

事物和不明白的是,当我在运行常规MVC应用时使用相同的急切加载时,它将带给我准确我需要什么,别无其他。

到目前为止,这是我正在使用的相关代码:

beer.cs

public class Beer
{
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; } = null!;
    
    public int BrandId { get; set; }

    public virtual Brand Brand { get; set; } = null!;
}

brand.cs

public class Brand
{
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; } = null!;

    public virtual ICollection<Beer> Beers { get; set;} = null!;
}

beerrepository.cs

public class BeerRepository : IBeerRepository
{
    private readonly BeerContext _context;

    public BeerRepository(BeerContext context)
    {
        _context = context;
    }

    public async Task CreateBeerAsync(Beer beer)
    {
        if(beer == null)
            throw new ArgumentNullException(nameof(beer));

        await _context.Beers.AddAsync(beer);
    }

    //BUG: This is the one causing me issues!
    public async Task<ICollection<Beer>> GetAsync() => await _context.Beers.Include(b => b.Brand).ToListAsync();

    public async Task<Beer> GetByIdAsync(int id) => await _context.Beers.FirstAsync(b => b.Id == id);

    public async Task SavaChangesAsync()
    {
        await _context.SaveChangesAsync();
    }
}

beercontext.cs

#nullable disable
public partial class BeerContext : DbContext
{
    public DbSet<Beer> Beers { get; set; }
    public DbSet<Brand> Brands { get; set; }

    public BeerContext(DbContextOptions opt) : base(opt)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //BEER ENTITY
        modelBuilder.Entity<Beer>(entity => {
            entity.HasData(
                new Beer { Id = 1, BrandId = 1, Name = "Torobayo" },
                new Beer { Id = 2, BrandId = 1, Name = "Bock" },
                new Beer { Id = 3, BrandId = 1, Name = "Lager Sin Filtrar" },
                new Beer { Id = 4, BrandId = 2, Name = "Super Dry" },
                new Beer { Id = 5, BrandId = 2, Name = "Soukai" }
            );
        });

        //BRAND ENTITY
        modelBuilder.Entity<Brand>(entity => {
            entity.HasData(
                new Brand{ Id = 1, Name = "Kunstman" },
                new Brand{ Id = 2, Name = "Asahi" }
            );
        });

        OnModelCreatingPartial(modelBuilder);
    }

    partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

beerscontroller.cs

[Route("api/[controller]")]
[ApiController]
public class BeersController : ControllerBase
{
    private readonly IBeerRepository _repository;
    private readonly IMapper _mapper;

    public BeersController(IMapper mapper, IBeerRepository repository)
    {
        _mapper = mapper;
        _repository = repository;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<BeerReadDTO>>> GetAllBeers()
    {
        var beers = await _repository.GetAsync();
        return Ok(_mapper.Map<IEnumerable<BeerReadDTO>>(beers));
    }
}

我也更改了program.cs文件

builder.Services.AddControllers().AddJsonOptions(opt => {
    opt.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});

。但是相反,我得到了这个“不错的”数据!

[
  {
    "id": 1,
    "name": "Torobayo",
    "brand": {
      "id": 1,
      "name": "Kunstman",
      "beers": [
        {
          "id": 1,
          "name": "Torobayo",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 2,
          "name": "Bock",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 3,
          "name": "Lager Sin Filtrar",
          "brandId": 1,
          "brand": null
        }
      ]
    }
  },
  {
    "id": 2,
    "name": "Bock",
    "brand": {
      "id": 1,
      "name": "Kunstman",
      "beers": [
        {
          "id": 1,
          "name": "Torobayo",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 2,
          "name": "Bock",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 3,
          "name": "Lager Sin Filtrar",
          "brandId": 1,
          "brand": null
        }
      ]
    }
  },
  {
    "id": 3,
    "name": "Lager Sin Filtrar",
    "brand": {
      "id": 1,
      "name": "Kunstman",
      "beers": [
        {
          "id": 1,
          "name": "Torobayo",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 2,
          "name": "Bock",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 3,
          "name": "Lager Sin Filtrar",
          "brandId": 1,
          "brand": null
        }
      ]
    }
  },
  {
    "id": 4,
    "name": "Super Dry",
    "brand": {
      "id": 2,
      "name": "Asahi",
      "beers": [
        {
          "id": 4,
          "name": "Super Dry",
          "brandId": 2,
          "brand": null
        },
        {
          "id": 5,
          "name": "Soukai",
          "brandId": 2,
          "brand": null
        }
      ]
    }
  },
  {
    "id": 5,
    "name": "Soukai",
    "brand": {
      "id": 2,
      "name": "Asahi",
      "beers": [
        {
          "id": 4,
          "name": "Super Dry",
          "brandId": 2,
          "brand": null
        },
        {
          "id": 5,
          "name": "Soukai",
          "brandId": 2,
          "brand": null
        }
      ]
    }
  }
]

这是在后台运行的查询...

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (49ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [b].[Id], [b].[BrandId], [b].[Name], [b0].[Id], [b0].[Name]
      FROM [Beers] AS [b]
      INNER JOIN [Brands] AS [b0] ON [b].[BrandId] = [b0].[Id]

我实际上需要的是:

[
  {
    "id": 1,
    "name": "Torobayo",
    "brand":"Kunstman"
  },
  {
    "id": 2,
    "name": "Bock",
    "brand":"Kunstman"
  },
  {
    "id": 3,
    "name": "Lager Sin Filtrar",
    "brand":"Kunstman"
  },
  {
    "id": 4,
    "name": "Super Dry",
    "brand":"Asahi"
  },
]

自从我用完的想法以来,任何帮助都是一个好的开始。非常感谢!

编辑: 因此,我决定还发布我的DTO文件以及自动应用程序配置...似乎尚不清楚我实际上是从啤酒实体到相应的DTO绘制的。

BEERREADDTO.CS

public class BeerReadDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Brand Brand { get; set; }
}

AUTOMAPPERCONF.CS

public class AutoMapperConf : Profile
{
    public AutoMapperConf()
    {
        // Source -> Target
        CreateMap<Beer,BeerReadDTO>();
    }
}

I've been working with Entity Framework 6 for some time now and today I started with a new Web API project and as soon as I execute a query that includes some other entity I receive an error like this:

System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.

The thing and do not understand is that, when I used the same Eager Loading while running a regular MVC App it will bring me exactly what I need, nothing else.

So far this is the relevant code I'm working with:

Beer.cs

public class Beer
{
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; } = null!;
    
    public int BrandId { get; set; }

    public virtual Brand Brand { get; set; } = null!;
}

Brand.cs

public class Brand
{
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; } = null!;

    public virtual ICollection<Beer> Beers { get; set;} = null!;
}

BeerRepository.cs

public class BeerRepository : IBeerRepository
{
    private readonly BeerContext _context;

    public BeerRepository(BeerContext context)
    {
        _context = context;
    }

    public async Task CreateBeerAsync(Beer beer)
    {
        if(beer == null)
            throw new ArgumentNullException(nameof(beer));

        await _context.Beers.AddAsync(beer);
    }

    //BUG: This is the one causing me issues!
    public async Task<ICollection<Beer>> GetAsync() => await _context.Beers.Include(b => b.Brand).ToListAsync();

    public async Task<Beer> GetByIdAsync(int id) => await _context.Beers.FirstAsync(b => b.Id == id);

    public async Task SavaChangesAsync()
    {
        await _context.SaveChangesAsync();
    }
}

BeerContext.cs

#nullable disable
public partial class BeerContext : DbContext
{
    public DbSet<Beer> Beers { get; set; }
    public DbSet<Brand> Brands { get; set; }

    public BeerContext(DbContextOptions opt) : base(opt)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //BEER ENTITY
        modelBuilder.Entity<Beer>(entity => {
            entity.HasData(
                new Beer { Id = 1, BrandId = 1, Name = "Torobayo" },
                new Beer { Id = 2, BrandId = 1, Name = "Bock" },
                new Beer { Id = 3, BrandId = 1, Name = "Lager Sin Filtrar" },
                new Beer { Id = 4, BrandId = 2, Name = "Super Dry" },
                new Beer { Id = 5, BrandId = 2, Name = "Soukai" }
            );
        });

        //BRAND ENTITY
        modelBuilder.Entity<Brand>(entity => {
            entity.HasData(
                new Brand{ Id = 1, Name = "Kunstman" },
                new Brand{ Id = 2, Name = "Asahi" }
            );
        });

        OnModelCreatingPartial(modelBuilder);
    }

    partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

BeersController.cs

[Route("api/[controller]")]
[ApiController]
public class BeersController : ControllerBase
{
    private readonly IBeerRepository _repository;
    private readonly IMapper _mapper;

    public BeersController(IMapper mapper, IBeerRepository repository)
    {
        _mapper = mapper;
        _repository = repository;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<BeerReadDTO>>> GetAllBeers()
    {
        var beers = await _repository.GetAsync();
        return Ok(_mapper.Map<IEnumerable<BeerReadDTO>>(beers));
    }
}

I also changed the Program.cs file like this:

builder.Services.AddControllers().AddJsonOptions(opt => {
    opt.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});

It works (at least it will remove the error) but instead I'm getting this "nice" data!

[
  {
    "id": 1,
    "name": "Torobayo",
    "brand": {
      "id": 1,
      "name": "Kunstman",
      "beers": [
        {
          "id": 1,
          "name": "Torobayo",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 2,
          "name": "Bock",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 3,
          "name": "Lager Sin Filtrar",
          "brandId": 1,
          "brand": null
        }
      ]
    }
  },
  {
    "id": 2,
    "name": "Bock",
    "brand": {
      "id": 1,
      "name": "Kunstman",
      "beers": [
        {
          "id": 1,
          "name": "Torobayo",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 2,
          "name": "Bock",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 3,
          "name": "Lager Sin Filtrar",
          "brandId": 1,
          "brand": null
        }
      ]
    }
  },
  {
    "id": 3,
    "name": "Lager Sin Filtrar",
    "brand": {
      "id": 1,
      "name": "Kunstman",
      "beers": [
        {
          "id": 1,
          "name": "Torobayo",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 2,
          "name": "Bock",
          "brandId": 1,
          "brand": null
        },
        {
          "id": 3,
          "name": "Lager Sin Filtrar",
          "brandId": 1,
          "brand": null
        }
      ]
    }
  },
  {
    "id": 4,
    "name": "Super Dry",
    "brand": {
      "id": 2,
      "name": "Asahi",
      "beers": [
        {
          "id": 4,
          "name": "Super Dry",
          "brandId": 2,
          "brand": null
        },
        {
          "id": 5,
          "name": "Soukai",
          "brandId": 2,
          "brand": null
        }
      ]
    }
  },
  {
    "id": 5,
    "name": "Soukai",
    "brand": {
      "id": 2,
      "name": "Asahi",
      "beers": [
        {
          "id": 4,
          "name": "Super Dry",
          "brandId": 2,
          "brand": null
        },
        {
          "id": 5,
          "name": "Soukai",
          "brandId": 2,
          "brand": null
        }
      ]
    }
  }
]

This is the query running in the background...

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (49ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [b].[Id], [b].[BrandId], [b].[Name], [b0].[Id], [b0].[Name]
      FROM [Beers] AS [b]
      INNER JOIN [Brands] AS [b0] ON [b].[BrandId] = [b0].[Id]

And what I actually need is this:

[
  {
    "id": 1,
    "name": "Torobayo",
    "brand":"Kunstman"
  },
  {
    "id": 2,
    "name": "Bock",
    "brand":"Kunstman"
  },
  {
    "id": 3,
    "name": "Lager Sin Filtrar",
    "brand":"Kunstman"
  },
  {
    "id": 4,
    "name": "Super Dry",
    "brand":"Asahi"
  },
]

Any help will be a good start since I ran out of ideas. Thank you very much!

EDIT:
So, i decided to also post my DTO File and also the Automapper configuration... looks like it is not clear that i'm actually mapping from Beer entity to the corresponding DTO.

BeerReadDTO.cs

public class BeerReadDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Brand Brand { get; set; }
}

AutoMapperConf.cs

public class AutoMapperConf : Profile
{
    public AutoMapperConf()
    {
        // Source -> Target
        CreateMap<Beer,BeerReadDTO>();
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

百合的盛世恋 2025-02-14 14:03:58

好的..谢谢 kamran khan 他指向我朝正确的方向指向正确的方向!...将导航属性从品牌删除到啤酒,并将DTO品牌更改为字符串。

这是我更改的文件,它运行良好!

public class Brand
{
    [Key]
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; } = null!;

    //public virtual ICollection<Beer> Beers { get; set;} = null!;
}

public class BeerReadDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Brand { get; set; }
}

public class AutoMapperConf : Profile
{
    public AutoMapperConf()
    {
        // Source -> Target
        CreateMap<Beer,BeerReadDTO>()
            .ForMember(d => d.Brand, opt => opt.MapFrom(src => src.Brand.Name));
    }
}

非常感谢!

Okay.. thanks to Kamran Khan who pointed me in the right direction!... the issue was fixed just by removing the navigation property from Brands to Beers and changing the Brands for the DTO to a string.

Here are the files i changed and it worked fine!

public class Brand
{
    [Key]
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; } = null!;

    //public virtual ICollection<Beer> Beers { get; set;} = null!;
}

public class BeerReadDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Brand { get; set; }
}

public class AutoMapperConf : Profile
{
    public AutoMapperConf()
    {
        // Source -> Target
        CreateMap<Beer,BeerReadDTO>()
            .ForMember(d => d.Brand, opt => opt.MapFrom(src => src.Brand.Name));
    }
}

Thank you very much!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文