【问题标题】:EF Core - ContainsAsync verification is failed. Duplicated registersEF Core - ContainsAsync 验证失败。重复的寄存器
【发布时间】:2020-11-05 14:29:16
【问题描述】:

我的代码验证数据库中是否已存在项目。如果不存在,则会添加。

foreach(var element in elements)
{
  var item = new Item
  {
    Id = element.Id,
    ...
  }

  if (!await context.Itens.ContainsAsync(new Item { Id = item.Id }))
    await context.Itens.AddAsync(item);
}

await context.SaveChangesAsync();

请注意,项目是在 foreach 循环中创建的,并且上下文更改保存在循环之外。此代码无法正常工作。我的数据库中有重复的项目。

也许 ContainsAsync 方法只是在数据库上验证,而不是在 DbSet 上验证?

【问题讨论】:

  • Id不是主键吗?
  • new Item { Id = item.Id } 永远不会等于 context.Items 中的任何项目,因为它基于对象相等性。

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


【解决方案1】:

试试这个

foreach(var element in elements)
{
  

  if ((await context.Itens.FindAsync(element.Id)!=null)
{
var item = new Item
  {
    Id = element.Id,
    ...
  }
    context.Itens.Add(item);
}
}

await context.SaveChangesAsync();

如果您不需要在数据库中检查 Id,只需创建项目列表并在每次检查它是否已经存在时添加它们:

var items= List<Item>();

foreach(var element in elements)
{
  

  if (!items.Any(i=>i.Id==elementId))
{

var item = new Item
  {
    Id = element.Id,
    ...
  }
    items.Add(item);
}
}

  context.Items.AddRange(items.ToArray());
await context.SaveChangesAsync();

或者如果你有数百万个元素,那么


var items= List<Item>();

foreach(var element in elements)
{
  
var item = new Item
  {
    Id = element.Id,
    ...
  }
    items.Add(item);
}

var distinctItems = items.Distinct(new DistinctItemComparer());

  context.Items.AddRange(distinctItems.ToArray());
await context.SaveChangesAsync();

class DistinctItemComparer : IEqualityComparer<Item> {

    public bool Equals(Item x, Item y) {
        return x.Id == y.Id;
         // &&  x.Name == y.Name;
     }

    public int GetHashCode(Item obj) {
        return obj.Id.GetHashCode() 
        //  ^  obj.Name.GetHashCode() ^
      }
}

【讨论】:

  • Find 仅在它们已经将实体加载到内存中时才有效,否则它们每次都会对数据库进行查询。
  • “如果上下文中存在具有给定主键值的实体,则立即返回它而不向商店发出请求。”来自docs.microsoft.com/en-us/dotnet/api/…。查找也是如此。
  • 是的,这就是我所说的 Find 只有在实体已加载到内存中时才有效。如果操作没有加载任何实体,它将每次向数据库发出请求
  • 如果您不需要检查数据库中的 ID,只需创建项目列表并在每次检查它是否已经存在时添加它们。在此之后使用 AddRange(items) 和 SaveChanges。
【解决方案2】:

最好在一个查询和事务中做到这一点。

await using var transaction = await context.Database.BeginTransactionAsync();

var items = elements.Select(element => new Item
{
   Id = element.Id,
   ...
}).ToList();

// Ids to test
var toTest = items.Select(item => item.Id).ToList();

// select Ids that are already in database
var existing = await context.Items
   .Where(item => toTest.Contains(item.Id))
   .Select(item => item.Id)
   .ToListAsync();

var hashSet = new HashSet<int>(existing);

foreach(var element in items.Where(item => !hashSet.Contains(existing)))
{
    context.Items.Add(item);
}

await context.SaveChangesAsync();

await transaction.CommitAsync();

不是理想的解决方案。但这就是 EF Core 的设计方式。

【讨论】:

    【解决方案3】:

    您正在执行的查询效率低下,因为您循环查询而不是制作单个查询,并且它没有考虑对象相等性。

    var itemIds = elements.Select(x => x.Id).ToArray();
    var existingItemIds = await context.Items
            .Where(x => itemIds.Contains(x.Id))
            .Select(x => x.Id)
            .ToArrayAsync();
    
    var missingItemIds = itemIds.Except(existingItemIds).ToArray();
    
    foreach(var itemId in missingItemIds)
    {
       context.Items.Add(new Item {Id = itemId });
    }
    
    await context.SaveChangesAsync();
    

    而不是每次进行单个查询时都循环以获取确实存在的 itemId,然后将它们过滤掉。

    注意:我觉得奇怪的是该项目已经有一个 Id。通常它是创建 ID 的数据库。您确定不想只为 Id 为 0 的项目创建项目吗?

    【讨论】:

      猜你喜欢
      • 2018-08-28
      • 2023-03-13
      • 2021-04-11
      • 2020-03-16
      • 2011-04-27
      • 1970-01-01
      • 2015-10-08
      • 2022-10-21
      • 1970-01-01
      相关资源
      最近更新 更多