Рекурсивный вызов универсального метода с изменением типа в c#

#c# #generics #reflections

Вопрос:

Я использую следующую библиотеку для выполнения массовой вставки. введите описание ссылки здесь я пытаюсь массово вставить огромное количество данных со связанными с ними элементами.решение отлично работает с первым уровнем, но не вставляет дочерние элементы.

Итак, у меня есть следующий универсальный класс

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

и я получил следующую ошибку

Общие аргументы [0], «System.Data.Entity.Core.Metadata.Edm.EntityType» для «EntityFramework.Служебные программы.BatchOperationResult insertAll [TEntity] (Система.Коллекции.Общий.IEnumerable 1 [TEntity], System.Data.Common .DbConnection, System.Nullable 1 [System.Int32]) «превышает ограничение типа» TEntity».

Обновить:

  • идея здесь состоит в том, чтобы вставить дочерние свойства, потому что исходный код просто вставил основную сущность, а не дочерние элементы.
  • Также обновил код с дополнительным кодом, чтобы уточнить, что я пытаюсь здесь сделать

Комментарии:

1. прочитайте сообщение об исключении. Ваш childValueType становится int? и не проходит общее ограничение

2. System.Data.Entity.Core.Metadata.Edm.EntityType ? Значит , ты childValueType ан IEntityType , а не а Type ? Тогда я думаю, что вам нужно childValueType.ClrType .

3. @JeremyLakeman Я думаю, что вы правы, я пытался преобразовать тип из значения, но я не могу

4. В любом случае, что ты пытаешься написать? Можете ли вы заменить все это context.ChangeTracker.TrackGraph(...) чем-нибудь другим ?

5. @MohamedSalah вы не можете выполнять массовую вставку в несколько таблиц — предполагая bulk insert , что вы на самом деле имеете в виду использование механизма массовой вставки. Сначала вы должны загрузить родительскую таблицу, а затем все связанные таблицы. На самом деле, для ускорения процесса обычно отключают индексы и ограничения внешнего ключа во время массовых операций. Также часто используется массовая вставка в промежуточные таблицы, а затем либо обновление целевой таблицы, либо даже переключение разделов для замены фактических промежуточных данных

Ответ №1:

Я предполагаю, что этот тип T -это какой-то базовый класс, который расширяют все ваши сущности модели? Включая это childValueType ?

Из сообщения об ошибке, System.Data.Entity.Core.Metadata.Edm.EntityType не подпадает под действие ограничений на TEntity .

EntityType является основной реализацией EF IEntityType . Хотя вы не указали в своем примере, где childValueType определено , я считаю, что вы назначили childValueType = [IEntityType].GetType() , где вы намеревались childValueType = [IEntityType].ClrType .

Обновите, теперь, когда вы добавили больше кода. Как я и предполагал, так и childProperty.GetType(); должно быть childProperty.ClrType .

Комментарии:

1. Похоже, вы правы, тип childValueType отличается от ожидаемого, я попытаюсь преобразовать его в нужный тип. Я обновил сам вопрос, добавив больше кода для лучшего понимания дела

2. Джереми, спасибо, что указал на это int? !

Ответ №2:

Спасибо, @JeremyLakeman за указание на то, что я совершенно неправильно истолковал сообщение об исключении int? .


Мохаммед, у твоего InsertAll метода есть where TEntity : class, T ограничения. Даже при вызове его через отражение через MakeGenericMethod вы все равно не можете передать какой — либо произвольный тип-вы должны передать тип, который удовлетворяет этому ограничению. Вот что говорит вам ошибка: вы передали какой-то тип , который не удовлетворяет ни class тому, ни T другому ограничению.

Что T это происходит из класса EFBatchOperation , и оно, по-видимому, не соответствует другому типу сущности, который InsertAll пытается обработать. Например, он начинается с EFBatchOperation<House> исходного вызова метода InsertAll<House> , а затем пытается выполнить рекурсию в InsertAll<Tenant> — и терпит неудачу, так как арендатор, вероятно, не соответствует class,House ограничению.

Действительно ли нужна такая связь между <TEntity> insertAll и <T> EFBatchOperation? Если нет, просто уберите его и уходите where TEntity: class . Если он должен оставаться там для публичных вызовов, то, может быть, попробуйте написать частную версию insertAll, которая может обрабатывать любой тип и которая не требует <T> и вызывает ее вместо этого при рекурсии?

Ответ №3:

Из комментариев видно, что реальная проблема заключается в том, как массово вставлять много строк. Это не то же самое, что пакетные обновления (объединение нескольких операторов в одном сценарии).

EF Core уже обновляет пакеты и даже позволяет изменять размер пакета по умолчанию. Однако объединение 42 INSERTs в один скрипт все равно приведет к выполнению 42 полностью зарегистрированных вставок. Вставка тысяч строк все равно будет медленной.

Массовые вставки используют тот же механизм минимальной регистрации bcp , что и или BULK INSERT для вставки строк как можно быстрее. Вместо регистрации каждого изменения строки SQL Server будет регистрировать изменения на страницах данных. Это намного быстрее, чем пакетная обработка отдельных операторов ВСТАВКИ. Вместо кэширования записей и изменений в памяти данные отправляются непосредственно на сервер в потоке.

Нет механизма массового обновления или удаления, независимо от того, что утверждают некоторые библиотеки.

Для выполнения массовых вставок вам понадобится SqlBulkCopy. Этот класс принимает либо объект DataTable, либо объект IDataReader. Вы можете создать IDataReader оболочку поверх любой IEnumerable<T> , используя ObjectReader FastMember :

 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 не использует транзакцию. Операции транзакции и массового копирования объясняют, как использовать транзакцию и как настроить размер пакета для внесения изменений в пакеты, если это необходимо.