Reputation: 975
I using the following library to do the Bulk insert. enter link description here i am trying to bulk insert a huge amount of data with its related items the solution is working fine with the first level but not inserting the children.
So, I have the following generic class
public class EFBatchOperation<TContext, T> : IEFBatchOperationBase<TContext, T>, IEFBatchOperationFiltered<TContext, T>
where T : class
where TContext : DbContext{
private ObjectContext context;
private DbContext dbContext;
private IDbSet<T> set;
private Expression<Func<T, bool>> predicate;
public EFBatchOperation(TContext context, IDbSet<T> set)
{
this.dbContext = context;
this.context = (context as IObjectContextAdapter).ObjectContext;
this.set = set;
}
public static IEFBatchOperationBase<TContext, T> For<TContext, T>(TContext context, IDbSet<T> set)
where TContext : DbContext
where T : class
{
return new EFBatchOperation<TContext, T>(context, set);
}
public BatchOperationResult InsertAll<TEntity>(IEnumerable<TEntity> items, DbConnection connection = null, int? batchSize = null) where TEntity : class, T
{
// the problem is here I want to call the current function 'InsertAll' but after changing the type of the function. passing a different type to the function. I tried the following but its not working var connectionToUse = connection ?? con.StoreConnection;
var currentType = typeof(TEntity);
var provider = Configuration.Providers.FirstOrDefault(p => p.CanHandle(connectionToUse));
if (provider != null && provider.CanInsert)
{
var mapping = EntityFramework.Utilities.EfMappingFactory.GetMappingsForContext(this.dbContext);
// use of T to get Type Mapping
var typeMapping = mapping.TypeMappings[typeof(T)];
var tableMapping = typeMapping.TableMappings.First();
var properties = tableMapping.PropertyMappings
.Where(p => currentType.IsSubclassOf(p.ForEntityType) || p.ForEntityType == currentType)
.Select(p => new ColumnMapping { NameInDatabase = p.ColumnName, NameOnObject = p.PropertyName }).ToList();
if (tableMapping.TPHConfiguration != null)
{
properties.Add(new ColumnMapping
{
NameInDatabase = tableMapping.TPHConfiguration.ColumnName,
StaticValue = tableMapping.TPHConfiguration.Mappings[typeof(TEntity)]
});
}
provider.InsertItems(items, tableMapping.Schema, tableMapping.TableName, properties, connectionToUse, batchSize);
var objectContext = ((IObjectContextAdapter)this.dbContext).ObjectContext;
var os = objectContext.CreateObjectSet<TEntity>();
var foreignKeyProperties = os.EntitySet.ElementType.NavigationProperties.Where(x => x.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);
Type entityType = typeof(TEntity);
foreach (var foreignKeyProperty in foreignKeyProperties)
{
var childProperty = foreignKeyProperty.ToEndMember.GetEntityType();
foreach (var item in items)
{
var childValue = entityType.GetProperty(foreignKeyProperty.Name).GetValue(item);
Type childValueType = childProperty.GetType();
//MethodInfo method = typeof(EFBatchOperation).GetMethod("InsertAll");
MethodInfo method = typeof(EFBatchOperation<TContext, T>).GetMethod("InsertAll");
var newMethod = method.MakeGenericMethod(new[] { childValueType.DeclaringType });
newMethod.Invoke(this, new object[] { childValue });
// InsertAll<>(childValue, connection, batchSize);
}
}
}
}
I am calling InsertAll function as follows:
BatchOperationResult batchOperationResult = EFBatchOperation.For(context, dbSet).InsertAll(collectionOfEntitiesToInsert);
the problem is here I want to call the current function 'InsertAll' but after changing the type of the function. passing a different type to the function.
I have tried to call the function using reflection but its not working using the following code
MethodInfo method = typeof(EFBatchOperation<TContext, T>).GetMethod("InsertAll");
var newMethod = method.MakeGenericMethod(new[] { childValueType });
newMethod.Invoke(this, new object[] { childValue });
and I got the following error
GenericArguments [0], "System.Data.Entity.Core.Metadata.Edm.EntityType" for "EntityFramework.Utilities.BatchOperationResult InsertAll [TEntity] (System.Collections.Generic.IEnumerable
1 [TEntity], System.Data.Common .DbConnection, System.Nullable
1 [System.Int32]) "exceeds the" TEntity "type constraint.
Update:
Upvotes: 0
Views: 190
Reputation: 131676
From the comments it looks like the actual problem is how to bulk insert a lot of rows. That's not the same as batch updates (combining multiple statements in a single script).
EF Core already batches updates and even allows modifying the default batch size. Batching 42 INSERTs
into a single script will still execute 42 fully logged INSERTs though. Inserting thousands of rows will still be slow.
Bulk inserts use the same minimally logged mechanism as bcp
or BULK INSERT
to insert rows as fast as possible. Instead of logging every row change SQL Server will log the changes to the data pages. It's far faster than batching individual INSERT statements. Instead of caching records and changes in memory, the data is sent directly to the server in a stream.
There's no bulk update or delete mechanism, no matter what some libraries claim.
To perform bulk inserts you need the SqlBulkCopy. That class accepts either a DataTable or an IDataReader. You can create an IDataReader
wrapper over any IEnumerable<T>
using FastMember's ObjectReader :
var data = new List<Customer>();
....
using(var bcp = new SqlBulkCopy(connection))
using(var reader = ObjectReader.Create(data, "Id", "Name", "Description"))
{
bcp.DestinationTableName = "SomeTable";
bcp.WriteToServer(reader);
}
That's it.
ObjectReader
will use the property names by default, or the list of names passed to ObjectReader.Create
By default, SqlBulkCopy doesn't use a transaction. Transaction and Bulk Copy Operations explains how to use a transaction and how to configure the batch size to commit changes in batches if desired.
Upvotes: 0
Reputation: 33556
Thanks, @JeremyLakeman for pointing out that I totally misread the exception message regarding int?
.
Mohamed, your InsertAll
method has where TEntity : class, T
restriction. Even when calling it via reflection via MakeGenericMethod, you still cannot pass any arbitrary type - you have to pass a type that satisfies that restriction. This is what the error tells you: you have passed some type that does not satisfy either class
, or T
restriction.
That the T
that comes from the class EFBatchOperation
, and it apparently does not match the other entity type that InsertAll
tries to process. For example, it starts with EFBatchOperation<House>
, original method call is InsertAll<House>
and it then tries to recurse into InsertAll<Tenant>
- and fails, since Tenant probably does not comply with class,House
restriction.
Is that relation between <TEntity>
of InsertAll, and <T>
of EFBatchOperation really needed? If not, just remove it and leave where TEntity: class
. If it has to stay there for public calls, then maybe try writing a private version of InsertAll that can process any type and that doesn't require <T>
and call it instead when recursing?
Upvotes: 0
Reputation: 11153
I'm assuming that type T
is some base class that all of your model entities extend? Including this childValueType
?
From the error message, System.Data.Entity.Core.Metadata.Edm.EntityType
does not qualify for the constraints on TEntity
.
EntityType
is the EF Core implementation of IEntityType
. Though you have not included in your example where childValueType
is defined, I believe you have assigned childValueType = [IEntityType].GetType()
, where you intended childValueType = [IEntityType].ClrType
.
Update, now that you have added more code. As I guessed, this; childProperty.GetType();
should be childProperty.ClrType
.
Upvotes: 1