简体   繁体   中英

EF Core with Automapper throw Exception 'Entity type cannot be tracked'

I have a problem with EF Core in combination with automapper.

My Solution contains this hierarchy:

BLL <=> DAL <=> EF Core <=> Mysql Database

I use automapper in the BLL.

And have as example this user class:

public class User
{
    public Guid Id { get; set; } = Guid.NewGuid();

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public Guid? CreatedById { get; set; }

    public Guid? ModifiedById { get; set; }

    public virtual User CreatedBy { get; set; }

    public virtual User ModifiedBy { get; set; }
}

And i have the same class for the UserDTO (DataTransferObject).

I already have a record for an user in the database to create another user, because it's necessary to fill createdby and modifiedby to create or update a user.

Get the user from the database:

UserDTO userDTO = this._UserBLL.GetUser("Unit", "Test");

//BLL
var mapped = new List<UserDTO>();
this._Mapper.Map(dalResult, mapped); //dalResult = List<User> object
return mapped;

And here an example how to add a user:

UserDTO testUser2 = new UserDTO
{
    FirstName = "Test 2",
    LastName = "Test 2",
    CreatedBy = userDTO,
    ModifiedBy = userDTO
};

this._UserBLL.Add(testUser2);

//BLL
List<User> mappedUsers = new List<User>();

this._Mapper.Map(users, mappedUsers); //users = List<UserDTO> that contains testUser2

DbContext.Users.AddRange(mappedUsers); //Throws exception
DbContext.SaveChanges();

The Exception that is thrown: The instance of entity type 'User' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.

But i dont know how to solve the exception. Same issue is, when i update an existing record.

AutoMapping class is:

this.CreateMap<User, UserDTO>()
    .ReverseMap();

Do u have an idea how to fix this? I hope you understand my problem, else not, ask me.

King regards

There are a couple of misconceptions here

  1. Bare in mind that a DTO is a dumb class, in a sense that it does nothing but serving as a container for the information to travel in a strongly typed way. You will be normally dealing with DTOs when the front-end send you some data and your backend catches on the controller parameter (I know this is not exactly true, just trying to make a point here), or when returning a DTO to the front end to whatever reason it needs. The general approach then will be:

  2. Frontend sent a DTO

  3. Backend controller catches

  4. Backend services map the DTO to some domain model of interest (automapper)

  5. Backend services perform the logical steps of the business rules such as saving a record on the database or updating something I don't know you name it

  6. Backend service map the response (if any) to a DTO (automapper)

  7. Backend controller sends the data right back to the front end, to do whatever it needs to do

Now, regarding your bug it's a common bug of entity frameworks begginers. Let me ilustrate what happend:

UserDTO userDTO = this._UserBLL.GetUser("Unit", "Test"); 
//tip: as per the explanation above this should't be a DTO

//EF says "alright!, programmer pull out
//a record from the database and got a object of type UserDTO with Id = "someguid"
//im gonna begin tracking this object to see if it changes in the
//execution of the code.
//In case my fellow programmer wants to modify this object and 
//save it in on the database, as I was tracking it down in 
//the first place, I'm gonna know how to build my
//SQL statements to perform the right update/delete/create sentence in 
//SQL form.

Somewhere over the mapping you are creating the same object type with the same Id and then entity framework go bananas

//EF says: "mmm, that's weird, im tracking the same object 
//again, im gonna alert the human. *throws exception*

The key here is to re-read the documentation of entity framework/core to fully understand how entity framework think Now, regarding your code and business requirement, you should be aiming to do something like this:

//Suppose that the in the controller parameter we receive this DTO
var newUserDto = new UserDto() 
{
    Firstname = "Rodrigo",
    Lastname = "Ramirez",
    CreatedBy = 1, //this can be the user Id of the user that sent this DTO
};

var userToCreate = Mapper.Map<User>(newUserDto);
var userCreator = UserRepository.GetById(newUserDto.CreatedBy);
userToCreate.CreatedBy = userCreator;

final words on this topic:

  1. It's a matter of preferences but I do rather read
var user = Mapper.Map<User>(newUserDto); //hey mapper, be a pal and transform this newUserDto into a User object.
  1. Check that you are using List<> to map when you don't need to.
  2. For this business logic, you don't need to prevent entity framework to begin tracking a given entity. But in case you find such case you can checkout the method "AsNoTracking()" that does that.
  3. Review the necesity of create a new GUID over User class instantiation. This seems to be buggy and you need to fully understand how EF works in order to succed with this practice.
  4. The thing you want to do is a common audit practice, and it's advisable that you override the method OnSaveChanges() to perform this task.

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