【问题标题】:Calling generic method recursively with type change in c#在c#中使用类型更改递归调用泛型方法
【发布时间】:2021-11-10 23:23:07
【问题描述】:

我使用以下库进行批量插入。 enter link description here 我正在尝试批量插入大量数据及其相关项,该解决方案在第一级工作正常,但不插入子项。

所以,我有以下泛型类

 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);
                }
            }
    }
   }

我调用 InsertAll 函数如下:

 BatchOperationResult batchOperationResult = EFBatchOperation.For(context, dbSet).InsertAll(collectionOfEntitiesToInsert);

问题出在我想调用当前函数“InsertAll”但在更改函数类型之后。将不同的类型传递给函数。

我尝试使用反射调用该函数,但使用以下代码无法正常工作

   MethodInfo method = typeof(EFBatchOperation<TContext, T>).GetMethod("InsertAll");
   var newMethod = method.MakeGenericMethod(new[] { childValueType });
   newMethod.Invoke(this, new object[] { childValue });

我收到以下错误

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 ])"超出了"TEntity"类型约束。

更新:

  • 这里的想法是插入与子相关的属性,因为原始代码只是插入了主实体,而不是子元素。
  • 还用更多代码更新了代码,以阐明我在这里要做什么

【问题讨论】:

  • 阅读异常信息。您的 childValueType 变为 int? 并且没有通过通用约束
  • System.Data.Entity.Core.Metadata.Edm.EntityType?所以你的childValueTypeIEntityType,而不是Type?那我觉得你需要childValueType.ClrType
  • @JeremyLakeman 我认为你是对的我试图从值转换类型但我不能
  • 你到底想写什么?你能用context.ChangeTracker.TrackGraph(...)替换整个东西吗?
  • @MohamedSalah 您不能批量插入多个表 - 假设 bulk insert 您实际上是指使用批量插入机制。您必须先加载父表,然后再加载任何相关表。实际上,在批量操作期间禁用索引和外键约束以加快处理速度是很常见的。批量插入到暂存表中,然后更新目标表甚至使用分区切换来用暂存数据替换实际数据也很常见

标签: c# generics reflections


【解决方案1】:

我假设 T 类型是您的所有模型实体都扩展的某个基类?包括这个childValueType

根据错误消息,System.Data.Entity.Core.Metadata.Edm.EntityType 不符合 TEntity 的约束条件。

EntityTypeIEntityType 的 EF Core 实现。尽管您没有在示例中包含定义 childValueType 的位置,但我相信您已经分配了 childValueType = [IEntityType].GetType(),您打算在其中指定 childValueType = [IEntityType].ClrType

更新,现在您已经添加了更多代码。正如我所猜测的,这个; childProperty.GetType(); 应该是 childProperty.ClrType

【讨论】:

  • 看来您是正确的 childValueType 的类型与预期的不同,我将尝试将其转换为所需的类型。我用更多代码更新了问题本身,以便更好地理解案例
  • 杰里米,感谢您指出int?
【解决方案2】:

感谢@JeremyLakeman 指出我完全误读了关于int? 的异常消息。


Mohamed,您的 InsertAll 方法有 where TEntity : class, T 限制。即使通过 MakeGenericMethod 通过反射调用它,您仍然不能传递任何任意类型 - 您必须传递满足该限制的类型。这就是错误告诉您的内容:您传递了一些不满足classT 限制的类型。

来自EFBatchOperation 类的T 显然与InsertAll 尝试处理的其他实体类型不匹配。例如,它以EFBatchOperation&lt;House&gt; 开头,原始方法调用是InsertAll&lt;House&gt;,然后尝试递归到InsertAll&lt;Tenant&gt; - 并且失败,因为租户可能不符合class,House 限制。

InsertAll 的&lt;TEntity&gt; 和EFBatchOperation 的&lt;T&gt; 之间的关系真的需要吗?如果没有,只需将其删除并留下where TEntity: class。如果它必须留在那里进行公共调用,那么也许可以尝试编写一个可以处理任何类型且不需要 &lt;T&gt; 的私有版本的 InsertAll 并在递归时调用它?

【讨论】:

    【解决方案3】:

    从 cmets 看来,实际问题是如何批量插入大量行。这与批量更新(在单个脚本中组合多个语句)相同。

    EF Core already batches updates 甚至允许修改默认批量大小。将 42 个INSERTs 批处理到一个脚本中仍然会执行 42 个完全记录的 INSERT。插入数千行仍然会很慢。

    Bulk inserts 使用与bcpBULK INSERT 相同的minimally logged 机制以尽可能快地插入行。 SQL Server 将记录对数据页的更改,而不是记录每一行更改。它比批处理单个 INSERT 语句要快得多。数据不是在内存中缓存记录和更改,而是直接以流的形式发送到服务器。

    无论某些库声称什么,都没有批量更新或删除机制。

    要执行批量插入,您需要SqlBulkCopy。该类接受 DataTable 或 IDataReader。您可以使用 FastMember's ObjectReader 在任何 IEnumerable&lt;T&gt; 上创建 IDataReader 包装器:

    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); 
    }
    

    就是这样。

    ObjectReader 将默认使用属性名称,或传递给ObjectReader.Create 的名称列表

    默认情况下,SqlBulkCopy 不使用事务。 Transaction and Bulk Copy Operations 解释了如何使用事务以及如何配置批量大小以根据需要批量提交更改。

    【讨论】:

      猜你喜欢
      • 2011-08-26
      • 1970-01-01
      • 2017-04-01
      • 1970-01-01
      • 2012-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多