在 C# 中通过类型更改递归调用泛型方法
Calling generic method recursively with type change in c#
我使用以下库进行批量插入。 enter link description here 我正在尝试批量插入大量数据及其相关项目解决方案在第一级工作正常但不插入子项。
所以,我有以下通用的 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);
}
}
}
}
我调用 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.Nullable
1 [System.Int32]) "exceeds the" TEntity "type constraint.
更新:
- 这里的想法是插入与子元素相关的属性,因为原始代码只是插入了主实体,而不是子元素。
- 还用更多代码更新了代码,以阐明我在这里要做什么
我假设类型 T
是您所有模型实体扩展的基础 class?包括这个childValueType
?
从错误消息来看,System.Data.Entity.Core.Metadata.Edm.EntityType
不符合 TEntity
的约束条件。
EntityType
是 IEntityType
的 EF Core 实现。尽管您没有在定义 childValueType
的示例中包括在内,但我相信您已经在您打算 childValueType = [IEntityType].ClrType
.
的位置分配了 childValueType = [IEntityType].GetType()
更新,现在您已经添加了更多代码。如我所料,这个; childProperty.GetType();
应该是 childProperty.ClrType
.
谢谢@JeremyLakeman 指出我完全误读了有关 int?
.
的异常消息
Mohamed,您的 InsertAll
方法有 where TEntity : class, T
限制。即使通过 MakeGenericMethod 通过反射调用它,您仍然不能传递任何任意类型 - 您必须传递满足该限制的类型。这就是错误告诉您的内容:您传递了一些不满足 class
或 T
限制的类型。
来自 class EFBatchOperation
的 T
,它显然与 InsertAll
尝试处理的其他实体类型不匹配。例如,它以 EFBatchOperation<House>
开始,原始方法调用是 InsertAll<House>
然后它尝试递归到 InsertAll<Tenant>
- 但失败了,因为 Tenant 可能不符合 class,House
限制.
InsertAll 的 <TEntity>
和 EFBatchOperation 的 <T>
之间的关系真的需要吗?如果没有,就把它去掉,留下where TEntity: class
。如果它必须留在那里进行 public 调用,那么可以尝试编写一个 InsertAll 的私有版本,它可以处理任何类型并且不需要 <T>
并在递归时调用它?
从评论来看,实际问题似乎是如何 批量 插入大量行。这与批量更新(在单个脚本中组合多个语句)不同。
EF Core already batches updates 甚至允许修改默认批量大小。但是,将 42 INSERTs
批处理到单个脚本中仍将执行 42 个完全记录的 INSERT。插入数千行仍然会很慢。
Bulk inserts use the same minimally logged 机制作为 bcp
或 BULK INSERT
尽可能快地插入行。服务器将记录对数据页的更改,而不是记录每一行更改 SQL。它比批处理单个 INSERT 语句快得多。数据不是在内存中缓存记录和更改,而是以流的形式直接发送到服务器。
无论某些图书馆声称如何,都没有批量更新或删除机制。
要执行批量插入,您需要 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);
}
就是这样。
ObjectReader
将默认使用 属性 名称,或传递给 ObjectReader.Create
的名称列表
默认情况下,SqlBulkCopy 不使用事务。 Transaction and Bulk Copy Operations 解释了如何使用事务以及如何配置批处理大小以在需要时分批提交更改。
我使用以下库进行批量插入。 enter link description here 我正在尝试批量插入大量数据及其相关项目解决方案在第一级工作正常但不插入子项。
所以,我有以下通用的 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);
}
}
}
}
我调用 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.IEnumerable
1 [TEntity], System.Data.Common .DbConnection, System.Nullable
1 [System.Int32]) "exceeds the" TEntity "type constraint.
更新:
- 这里的想法是插入与子元素相关的属性,因为原始代码只是插入了主实体,而不是子元素。
- 还用更多代码更新了代码,以阐明我在这里要做什么
我假设类型 T
是您所有模型实体扩展的基础 class?包括这个childValueType
?
从错误消息来看,System.Data.Entity.Core.Metadata.Edm.EntityType
不符合 TEntity
的约束条件。
EntityType
是 IEntityType
的 EF Core 实现。尽管您没有在定义 childValueType
的示例中包括在内,但我相信您已经在您打算 childValueType = [IEntityType].ClrType
.
childValueType = [IEntityType].GetType()
更新,现在您已经添加了更多代码。如我所料,这个; childProperty.GetType();
应该是 childProperty.ClrType
.
谢谢@JeremyLakeman 指出我完全误读了有关 int?
.
Mohamed,您的 InsertAll
方法有 where TEntity : class, T
限制。即使通过 MakeGenericMethod 通过反射调用它,您仍然不能传递任何任意类型 - 您必须传递满足该限制的类型。这就是错误告诉您的内容:您传递了一些不满足 class
或 T
限制的类型。
来自 class EFBatchOperation
的 T
,它显然与 InsertAll
尝试处理的其他实体类型不匹配。例如,它以 EFBatchOperation<House>
开始,原始方法调用是 InsertAll<House>
然后它尝试递归到 InsertAll<Tenant>
- 但失败了,因为 Tenant 可能不符合 class,House
限制.
InsertAll 的 <TEntity>
和 EFBatchOperation 的 <T>
之间的关系真的需要吗?如果没有,就把它去掉,留下where TEntity: class
。如果它必须留在那里进行 public 调用,那么可以尝试编写一个 InsertAll 的私有版本,它可以处理任何类型并且不需要 <T>
并在递归时调用它?
从评论来看,实际问题似乎是如何 批量 插入大量行。这与批量更新(在单个脚本中组合多个语句)不同。
EF Core already batches updates 甚至允许修改默认批量大小。但是,将 42 INSERTs
批处理到单个脚本中仍将执行 42 个完全记录的 INSERT。插入数千行仍然会很慢。
Bulk inserts use the same minimally logged 机制作为 bcp
或 BULK INSERT
尽可能快地插入行。服务器将记录对数据页的更改,而不是记录每一行更改 SQL。它比批处理单个 INSERT 语句快得多。数据不是在内存中缓存记录和更改,而是以流的形式直接发送到服务器。
无论某些图书馆声称如何,都没有批量更新或删除机制。
要执行批量插入,您需要 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);
}
就是这样。
ObjectReader
将默认使用 属性 名称,或传递给 ObjectReader.Create
默认情况下,SqlBulkCopy 不使用事务。 Transaction and Bulk Copy Operations 解释了如何使用事务以及如何配置批处理大小以在需要时分批提交更改。