C# 实体框架Core1.1-批量插入或更新-InvalidOperationException

C# 实体框架Core1.1-批量插入或更新-InvalidOperationException,c#,entity-framework,entity-framework-core,C#,Entity Framework,Entity Framework Core,我在插入或更新大约950个实体时遇到问题 var coins = JsonConvert.DeserializeObject<List<Currency>>(json); var sw = new Stopwatch(); sw.Start(); using (var ctx = CryptoContext.Get) { var existingCoins = ctx.Coins.ToList(); foreach (var coin in coins)

我在插入或更新大约950个实体时遇到问题

var coins = JsonConvert.DeserializeObject<List<Currency>>(json);
var sw = new Stopwatch();
sw.Start();
using (var ctx = CryptoContext.Get)
{
    var existingCoins = ctx.Coins.ToList();
    foreach (var coin in coins)
    {
        var existing = existingCoins.FirstOrDefault(c => c.CMC_Id == coin.CMC_Id);
        if (existing != null)
        {
            ctx.Entry<Currency>(coin).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
        } else
        {
            ctx.Entry<Currency>(coin).State = Microsoft.EntityFrameworkCore.EntityState.Added;
        }

    }
    ctx.SaveChanges();
    var el = sw.ElapsedMilliseconds;
}
var=JsonConvert.DeserializeObject(json);
var sw=新秒表();
sw.Start();
使用(var ctx=CryptoContext.Get)
{
var existingCoins=ctx.Coins.ToList();
foreach(硬币中的var硬币)
{
var existing=existingCoins.FirstOrDefault(c=>c.CMC\u Id==coin.CMC\u Id);
if(现有!=null)
{
ctx.Entry(coin.State=Microsoft.EntityFrameworkCore.EntityState.Modified;
}否则
{
ctx.Entry(coin.State=Microsoft.EntityFrameworkCore.EntityState.Added;
}
}
ctx.SaveChanges();
var el=sw.ElapsedMilliseconds;
}
该代码在netcoreapp1.1的后台运行,使用SQLite,并检索货币列表。使用FluentScheduler每5分钟执行一次。因为它们不完全是大对象,所以我在内存中进行所有比较,并尝试添加或更新每个对象。我的实体有一个给定ID的数据库,我从中检索的API保证CMC_ID是唯一的

初始插入工作正常。我在第二次“更新”时出错。我相信我正在跟踪多个实体,每个实体的Id都是0

我试着这样做:

我得到的错误是:
“无法跟踪实体类型‘Currency’的实例,因为已跟踪具有相同密钥的该类型的另一个实例。添加新实体时,如果未设置密钥(即,如果为其类型分配了密钥属性的默认值),则将为大多数密钥类型创建唯一的临时密钥值。”。如果要显式设置新实体的键值,请确保它们不会与现有实体或为其他新实体生成的临时值冲突。附加现有实体时,请确保只有一个具有给定键值的实体实例附加到上下文。“


我不确定如何继续更新每一行。

问题此处要求跟踪具有相同密钥的多个实体

当您将
EntityEntry.State
设置为某个值时,EF Core将开始跟踪处于特定状态的实体。由于在代码中,您正在查询数据库以查找现有实体,EF Core将开始跟踪具有给定密钥的实体,因此在设置
EntityEntry.State
时引发上述异常,因为已经存在具有相同密钥的实体正在跟踪

更确切地说,您正在尝试添加或更新。实现该行为有多种方法。哪一个是最好的取决于您添加的是一个没有关系的实体还是一个复杂的图形

最简单的方法是检查实体是否存在,而不是从数据库中跟踪实体。选项是在查询中使用
AsNoTracking
,这样EF就不会开始跟踪它。更优化的方法是从数据库中获取计数。若您正在查询PK属性,则计数将为0(不存在)或1(现有实体)。如果它不存在,则调用
Add
否则
Update

var updatedBlog = new Blog { Id = 1, Title = "Updated" };
var exist = db.Blogs.Count(b => b.Id == updatedBlog.Id) != 0;
if (exist)
{
    db.Update(updatedBlog);
}
else
{
    db.Add(updatedBlog);
}
db.SaveChanges();
由于
Add
Update
方法开始跟踪整个图形,如果您的图形处于一个一致的状态(所有实体都是新的或所有实体都在修改),那么它就可以正常工作

如果您的图有点不一致,则图中每个节点的状态可能不同(例如,更新博客,但它有新帖子)。然后应该对单个实体使用
EntityEntry.State
。这确保状态只应用于给定的实体,而不应用于图中的其他相关实体。尽管您需要对图中的每个节点执行上述检查。另一种选择是使用
Attach
方法在
Unchanged
状态下附加整个图形,然后为单个节点设置状态

如果您有自动生成的键值,那么可能只有在更新时才设置PK值,否则将为CLR默认值。对于没有关系的单个实体,您可以自己进行检查,而不是像上面的代码那样查询数据库并做出决策。对于图形,可以使用

db.ChangeTracker.TrackGraph(updatedBlog, n => n.Entry.State = n.Entry.IsKeySet ? EntityState.Modified : EntityState.Added);
这将根据是否设置PK值来设置每个节点的状态


希望这有帮助:)

我已经解决了这个问题,只需移除所有硬币,然后重新添加它们,在种子Id过高时标记并重置它。这个过程大约需要7秒钟。