Here is full error:
System.InvalidOperationException: "The instance of entity type 'Book' cannot be tracked because another instance with the key value '{BookId: 382234c3-721d-4f34-80e5-57657bbbbb32}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."
It throws when I try to add new entity to my DB from my service in business logic layer. I used EF Core, made repositories and unit of work for interaction with DB in my data access layer.
Here is some code where an error throws, it is in repository:
public void Create(T entity)
{
this.LibraryContext.Set<T>().Add(entity);
}
It, in turn, is called from here:
public bool TakeBookById(Guid bookId, Guid studentId, DateTime issueDate) // student takes the book from library
{
var book = mapper.Map<BookDto>(unit.Books.FindByCondition(x => x.BookId == bookId).FirstOrDefault()); // take that book for later using
if (book != null && book.IsAvailable == true) // if this book is available
{
var student = mapper.Map <StudentDto>(unit.Students.FindByCondition(x => x.StudentId == studentId).FirstOrDefault()); // look for that student
if (student != null && student.LibraryCard.Fields.Where(x => x.Book.IsAvailable == false).Count() <= 10)
{ // if student exists and his/her limit of non-returned books is not exceeded
var libraryCard = mapper.Map<LibraryCardDto>(student.LibraryCard); // take his/her library card
unit.LibraryCardFields.Create( // create library card's field with current data
mapper.Map<LibraryCardField> (
new LibraryCardFieldDto()
{
Id = Guid.NewGuid(), // generate new id
Book = book, // student got this book
LibraryCard = libraryCard, // field is in this library card
IssueDate = issueDate, // note current date
ReturnDate = null // student still didn't return it
} )
);
unit.Save(); // save changes
return true;
}
else { return false; }
}
else { return false; }
}
Here is the code from where I call the service method, as hard-coded as possible for clarity:
StudentService sts = new StudentService("Server=.\\SQLEXPRESS;Database=StudentsLibrary;Trusted_Connection=True;");
sts.TakeBookById(Guid.Parse("382234C3-721D-4F34-80E5-57657BBBBB32"), Guid.Parse("382C74C3-721D-4F34-80E5-57657B6CBC27"), DateTime.Now);
And I also apply the methods that I use from the repositories:
public IEnumerable<T> FindAll()
{
return this.LibraryContext.Set<T>();
}
public IEnumerable<T> FindByCondition(Expression<Func<T, bool>> expression)
{
return this.LibraryContext.Set<T>().Where(expression);
}
I didn't use AsNoTracking() in repository because I want Lazy Loading to work (I don't want to write all that Inlude&ThenInclude stuff for each my model).
And I thought, if specifically this book is being tracked, then I can make context stop tracking it. I wrote something like this:
public void Reset()
{
var entries = libraryContext.ChangeTracker.Entries().ToArray();
foreach (var entry in entries)
{
entry.State = EntityState.Detached;
}
}
And tried this one:
public void Reset()
{
libraryContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
And do you know what? There is still that error. I don't know maybe it all because of mappers?
UPDATE: It IS because of mappers. But how can I fix this situation? It works if in Create method I create object of my DAL model. I cannot create object of my BLL model and then map it. EF can't stand this stuff.
Help me, please. I'm new to EF.
Drop the Auto Mapper. It takes seconds to type your own extension methods and you easily have full control over it.
Your main issue is that your creating a DTO
then converting it to an entity and you haven't configured that correctly. Skip that part entirely and just create the entity. It doesn't make sense to use a DTO
like this. Look at your code below.
unit.LibraryCardFields.Create( // create library card's field with current data
mapper.Map<LibraryCardField> (
new LibraryCardFieldDto()
{
Id = Guid.NewGuid(), // generate new id
Book = book, // student got this book
LibraryCard = libraryCard, // field is in this library card
IssueDate = issueDate, // note current date
ReturnDate = null // student still didn't return it
} )
);
Change this to create your own extension methods and convert it yourself.
Take this for example.
Supposed you passed in a DTO instead of creating it.
var LibraryCardField = LibraryCardFeildDto.ToEntity();//ToEntity is your extension method
unit.LibraryCardFields.Create(LibraryCardField);
Create a new class for the extension methods.
public static class ExampleMapperClass //or w/e you want to call it{
public static LibaryCardField ToEntity(this LibraryCardFieldDto lbf){
return new LibraryCardField{
Id = lbf.Id,
Book = lbf.book.ToEntity(),//create another extension method for this one
LibraryCard = LibraryCardDto.ToEntity(),//same here
IssueDate = lbf.issueDate,
ReturnDate = lbf.returnDate
};
}
}