简体   繁体   中英

Can't insert data with navigation properties on Entity Framework Core 6

Post updated with solution.

Let's say i have two simple model classes to explain my problem. Each model as navigation properties for DB relationships. Everything it's ok with migrations creation and update-database, also with postman for GET endpoints. The problem... i can POST a User, but can't POST a Car record. I followed the Code First from Microsoft tutorial with Fluent API method, but i also tried with Data Annotations, but same result (between some other tests i also tried with 'virtual' keyword). A brief search pointed me to nested Object/Array upon doing the POST record to DB, but also no success, same error! This is a little example from my project with more than 6 model classes, but with this two i think i can explain my problem. This is for one-to-many relationship.

public class User
    {
        public int Id { get; set; }
        public string Age { get; set; }
        public string Name { get; set; }
        public string City { get; set; }
        public string Country { get; set; }

        // navigation property
        public List<Car> Cars { get; set; }
    }

And Car model:

public class Car
    {
        public int Id { get; set; }
        public int Year { get; set; }
        public string Plate { get; set; }

        // navigation property
        public int UserId { get; set; }
        public User User { get; set; }
    }

And here's the DbContext:

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

        public DbSet<User> Users { get; set; }
        public DbSet<Car> Cars { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // setting database relationships with constraints and cascade delete
            // one-to-many for User -> Car tables
            modelBuilder.Entity<Car>()
                .HasOne(u => u.User)
                .WithMany(c => c.Cars)
                .HasForeignKey(u => u.UserId)
                .HasConstraintName("FK_User_Cars")
                .OnDelete(DeleteBehavior.Cascade);
        }

I POST this example record in api/cars endpoint:

{
    "year": 2012,
    "plate": "YY-11-BB",
    "userId": 2
}

Postman gives me:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "00-36feeb71fcd60b67da499091bf94598a-1b14d3e2c48fc635-00",
    "errors": {
        "User": [
            "The User field is required."
        ]
    }
}

I also tried POSTing an empty array to try to cheat:

{
        "year": 2012,
        "plate": "YY-11-BB",
        "userId": 2,
        "user": []
    }

CarsController:

public async Task<ActionResult<Car>> PostCar(Car car)
        {
            if (_context.Cars == null)
            {
                return Problem("Entity set 'MyPersonalProjectContext.Cars'  is null.");
            }

            _context.Cars.Add(car);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetCar", new { id = car.Id }, car);
        }

[SOLUTION]: After some digging and try and error, i came up with the solution, at least for my case. I will post here what i've achieved so that many others with the same problem can solve it also!

Create a ModelDTO (for one who don't know what is, here's a good explanation from this 12 years old topic What is a Data Transfer Object (DTO)? )

public class CreateCarDTO
    {
        public int Id { get; set; }
        public int Year { get; set; }
        public string Plate { get; set; }
        public int UserId { get; set; }
    }

Then i've made a little changes in my controller POST method:

[HttpPost]
        public async Task<ActionResult<Car>> PostCar(CreateCarDTO request)
        {

            var user = await _context.Users.FindAsync(request.UserId);
            if (user == null)
            {
                return NotFound();
            }
            var newCar = new Car
            {
                Year = request.Year,
                Plate = request.Plate,
                User = user
            };

            _context.Cars.Add(newCar);
            await _context.SaveChangesAsync();
            return await GetCar(newCar.UserId);
        }

And finally for JSON exceptions, i added [JsonIgnore] in Model:

public class Car
    {
        public int Id { get; set; }
        public int Year { get; set; }
        public string Plate { get; set; }

        // navigation property for parent entity and foreign key
        // one-to-many car -> user
        public int UserId { get; set; }
        [JsonIgnore]
        public User User { get; set; }

    }

Hope this solution help others :-).

First of all If you want to save Cars without User you should Cars entity as below.

public class Car
{
    public int Id { get; set; }
    public int Year { get; set; }
    public string Plate { get; set; }

    // navigation property make nullable
    public int? UserId { get; set; }
    public User User { get; set; }
}

If you want to save with user in this case that User must exist in database. For example in your example User must exists in database with id=2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM