Nalaka526
Nalaka526

Reputation: 11464

Entity framework inserts new record to navigation table inappropriately

I'm facing an issue when inserting records with Many to Many navigation property,

I have these models

//ModelBase
public class ModelBase
{
    [Key]
    public long Id { get; set; }
}

//Book
public class Book : ModelBase
{
    public string Title { get; set; }
    public virtual ICollection<Author> Authors { get; set; }
    public virtual ICollection<PublishedBook> PublishedBooks { get; set; }
}

//Author
public class Author : ModelBase
{
    public string Name { get; set; }
    public virtual ICollection<Book> Books { get; set; }
}

And DTOs called BookDto and AuthorDto for transfer data between layers

From the controller I fill data to DTOs and call Create method (which is in separate layer) to Save data to database,

BookDto bookDto = new BookDto()
{
    Id = model.Id,
    Title = model.Title,
    Authors = model.AuthorIds.Select(c => new AuthorDto { Id = c }).ToList()
}

using (ServiceFactory facory = new ServiceFactory())
{
    factory.Book.Create(bookDto);
}

In the Create method I map DTOs with POCOs using ValueInjector

public void Create(BookDto bookDTO)
{
    Book book = new Book();
    book.InjectFrom<DeepCloneInjection>(bookDTO);
    bookRepository.Add(book);
    unitOfWork.Commit();
}

And it calls Add method in Genaric Repository Base

public virtual void Add(T entity)
{
    dbset.Add(entity);
}

This inserts data to Books table and BookAuthors tables appropriately BUT inserts new record into the Authors table even if I pass Authors which has existing AuthorIds for Book.Authors from the controller.

Any Idea how to fix this?

Upvotes: 1

Views: 388

Answers (1)

Slauma
Slauma

Reputation: 177163

You are missing to attach the existing authors to the context. You could do it like so:

public void Create(BookDto bookDTO)
{
    Book book = new Book();
    book.InjectFrom<DeepCloneInjection>(bookDTO);
    foreach (var author in book.Authors)
        authorRepository.Attach(author);
    bookRepository.Add(book);
    unitOfWork.Commit();
}

where the generic Attach methods is just implemented calling dbset.Attach(entity);. I am assuming that all repositories (authorRepository and bookRepository) share the same context instance. Otherwise the above won't work.

The fact that the AuthorId already exists in the database doesn't matter. EF doesn't check if the Id exists by a query first. If you call Add on an object graph of detached entities it tries to insert the whole graph by generating INSERT statements for the parent and for all children. By attaching the children you effectively turn off the INSERT statements for those.

Upvotes: 2

Related Questions