Проблема с включением .Net Core 3 и EF Core 3 (JsonException)

Я пытаюсь разработать приложение с использованием .NET Core 3 и EF Core. Я столкнулся с ошибкой, решение которой не нашел. Я не мог создать в ".Net Core 3" структуру, которую можно было бы просто создать с помощью PHP eloquent.

Модель;

public NDEntityContext(DbContextOptions<NDEntityContext> options)
            : base(options)
        { }

        public DbSet<User> Users { get; set; }
        public DbSet<Order> Orders { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<User>(entity =>
            {
                entity.Property(u => u.CreatedAt)
                    .HasDefaultValueSql("DATEADD(HOUR, +3, GETUTCDATE())");

                entity.HasMany(u => u.Orders)
                    .WithOne(o => o.User);
            });
            modelBuilder.Entity<Order>(entity =>
            {
                entity.Property(o => o.CreatedAt)
                    .HasDefaultValueSql("DATEADD(HOUR, +3, GETUTCDATE())");

                entity.HasOne(o => o.User)
                    .WithMany(u => u.Orders)
                    .HasForeignKey(o => o.UserId)
                    .HasConstraintName("Fk_Order_User");
            });
        }
    }

    public class User : EntityBase
    {
        public int UserId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string FullName { get; set; }
        public int Type { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public string Password { get; set; }
        public string Gender { get; set; }
        [IgnoreDataMember]
        public string HomePhone { get; set; }
        [IgnoreDataMember]
        public string WorkPhone { get; set; }
        public DateTime? BirthDate { get; set; }
        public string SmsConfCode { get; set; }
        public bool IsActive { get; set; }
        public bool IsOutOfService { get; set; }

        public ICollection<Order> Orders { get; set; }
    }

    public class Order : EntityBase
    {
        public int OrderId { get; set; }

        public int UserId { get; set; }
        public User User { get; set; }

        public decimal Price { get; set; }
    }

UserController:

[Route("api")]
    [ApiController]
    public class UserController : ControllerBase
    {
        private readonly NDEntityContext _context;
        private readonly ILogger<UserController> _logger;

        public UserController(ILogger<UserController> logger, NDEntityContext context)
        {
            _logger = logger;
            _context = context;
        }

        [HttpGet("users")]
        public async Task<ActionResult<IEnumerable<User>>> GetUsers()
        {
            _logger.LogInformation("API Users Get");
            return await _context.Users.ToListAsync();
        }

        [HttpGet("user/{id:int}")]
        public async Task<ActionResult<User>> GetUser(int id)
        {
            _logger.LogInformation("API User Get");
            return await _context.Users.Include(u => u.Orders).FirstOrDefaultAsync(e => e.UserId == id);
        }
    }

Запуск ConfigureServices;

services.AddControllersWithViews().AddNewtonsoftJson(opt => opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);

services.AddDbContext<NDEntityContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DevConnection")));

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "ND API", Version = "v1" });
});

localhost/api/users/;

[
  {
    "userId": 1,
    "firstName": "John",
    "lastName": "Doe",
    "fullName": "John Doe",
    "type": 1,
    "email": "[email protected]",
    "phone": "01234567890",
    "password": "123456789",
    "gender": "Man",
    "homePhone": "123456789",
    "workPhone": "987654321",
    "birthDate": null,
    "smsConfCode": null,
    "isActive": true,
    "isOutOfService": false,
    "orders": null,
    "createdAt": "2019-10-01T21:47:54.2966667",
    "updatedAt": null,
    "deletedAt": null
  }
]

localhost/api/user/1/;

System.Text.Json.JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerCycleDetected(Int32 maxDepth)
   at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type inputType, JsonSerializerOptions options, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
   at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Accept: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: tr,en;q=0.9
Connection: close
Cookie: .AspNet.Consent=yes
Host: localhost:44352
Referer: https://localhost:44352/swagger/index.html
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
sec-fetch-mode: cors
sec-fetch-site: same-origin

Когда я удаляю код .Include(u => u.Orders), я могу получить успешный ответ, как на localhost/api/users/. Я вижу эту ошибку, когда использую "Включить"

