【问题标题】:EF Core: How to avoid duplicate entries?EF Core:如何避免重复条目?
【发布时间】:2020-12-30 19:28:34
【问题描述】:

我正在构建一个应用程序来保存图书目录。

添加图书时,我正在检查该图书的出版商和作者是否已存在于数据库中。如果答案是否定的,那么它就会被创建。但是,例如,如果一个出版商已经存在,我会从数据库中获取它并将其设置在传入的 Book 对象上,以避免创建一个新条目,基本上具有相同的值但不同的 id。

但是,我在尝试执行此操作时遇到错误:

System.InvalidOperationException:实体类型的实例 无法跟踪“发布者”,因为另一个具有密钥的实例 值“{Id}”已经存在 跟踪。附加现有实体时,确保只有一个实体 附加了具有给定键值的实例。

我一直试图弄明白这个错误,但还没有找到答案(我在为作者做同样的事情时遇到同样的错误)。

以下是部分代码:

图书服务

public async Task<BookDto> AddBookAsync(BookDto book)
{
    await SetPublisherIfItAlreadyExists(book);
    await SetAnyAuthorsThatMayAlreadyExist(book);
    
    var bookEntity = _mapper.Map<Book>(book);
    var newBook = await _booksRepository.AddBookAsync(bookEntity);
    return _mapper.Map<BookDto>(newBook);
}

private async Task SetPublisherIfItAlreadyExists(BookDto book)
{
    if (book.Publisher?.Name == null) return;
    var publisher = await _publisherRepository.GetPublisherByNameAsync(book.Publisher.Name);
    if (publisher == null) return;
    book.Publisher = _mapper.Map<PublisherDto>(publisher);
}

private async Task SetAnyAuthorsThatMayAlreadyExist(BookDto book)
{
    if (!book.Authors.Any()) return;
    
    var existingAuthors = new List<Author>();
    var nonExistingAuthors = new List<AuthorDto>();

    foreach (var incomingAuthor in book.Authors)
    {
        var author = await _authorsRepository.GetAuthorByNameAsync(incomingAuthor.FirstName, incomingAuthor.LastName);
        if (author != null)
        {
            existingAuthors.Add(author);
        }
        else
        {
            nonExistingAuthors.Add(incomingAuthor);
        }
    }

    if (!existingAuthors.Any()) return;
    var existingAuthorsDtos = _mapper.Map<List<AuthorDto>>(existingAuthors);
    book.Authors = existingAuthorsDtos.Concat(nonExistingAuthors).ToList();
}

PublisherRepository

​​>
public async Task<Publisher> GetPublisherByNameAsync(string publisherName)
{
    var publisher = await _dbContext.Publishers.FirstOrDefaultAsync(p => p.Name.Equals(publisherName));
    return publisher;
}

AuthorsRepository

​​>
public async Task<Author> GetAuthorByNameAsync(string firstName, string lastName)
{

    var author = await _dbContext.Authors.FirstOrDefaultAsync(a =>
        a.FirstName.ToLower().Equals(firstName.ToLower()) &&
        a.LastName.ToLower().Equals(lastName.ToLower())
    );
    return author;
}

图书资料库

public async Task<Book> AddBookAsync(Book book)
{
    await _dbContext.Books.AddAsync(book);
    await _dbContext.SaveChangesAsync();
    return book;
}

如何触发错误(更新)

为简单起见,以下是 HTTP 请求中包含的简化负载。假设第一次请求时数据库为空

第一个请求

// POST /books
{
   title: "book title",
   publisher: "sunny publications"
   ... // other properties
}

第二次请求

// POST /books
{
   title: "some other book",
   publisher: "sunny publications"
   ... // other properties
}

第二个请求会引发所描述的错误。

我在这里缺少什么?将不胜感激!

【问题讨论】:

  • 那么到底是哪里抛出的异常呢?该错误并不意味着您的数据库中有一些 dup 实体,它位于包含 dup 跟踪实体的DbContext 中(通过实体键属性标识)
  • @Hopeless 让我更新问题以包含触发错误的示例
  • 我一直遇到这类问题。我的建议是,当您只是从数据库中选择(例如,在您的情况下,您只是按姓名选择作者以查看他是否存在),添加此AsNoTracking()。例如,在您的GetAuthorByNameAsync 中,看看是否有帮助

标签: c# asp.net-core entity-framework-core webapi


【解决方案1】:

如果发布者存在,您将获得它,这将开始跟踪该实体。

然后您将其映射到 PublisherDto 以将其分配给您的 BookDto,并将 BookDto 映射回实体,包括发布者。这将创建具有相同 Id 的第二个 Publisher 实例。

不要再映射又映射,在实体上创建实体。

【讨论】:

  • 啊!!!现在你指出来很明显。这帮助我找到了避免创建第二个发布者实例的方法。如果存在发布者并且从数据库返回结果,我只是直接在实体上设置它,而不是在 BookDto 上设置它,然后将 Dto 映射回实体。非常好和简洁的答案。非常感谢!
  • 它也可以按照您的方式工作,但只需通过在“查询”中添加AsNoTracking() 来调整GetPublisherByNameAsync / GetAuthorByNameAsync
  • @Ergis 我试过了,但实际上我得到了一个关于该 ID 已经存在的条目的不同错误!
  • 哦,对不起,nvm。还有更多内容
猜你喜欢
  • 2023-03-22
  • 2012-08-24
  • 1970-01-01
  • 2022-01-08
  • 1970-01-01
  • 2021-07-23
  • 2014-06-17
  • 2017-06-23
相关资源
最近更新 更多