4

I'm trying to develop an application using .NET Core 3 and EF Core. I encountered an error that I could not find a solution for. I could not do on ".Net Core 3" a structure which can be simply created with PHP eloquent.

Model;

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);
        }
    }

Startup 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": "jhondoe@test.com",
    "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

When I remove .Include(u => u.Orders) code I can take successful response like at localhost/api/users/. I see this error when I use Include

I would like to get this response;

{
  "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"
}
double-beep
  • 5,031
  • 17
  • 33
  • 41
Şahin Başel
  • 99
  • 1
  • 6
  • Mapping to new model classes to use in controller would be neater solution and will avoid circular referencing, Automapper can be used to map without writing too much code. – Nagashree Hs Oct 03 '19 at 15:44

4 Answers4

5

For .NET Core 3, NewtonJson has just released a new patch. The problem was solved when I installed the package Microsoft.AspNetCore.Mvc.NewtonsoftJson.

double-beep
  • 5,031
  • 17
  • 33
  • 41
Şahin Başel
  • 99
  • 1
  • 6
  • Do you need to do anything else? I install the package but still have the same issue –  coinhndp Nov 03 '20 at 13:41
  • .NET Core 3 comes with Microsofts System.Text.Json for serializing and deserializing. Adding Newtonsoft package without changing the service registration does not do anything and shows a missing understanding of the different JSON serializer "in the market". Now that .Net Core 5 has been released (was of course not available when the question was asked) there is a new handler for System.Text.Json that should solve this https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.referencehandler?view=net-5.0 – MariWing Dec 11 '20 at 08:07
1

In my case adding NewtonSoft package only did not work. I also had to modify ConfigurationService method in the Startup.cs file as described in the following answer https://stackoverflow.com/a/58084628/6026570

U.Savas
  • 129
  • 11
0

Your OnModelCreating method is missing the definition for the foreign key constraint. The following should work:

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);

        });
     }

This defines that an order is connected to one user but a user can have multiple orders.

René
  • 3,413
  • 2
  • 20
  • 34
0

I faced the same issue and it got resolved by removing the "inverse navigation property" i.e. remove "public User User { get; set; }" from the Order entity class so that it looks like below:

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

    public int UserId { get; set; }        

    public decimal Price { get; set; }
}
  • This indeed would work but time will come that you will have to include that property, and that will make you go back here to refer to solutions for serializing objects. – d.i.joe Mar 26 '20 at 14:02