Я хотел бы получить такой ответ;

{
  "userId": 0,
  "firstName": "string",
  "lastName": "string",
  "fullName": "string",
  "type": 0,
  "email": "string",
  "phone": "string",
  "password": "string",
  "gender": "string",
  "birthDate": "2019-10-02T18:24:44.272Z",
  "smsConfCode": "string",
  "isActive": true,
  "isOutOfService": true,
  "orders": [
    {
      "orderId": 0,
      "userId": 0,
      "price": 0,
      "createdAt": "2019-10-02T18:24:44.272Z",
      "updatedAt": "2019-10-02T18:24:44.272Z",
      "deletedAt": "2019-10-02T18:24:44.272Z"
    }
  ],
  "createdAt": "2019-10-02T18:24:44.272Z",
  "updatedAt": "2019-10-02T18:24:44.272Z",
  "deletedAt": "2019-10-02T18:24:44.272Z"
}

person Şahin Başel    schedule 02.10.2019    source источник
comment
Сопоставление с новыми классами моделей для использования в контроллере было бы более аккуратным решением и позволило бы избежать циклических ссылок, Automapper можно использовать для сопоставления без написания слишком большого количества кода.   -  person Nagashree Hs    schedule 03.10.2019


Ответы (4)


Для .NET Core 3 NewtonJson только что выпустил новый патч. Проблема была решена, когда я установил пакет Microsoft.AspNetCore.Mvc.NewtonsoftJson.

person Şahin Başel    schedule 06.10.2019
comment
Вам нужно что-нибудь еще сделать? Я устанавливаю пакет, но проблема не устранена - person coinhndp; 03.11.2020
comment
.NET Core 3 поставляется с Microsoft System.Text.Json для сериализации и десериализации. Добавление пакета Newtonsoft без изменения регистрации службы ничего не делает и показывает отсутствие понимания различных сериализаторов JSON на рынке. Теперь, когда выпущен .Net Core 5 (который, конечно, был недоступен, когда задавался вопрос), есть новый обработчик для System.Text.Json, который должен решить эту проблему docs.microsoft.com/en-us/dotnet/api/ - person MariWing; 11.12.2020

В моем случае добавление только пакета NewtonSoft не помогло. Мне также пришлось изменить метод ConfigurationService в файле Startup.cs, как описано в следующем ответе https://stackoverflow.com/a/58084628/6026570

person U.Savas    schedule 27.10.2019

В вашем методе OnModelCreating отсутствует определение ограничения внешнего ключа. Следующее должно работать:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().Property(u => u.CreatedAt).HasDefaultValueSql("DATEADD(HOUR, +3, GETUTCDATE())");

        modelBuilder.Entity<Order>(entity =>
        {
           entity.Property(o => o.CreatedAt)
                 .HasDefaultValueSql("DATEADD(HOUR, +3, GETUTCDATE())");

           entity.HasOne(o => o.User)
                 .WithMany(u => u.Orders)
                 .HasForeignKey(o => o.UserId)
                 .HasConstraintName("Fk_Order_User);

        });
     }

Это определяет, что заказ связан с одним пользователем, но у пользователя может быть несколько заказов.

person René    schedule 04.10.2019
comment
Пробовал твой образец. Но проблема все еще сохраняется. Ошибка - это исключение JsonException, которое я принял. :( - person Şahin Başel; 06.10.2019

Я столкнулся с той же проблемой, и она была решена путем удаления «свойства обратной навигации», т.е. удаления «public User User {get; set;}» из класса сущности Order, чтобы он выглядел, как показано ниже:

public class Order : EntityBase
{
    public int OrderId { get; set; }

    public int UserId { get; set; }        

    public decimal Price { get; set; }
}
person Ashish Mathur    schedule 02.11.2019
comment
Это действительно сработает, но придет время, когда вам придется включить это свойство, и это заставит вас вернуться сюда, чтобы обратиться к решениям для сериализации объектов. - person d.i.joe; 26.03.2020