实体框架批量插入引发KeyNotFoundException错误

时间:2015-08-26 11:17:44

标签: c# entity-framework bulkinsert

我正在使用EF6,由于AddRange()方法的速度较慢,我需要使用BulkInsert。所以我通过here为EF6添加了BulkInsert的NuGet包。

添加dll后我收到的第一件事就是这个警告:

  

发现同一依赖的不同版本之间存在冲突   部件。请将“AutoGenerateBindingRedirects”属性设置为   在项目文件中为true。

我创建了ListContact个实体,即需要添加的 contactsToInsert (我的联系人在另一个表中也有外键)。当我尝试运行以下代码时,我收到KeyNotFoundException声称“字典中没有给定密钥”。

using (var db = new Entities(myConnectionString))
{
    db.BulkInsert(contactsToInsert);
    db.SaveChanges();
}

NB。我在BackgroundWorker内运行BulkInsert。这可能是this fix判断问题的原因吗?

堆栈跟踪:

   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at EntityFramework.MappingAPI.Mappers.MapperBase.BindForeignKeys() in c:\dev\EntityFramework.MappingAPI\trunk\src\EntityFramework.MappingAPI\Mappers\MapperBase.cs:line 603
   at EntityFramework.MappingAPI.Mappings.DbMapping..ctor(DbContext context) in c:\dev\EntityFramework.MappingAPI\trunk\src\EntityFramework.MappingAPI\Mappings\DbMapping.cs:line 101
   at EntityFramework.MappingAPI.EfMap.Get(DbContext context) in c:\dev\EntityFramework.MappingAPI\trunk\src\EntityFramework.MappingAPI\EfMap.cs:line 60
   at EntityFramework.MappingAPI.Extensions.MappingApiExtensions.Db(DbContext ctx, Type type) in c:\dev\EntityFramework.MappingAPI\trunk\src\EntityFramework.MappingAPI\Extensions\MappingApiExtensions.cs:line 51
   at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
   at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector)
   at EntityFramework.BulkInsert.Helpers.MappedDataReader`1..ctor(IEnumerable`1 enumerable, IEfBulkInsertProvider provider) in c:\dev\EntityFramework.BulkInsert\dev\Src\EntityFramework.BulkInsert\Helpers\MappedDataReader.cs:line 58
   at EntityFramework.BulkInsert.Providers.EfSqlBulkInsertProviderWithMappedDataReader.Run[T](IEnumerable`1 entities, SqlTransaction transaction, BulkInsertOptions options) in c:\dev\EntityFramework.BulkInsert\dev\Src\EntityFramework.BulkInsert\Providers\EfSqlBulkInsertProviderWithMappedDataReader.cs:line 22
   at EntityFramework.BulkInsert.Providers.ProviderBase`2.Run[T](IEnumerable`1 entities, IDbTransaction transaction, BulkInsertOptions options) in c:\dev\EntityFramework.BulkInsert\dev\Src\EntityFramework.BulkInsert\Providers\ProviderBase.cs:line 77
   at EntityFramework.BulkInsert.Providers.ProviderBase`2.Run[T](IEnumerable`1 entities, BulkInsertOptions options) in c:\dev\EntityFramework.BulkInsert\dev\Src\EntityFramework.BulkInsert\Providers\ProviderBase.cs:line 109
   at EntityFramework.BulkInsert.Extensions.BulkInsertExtension.BulkInsert[T](DbContext context, IEnumerable`1 entities, SqlBulkCopyOptions sqlBulkCopyOptions, Nullable`1 batchSize) in c:\dev\EntityFramework.BulkInsert\dev\Src\EntityFramework.BulkInsert\Extensions\BulkInsertExtension.cs:line 95
   at EntityFramework.BulkInsert.Extensions.BulkInsertExtension.BulkInsert[T](DbContext context, IEnumerable`1 entities, Nullable`1 batchSize) in c:\dev\EntityFramework.BulkInsert\dev\Src\EntityFramework.BulkInsert\Extensions\BulkInsertExtension.cs:line 75
   at Prospect.Update.bw_DoWork(Object sender, DoWorkEventArgs e) in c:\Users\pedram.mobedi\Documents\Visual Studio 2013\Projects\Prospect\Update.cs:line 546
   at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)

3 个答案:

答案 0 :(得分:2)

通过修改this blog post中的代码,在"The given key was not present in the dictionary"遇到相同的BulkInsert()错误后,我的Code First Fluent API设置有效。这里唯一的依赖是在上述帖子的ToDataTable()摘要中找到的DataExtensions扩展方法。

相关部分是GetColumnMappings()方法,它将POCO类属性的首选名称(您在代码中指定的名称)作为源列名称(在可枚举中) -turned-datatable)并将其与元数据成员的名称(数据库列名称)配对作为目标列名。

<强> GetColumnMappings():

private IEnumerable<SqlBulkCopyColumnMapping> GetColumnMappings<T>()
{
    var storageMetadata = ((EntityConnection)objectContext.Connection).GetMetadataWorkspace().GetItems(DataSpace.SSpace);
    var entityPropMembers = storageMetadata
        .Where(s => (s.BuiltInTypeKind == BuiltInTypeKind.EntityType))
        .Select(s => (EntityType)s)
        .Where(p => p.Name == typeof(T).Name)
        .Select(p => (IEnumerable<EdmMember>)(p.MetadataProperties["Members"].Value))
        .First();

    var sourceColumns = entityPropMembers.Select(m => (string)m.MetadataProperties["PreferredName"].Value);
    var destinationColumns = entityPropMembers.Select(m => m.Name);

    return Enumerable.Zip(sourceColumns, destinationColumns, (s, d) => new SqlBulkCopyColumnMapping(s, d));
}

完整代码:

// Modified from: https://ruijarimba.wordpress.com/2012/03/25/bulk-insert-dot-net-applications-part1 and
// https://ruijarimba.wordpress.com/2012/03/18/entity-framework-get-mapped-table-name-from-an-entity/

internal class BulkInserter
{
    private readonly ObjectContext objectContext;

    private readonly IDbConnection connection;

    internal BulkInserter(DbContext contextAdapter)
    {
        objectContext = ((IObjectContextAdapter)contextAdapter).ObjectContext;
        connection = contextAdapter.Database.Connection;
    }

    public void Insert<T>(IEnumerable<T> items) where T : class
    {
        EnsureOpenConnection();
        using (var bulkCopy = new SqlBulkCopy((SqlConnection)connection)
        {
            DestinationTableName = GetTableName<T>(),
        })
        {
            foreach (var mapping in GetColumnMappings<T>())
            {
                bulkCopy.ColumnMappings.Add(mapping);
            }

            bulkCopy.WriteToServer(items.ToDataTable());
        }
    }
    private void EnsureOpenConnection()
    {
        if (connection.State == ConnectionState.Closed)
        {
            connection.Open();
        }
    }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
    private string GetTableName<T>() where T : class
    {
        string sql = objectContext.CreateObjectSet<T>().ToTraceString();
        Regex regex = new Regex("FROM (?<table>.*) AS");
        Match match = regex.Match(sql);

        string table = match.Groups["table"].Value;
        return table;
    }

    private IEnumerable<SqlBulkCopyColumnMapping> GetColumnMappings<T>()
    {
        var storageMetadata = ((EntityConnection)objectContext.Connection).GetMetadataWorkspace().GetItems(DataSpace.SSpace);
        var entityPropMembers = storageMetadata
            .Where(s => (s.BuiltInTypeKind == BuiltInTypeKind.EntityType))
            .Select(s => (EntityType)s)
            .Where(p => p.Name == typeof(T).Name)
            .Select(p => (IEnumerable<EdmMember>)(p.MetadataProperties["Members"].Value))
            .First();

        var sourceColumns = entityPropMembers.Select(m => (string)m.MetadataProperties["PreferredName"].Value);
        var destinationColumns = entityPropMembers.Select(m => m.Name);

        return Enumerable.Zip(sourceColumns, destinationColumns, (s, d) => new SqlBulkCopyColumnMapping(s, d));
    }
}

答案 1 :(得分:0)

好的,我有同样的错误无法在网上找到任何答案,所以不得不深入了解所以这就是我的想法:

当你的实体有一个继承而子实体如果没有被定义为DBSet的一部分时,它会标记这样的错误,其次,不确定为什么它期望新的DbContext只包含用于bulkInsertions的相关实体(如果有的话)其他实体出现同样的错误。

所以它的第二个原因所以我必须修复这两个,并像马一样跑!

值得一试,所以试一试

答案 2 :(得分:-5)

&#34; BulkInsert&#34;库非常快,但不够灵活且不受支持。

它不支持所有类型的继承(TPC,TPT),并且在列映射方面存在一些问题。

您遇到的问题是出于以下原因之一。

免责声明:我是该项目的所有者Entity Framework Extensions

这个库是性能的终极库,允许:

  • BulkSaveChanges
  • BulkInsert
  • BulkUpdate
  • BulkDelete
  • BulkMerge

支持所有继承和关联。

示例:

using (var db = new Entities(myConnectionString)
{
    db.BulkInsert(contactsToInsert);
}

// BulkSaveChanges is slower than BulkInsert but way faster then SaveChanges
using (var db = new Entities(myConnectionString))
{
    db.Contacts.AddRange(contactsToInsert);
    db.BulkSaveChanges();
}