Search code examples
c#genericsreflections

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:

  • the idea here is to insert child-related properties because the original code just inserted the main entity, not the child elements.
  • Also Updated the code with more code to clarify what i am trying to do here

Solution

  • 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.