31

我首先使用带有实体框架代码的 ASP.NET MVC4。我有一个名为“users”的表,主键为“UserId”。该表可能有 200,000 多个条目。

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

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

问题是,这些新用户中的一个或多个可能已经存在于数据库中。如果我添加它们然后尝试保存,则会引发错误,并且不会添加任何有效的错误。我可以修改代码来做到这一点:

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

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

4

5 回答 5

15

你可以这样做:

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集合中过滤掉。

于 2013-08-07T20:48:48.637 回答
3

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

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

相当不错的一段代码来完成一个简单的单线工作。

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

于 2013-08-07T20:52:04.827 回答
3

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

我可能建议的是简单地在 Add 周围包装一个 try/catch 并在异常是重复键错误时吃掉异常。

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

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

于 2013-08-07T20:48:26.533 回答
0

您可以过滤掉失败并继续尝试,直到它成功或者您得到另一种异常

public partial class YourEntities: DbContext
{
    public override int SaveChanges()
    {
        var isSaved = false;
        do
        {
            try
            {
                return base.SaveChanges();
            }
            catch (DbUpdateException ex)
            {
                var entries = ex.Entries;
                foreach (var entry in entries)
                {
                    // change state to remove it from context 
                    entry.State = EntityState.Detached;
                }
            }
        }
        while (!isSaved);

        return null;    // never gets here
    }
}

您可能需要添加一些额外的逻辑来避免无限循环。

于 2020-05-31T15:07:54.800 回答
-1

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

 public static void AddRangeIgnore(this DbSet dbSet, IEnumerable<object> entities)
    {
        var entitiesList = entities.ToList();
        var firstEntity = entitiesList.FirstOrDefault();

        if (firstEntity == null || !firstEntity.HasKey() || firstEntity.HasIdentityKey())
        {
            dbSet.AddRange(entitiesList);
            return;
        }

        var uniqueEntities = new List<object>();

        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;
        }
    }
于 2017-06-17T09:07:55.343 回答