Mohamed Salah
Mohamed Salah

Reputation: 975

Calling generic method recursively with type change in c#

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.IEnumerable1 [TEntity], System.Data.Common .DbConnection, System.Nullable1 [System.Int32]) "exceeds the" TEntity "type constraint.

Update:

Upvotes: 0

Views: 190

Answers (3)

Panagiotis Kanavos
Panagiotis Kanavos

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

quetzalcoatl
quetzalcoatl

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

Jeremy Lakeman
Jeremy Lakeman

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

Related Questions