忽略具有Entity Framework的重复键插入

我正在使用ASP.NET MVC4和Entity Framework Code First。 我有一个名为“users”的表,主键为“UserId”。 该表可能有200,000多个条目。

我需要插入另外50个用户。 我可能会这样做

foreach(User user in NewUsers){ context.Add(user); } dbcontext.SaveChanges(); 

问题是,这些新用户中的一个或多个可能已经存在于DB中。 如果我添加它们然后尝试保存,则会抛出错误,并且不会添加任何有效的错误。 我可以修改代码来执行此操作:

 foreach(User user in NewUsers){ if(dbcontext.Users.FirstOrDefault(u => u.UserId) == null) { dbcontext.Users.Add(user); } } dbcontext.SaveChanges(); 

哪个会奏效。 问题是,它必须在200,000+条目表上运行50次查询。 所以我的问题是,插入这些用户的效率最高的方法是什么, 忽略任何重复?

你可以这样做:

 var newUserIDs = NewUsers.Select(u => u.UserId).Distinct().ToArray(); var usersInDb = dbcontext.Users.Where(u => newUserIDs.Contains(u.UserId)) .Select(u => u.UserId).ToArray(); var usersNotInDb = NewUsers.Where(u => !usersInDb.Contains(u.UserId)); foreach(User user in usersNotInDb){ context.Add(user); } dbcontext.SaveChanges(); 

这将在您的数据库中执行单个查询以查找已存在的用户,然后将其从NewUsers集中过滤掉。

您可以使用一个查询过滤掉现有用户

 foreach(User user in NewUsers.Where(us => !dbcontext.Users.Any(u => u.userId == us.userId))) { dbcontext.Users.Add(user); } dbcontext.SaveChanges(); 

编辑:

正如评论中所指出的,上面的提议将导致对NewUsers集合中的每个元素进行sql调用。 我可以使用SQL Server Profiler确认。

分析的一个有趣的结果是EF为每个项生成的有点奇怪的sql(模型名称与OP中的不同,但查询是相同的):

 exec sp_executesql N'SELECT CASE WHEN ( EXISTS (SELECT 1 AS [C1] FROM [dbo].[EventGroup] AS [Extent1] WHERE [Extent1].[EventGroupID] = @p__linq__0 )) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 1 AS [C1] FROM [dbo].[EventGroup] AS [Extent2] WHERE [Extent2].[EventGroupID] = @p__linq__0 )) THEN cast(0 as bit) END AS [C1] FROM ( SELECT 1 AS X ) AS [SingleRowTable1]',N'@p__linq__0 int',@p__linq__0=10 

相当简单的代码来完成一个简单的单行程序。

我的观点是,编写漂亮且可读的声明性代码并让编译器和优化器完成脏工作是一种很好的态度。 这是这种风格的结果令人惊讶并且你必须变脏的情况之一。

由于这是您的主键,因此您的选择有限。 如果这不是您的主键,并且只是一个唯一索引,假定SQL Server,您可以设置唯一键以忽略重复项。

我可能建议的是简单地将一个try / catch包装在Add周围,如果exception是重复的键错误则吃掉exception。

您可能还会看到您的对象是否支持AddOrUpdate()方法。 我知道Code First实现支持这一点。 我相信在这种情况下,如果行存在,它将添加新的或更新。 但是,这可能仍然需要访问数据库以查看用户是否已存在以便知道是否进行添加或更新。 而且,在某些情况下,您可能不想实际执行更新。

我想如果是我,我会去Try / Catch路线。

以下扩展方法将允许您插入任何类型的记录,同时忽略重复:

  public static void AddRangeIgnore(this DbSet dbSet, IEnumerable entities) { var entitiesList = entities.ToList(); var firstEntity = entitiesList.FirstOrDefault(); if (firstEntity == null || !firstEntity.HasKey() || firstEntity.HasIdentityKey()) { dbSet.AddRange(entitiesList); return; } var uniqueEntities = new List(); using (var dbContext = _dataService.CreateDbContext()) { var uniqueDbSet = dbContext.Set(entitiesList.First().GetType()); foreach (object entity in entitiesList) { var keyValues = entity.GetKeyValues(); var existingEntity = uniqueDbSet.Find(keyValues); if (existingEntity == null) { uniqueEntities.Add(entity); uniqueDbSet.Attach(entity); } } } dbSet.AddRange(uniqueEntities); } public static object[] GetKeyValues(this object entity) { using (var dbContext = _dataService.CreateDbContext()) { var entityType = entity.GetType(); dbContext.Set(entityType).Attach(entity); var objectStateEntry = ((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity); var value = objectStateEntry.EntityKey .EntityKeyValues .Select(kv => kv.Value) .ToArray(); return value; } } public static bool HasKey(this object entity) { using (var dbContext = _dataService.CreateDbContext()) { var entityType = entity.GetType(); dbContext.Set(entityType).Attach(entity); var objectStateEntry = ((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity); return objectStateEntry.EntityKey != null; } } public static bool HasIdentityKey(this object entity) { using (var dbContext = _dataService.CreateDbContext()) { var entityType = entity.GetType(); dbContext.Set(entityType).Attach(entity); var objectStateEntry = ((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity); var keyPropertyName = objectStateEntry.EntityKey .EntityKeyValues .Select(kv => kv.Key) .FirstOrDefault(); if (keyPropertyName == null) { return false; } var keyProperty = entityType.GetProperty(keyPropertyName); var attribute = (DatabaseGeneratedAttribute)Attribute.GetCustomAttribute(keyProperty, typeof(DatabaseGeneratedAttribute)); return attribute != null && attribute.DatabaseGeneratedOption == DatabaseGeneratedOption.Identity; } }