Search code examples
c#asp.net-coreentity-framework-corecode-first

Which is the best practice to add a related objects in Entity Framework?


I recently created a Web API project in .NET Core 3.1 and I implemented Entity Framework Core using code first approach. Each of my models refer to others like this:

public class Condominium
{
    public int Id { get; set; }
    public Address Address { get; set; }
    public int Levels { get; set; }
    public string Description { get; set; }
    public CondominiumType CondominiumType { get; set; }
}

As you can see in this example I am referring to Address and to CondominiumType without using reference Id. Entity Framework will create the corresponding foreign keys when the database will be migrated.

This works perfectly fine when I have to add a new Condominium with a new Address but i am facing an issue when I want to add an existing Address or Condominium Type.

When I pass an object to the context with an Id that has already been added to the database the program throws an exception because i'm violating the UNIQUE constraint of the table. I know that I can fix this problem by adding reference keys writing something like this

    public class Condominium
{
    public int Id { get; set; }
    public int AddressId { get; set; }
    public Address Address { get; set; }
    public int Levels { get; set; }
    public string Description { get; set; }
    public int CondominiumTypeId { get; set; }
    public CondominiumType CondominiumType { get; set; }

}

BUT I'm wondering which will be the proper way to resolve cases like this. ¿Is It Possible to add existing objects without rewriting my classes?

In the business Layer i'm passing the data throw an UnitOfWork Patter:

 public async Task<bool> AddCondominium(int AgencyId,CondominiumDTO newCondominium)
    {
        var condominium = _mapper.Map<Condominium>(newCondominium);
        condominium.ConsortiumAdministrationAgency = new ConsortiumAdministrationAgency() { Id = AgencyId };
        _unitOfWork.Addresses.Add(condominium.Address);
        _unitOfWork.Condominiums.Add(condominium);
        return await _unitOfWork.CompleteAsync();
    }

Solution

  • EntityFramework has to know if the object you have added to the context should be a new entity in the database or represent an already existing entity (with an Id). When you use the Add() method you are saying that this object is for a new row/entity. But this means that it tries to add the new entity with the values in the object. When it has the Id property set (which is the primary key) it tries to add the new row with that id, which will fail with a duplicate key constraint violation.

    There are several solutions. You can get the object you want to reference from the context you are using. When you use something like

     Address address = _context.Addresses.Single(it => it.Id == someId);
    

    you can use the address reference everywhere in your other entity classes to reference the existing entity from the database. This way, the object in address is in the state of "tracked" or "attached" to the context of EntityFramework. The EntityFramework will not try to add it as a new row when you run SaveChanges() but rather send UPDATE queries when you change a value (if any).

    The other way is to attach the address with the Attach() method instead of Add() to the context. That way the context of EntityFramework put the object in the "tracked" or "attached" state and will not try to add it as a new row when you run SaveChanges() and assumes it is a entity from the database without running a SELECT query from the database.