web-dev-qa-db-fra.com

Méthode la plus rapide d'insertion dans Entity Framework

Je recherche le moyen le plus rapide de s’insérer dans Entity Framework.

Je pose la question à cause du scénario dans lequel vous avez un TransactionScope actif et où l’insertion est énorme (plus de 4000). Elle peut potentiellement durer plus de 10 minutes (délai d'attente par défaut des transactions), ce qui entraînera une transaction incomplète.

582
Bongo Sharp

À votre remarque dans les commentaires à votre question:

"... SavingChanges ( pour chaque Enregistrement ) ..."

C'est la pire chose que vous puissiez faire! L'appel de SaveChanges() pour chaque enregistrement ralentit considérablement les insertions en bloc. Je voudrais faire quelques tests simples qui amélioreront très probablement les performances:

  • Appelez SaveChanges() une fois après TOUS les enregistrements.
  • Appelez SaveChanges() après par exemple 100 enregistrements.
  • Appelez SaveChanges() après par exemple 100 enregistrements, supprimez le contexte et créez-en un.
  • Désactiver la détection de changement

Pour les inserts en vrac, je travaille et expérimente un motif comme celui-ci:

using (TransactionScope scope = new TransactionScope())
{
    MyDbContext context = null;
    try
    {
        context = new MyDbContext();
        context.Configuration.AutoDetectChangesEnabled = false;

        int count = 0;            
        foreach (var entityToInsert in someCollectionOfEntitiesToInsert)
        {
            ++count;
            context = AddToContext(context, entityToInsert, count, 100, true);
        }

        context.SaveChanges();
    }
    finally
    {
        if (context != null)
            context.Dispose();
    }

    scope.Complete();
}

private MyDbContext AddToContext(MyDbContext context,
    Entity entity, int count, int commitCount, bool recreateContext)
{
    context.Set<Entity>().Add(entity);

    if (count % commitCount == 0)
    {
        context.SaveChanges();
        if (recreateContext)
        {
            context.Dispose();
            context = new MyDbContext();
            context.Configuration.AutoDetectChangesEnabled = false;
        }
    }

    return context;
}

J'ai un programme de test qui insère 560.000 entités (9 propriétés scalaires, pas de propriétés de navigation) dans la base de données. Avec ce code, cela fonctionne en moins de 3 minutes.

Pour l'exécution, il est important d'appeler SaveChanges() après "plusieurs" enregistrements ("plusieurs" autour de 100 ou 1000). Cela améliore également les performances de disposer du contexte après SaveChanges et d'en créer un nouveau. Cela efface le contexte de toutes les entités, SaveChanges ne le fait pas, les entités sont toujours attachées au contexte dans l'état Unchanged. C'est la taille croissante des entités attachées dans le contexte qui ralentit l'insertion étape par étape. Donc, il est utile de le nettoyer après un certain temps.

Voici quelques mesures pour mes 560 000 entités:

  • commitCount = 1, recreateContext = false: Plusieurs heures (C'est votre procédure actuelle)
  • commitCount = 100, recreateContext = false: plus de 20 minutes
  • commitCount = 1000, recreateContext = false: 242 sec
  • commitCount = 10000, recreateContext = false: 202 sec
  • commitCount = 100000, recreateContext = false: 199 sec
  • commitCount = 1000000, recreateContext = false: exception de mémoire insuffisante
  • commitCount = 1, recreateContext = true: plus de 10 minutes
  • commitCount = 10, recreateContext = true: 241 sec
  • commitCount = 100, recreateContext = true: 164 sec
  • commitCount = 1000, recreateContext = true: 191 s

Le comportement dans le premier test ci-dessus est que la performance est très non linéaire et diminue extrêmement avec le temps. ("Many hours" est une estimation, je n'ai jamais terminé ce test, je me suis arrêté après 50 minutes à 50 000 entités.) Ce comportement non linéaire n'est pas aussi significatif dans tous les autres tests.

886
Slauma

Cette combinaison augmente assez bien la vitesse.

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
167
arkhivania

Le moyen le plus rapide serait d’utiliser bulk insert extension , que j’ai développé.

Il utilise SqlBulkCopy et un datareader personnalisé pour obtenir des performances maximales. En conséquence, il est 20 fois plus rapide que l’utilisation d’insert standard ou AddRange EntityFramework.BulkInsert vs EF AddRange

l'utilisation est extrêmement simple

context.BulkInsert(hugeAmountOfEntities);
98
maxlego

Vous devriez envisager d'utiliser le System.Data.SqlClient.SqlBulkCopy pour cela. Voici la documentation , et bien sûr, il existe de nombreux tutoriels en ligne.

Désolé, je sais que vous cherchiez une solution simple pour que EF fasse ce que vous voulez, mais les opérations en bloc ne sont pas vraiment ce à quoi les ORM sont destinés.

72
Adam Rackis

Je suis d'accord avec Adam Rackis. SqlBulkCopy est le moyen le plus rapide de transférer des enregistrements en masse d'une source de données à une autre. J'ai utilisé cela pour copier des disques de 20K et cela a pris moins de 3 secondes. Regardez l'exemple ci-dessous.

public static void InsertIntoMembers(DataTable dataTable)
{           
    using (var connection = new SqlConnection(@"data source=;persist security info=True;user id=;password=;initial catalog=;MultipleActiveResultSets=True;App=EntityFramework"))
    {
        SqlTransaction transaction = null;
        connection.Open();
        try
        {
            transaction = connection.BeginTransaction();
            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = "Members";
                sqlBulkCopy.ColumnMappings.Add("Firstname", "Firstname");
                sqlBulkCopy.ColumnMappings.Add("Lastname", "Lastname");
                sqlBulkCopy.ColumnMappings.Add("DOB", "DOB");
                sqlBulkCopy.ColumnMappings.Add("Gender", "Gender");
                sqlBulkCopy.ColumnMappings.Add("Email", "Email");

                sqlBulkCopy.ColumnMappings.Add("Address1", "Address1");
                sqlBulkCopy.ColumnMappings.Add("Address2", "Address2");
                sqlBulkCopy.ColumnMappings.Add("Address3", "Address3");
                sqlBulkCopy.ColumnMappings.Add("Address4", "Address4");
                sqlBulkCopy.ColumnMappings.Add("Postcode", "Postcode");

                sqlBulkCopy.ColumnMappings.Add("MobileNumber", "MobileNumber");
                sqlBulkCopy.ColumnMappings.Add("TelephoneNumber", "TelephoneNumber");

                sqlBulkCopy.ColumnMappings.Add("Deleted", "Deleted");

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction.Rollback();
        }

    }
}
47
Irfons

J'ai étudié la réponse de Slauma (ce qui est génial, merci pour son idée homme), et j'ai réduit la taille du lot jusqu'à atteindre la vitesse optimale. En regardant les résultats de Slauma:

  • commitCount = 1, recreateContext = true: plus de 10 minutes
  • commitCount = 10, recreateContext = true: 241 secondes 
  • commitCount = 100, recreateContext = true: 164 secondes
  • commitCount = 1000, recreateContext = true: 191 secondes

Il est visible qu'il y a une augmentation de la vitesse lorsque vous vous déplacez de 1 à 10 et de 10 à 100, mais de 100 à 1 000, la vitesse d'insertion redescend.

Je me suis donc concentré sur ce qui se passe lorsque vous réduisez la taille d'un lot à une valeur comprise entre 10 et 100, et voici mes résultats (j'utilise un contenu de ligne différent, mes temps sont donc d'une valeur différente):

Quantity    | Batch size    | Interval
1000    1   3
10000   1   34
100000  1   368

1000    5   1
10000   5   12
100000  5   133

1000    10  1
10000   10  11
100000  10  101

1000    20  1
10000   20  9
100000  20  92

1000    27  0
10000   27  9
100000  27  92

1000    30  0
10000   30  9
100000  30  92

1000    35  1
10000   35  9
100000  35  94

1000    50  1
10000   50  10
100000  50  106

1000    100 1
10000   100 14
100000  100 141

Sur la base de mes résultats, l’optimum réel est proche de 30 pour la taille du lot. C'est moins que 10 et 100. Le problème, c'est que je ne sais pas pourquoi 30 est optimal, je n'aurais pas pu trouver d'explication logique à cela.

18
Admir Tuzović

Je recommanderais cet article sur la façon de réaliser des insertions en bloc en utilisant EF.

Entity Framework et INSERTs à encombrement réduit

Il explore ces domaines et compare les performances:

  1. EF par défaut (57 minutes pour compléter l’ajout de 30 000 enregistrements)
  2. Remplacement par du code ADO.NET (25 secondes pour ces mêmes 30 000)
  3. Flou de contexte - Conservez le graphique de contexte actif petit en utilisant un nouveau contexte pour chaque unité de travail (les mêmes 30 000 insertions prennent 33 secondes)
  4. Grandes listes - Désactivez AutoDetectChangesEnabled (ramène le temps à environ 20 secondes)
  5. Traitement en lots (jusqu'à 16 secondes)
  6. DbTable.AddRange () - (performance est dans la gamme 12)
16
ShaTin

Comme d'autres personnes l'ont dit, SqlBulkCopy est le moyen de le faire si vous voulez de très bonnes performances d'insertion.

C'est un peu fastidieux à mettre en place, mais il y a des bibliothèques qui peuvent vous aider. Il y en a quelques-uns mais je vais débrancher sans vergogne ma propre bibliothèque cette fois-ci: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

Le seul code dont vous auriez besoin est:

 using (var db = new YourDbContext())
 {
     EFBatchOperation.For(db, db.BlogPosts).InsertAll(list);
 }

Alors, combien est-ce plus rapide? Très difficile à dire car cela dépend de nombreux facteurs, performances de l'ordinateur, réseau, taille de l'objet, etc. Les tests de performance que j'ai effectués suggèrent que 25 000 entités peuvent être insérées à environ 10 secondes de la manière standard sur localhost SI vous optimisez votre configuration EF comme mentionné dans les autres réponses. Avec EFUtilities, cela prend environ 300 ms. Encore plus intéressant, j’ai sauvé environ 3 millions d’entités en moins de 15 secondes avec cette méthode, avec une moyenne d’environ 200 000 entités par seconde.

Le seul problème est bien sûr si vous devez insérer des données répétées. Cela peut être fait efficacement dans SQL Server à l'aide de la méthode ci-dessus, mais vous devez disposer d'une stratégie de génération d'identifiant vous permettant de générer des identifiants dans le code d'application du parent afin de pouvoir définir les clés étrangères. Cela peut être fait en utilisant des GUID ou quelque chose comme la génération d'identifiant HiLo. 

15
Mikael Eliasson

Dispose() context crée des problèmes si les entités que vous Add() s'appuient sur d'autres entités préchargées (propriétés de navigation, par exemple) dans le contexte

J'utilise un concept similaire pour garder mon contexte petit et atteindre les mêmes performances

Mais au lieu de Dispose() le contexte et de recréer, je détache simplement les entités qui déjà SaveChanges()

public void AddAndSave<TEntity>(List<TEntity> entities) where TEntity : class {

const int CommitCount = 1000; //set your own best performance number here
int currentCount = 0;

while (currentCount < entities.Count())
{
    //make sure it don't commit more than the entities you have
    int commitCount = CommitCount;
    if ((entities.Count - currentCount) < commitCount)
        commitCount = entities.Count - currentCount;

    //e.g. Add entities [ i = 0 to 999, 1000 to 1999, ... , n to n+999... ] to conext
    for (int i = currentCount; i < (currentCount + commitCount); i++)        
        _context.Entry(entities[i]).State = System.Data.EntityState.Added;
        //same as calling _context.Set<TEntity>().Add(entities[i]);       

    //commit entities[n to n+999] to database
    _context.SaveChanges();

    //detach all entities in the context that committed to database
    //so it won't overload the context
    for (int i = currentCount; i < (currentCount + commitCount); i++)
        _context.Entry(entities[i]).State = System.Data.EntityState.Detached;

    currentCount += commitCount;
} }

enveloppez-le avec try catch et TrasactionScope() si vous avez besoin, ne les affiche pas ici pour garder le code propre

13
Stephen Ho

Je sais que la question est très ancienne, mais un type ici a mis au point une méthode d’extension permettant d’utiliser des insertions en bloc avec EF. Lors de ma vérification, j’ai découvert que la bibliothèque coûtait 599 $ aujourd’hui (pour un développeur). Peut-être que cela a du sens pour toute la bibliothèque, mais c'est trop pour une simple insertion en bloc.

Voici une méthode d'extension très simple que j'ai faite. J'utilise cela d'abord sur paire avec base de données (ne pas tester avec le code d'abord, mais je pense que cela fonctionne de la même manière). Changez YourEntities avec le nom de votre contexte:

public partial class YourEntities : DbContext
{
    public async Task BulkInsertAllAsync<T>(IEnumerable<T> entities)
    {
        using (var conn = new SqlConnection(Database.Connection.ConnectionString))
        {
            await conn.OpenAsync();

            Type t = typeof(T);

            var bulkCopy = new SqlBulkCopy(conn)
            {
                DestinationTableName = GetTableName(t)
            };

            var table = new DataTable();

            var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));

            foreach (var property in properties)
            {
                Type propertyType = property.PropertyType;
                if (propertyType.IsGenericType &&
                    propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }

                table.Columns.Add(new DataColumn(property.Name, propertyType));
            }

            foreach (var entity in entities)
            {
                table.Rows.Add(
                    properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
            }

            bulkCopy.BulkCopyTimeout = 0;
            await bulkCopy.WriteToServerAsync(table);
        }
    }

    public void BulkInsertAll<T>(IEnumerable<T> entities)
    {
        using (var conn = new SqlConnection(Database.Connection.ConnectionString))
        {
            conn.Open();

            Type t = typeof(T);

            var bulkCopy = new SqlBulkCopy(conn)
            {
                DestinationTableName = GetTableName(t)
            };

            var table = new DataTable();

            var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));

            foreach (var property in properties)
            {
                Type propertyType = property.PropertyType;
                if (propertyType.IsGenericType &&
                    propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }

                table.Columns.Add(new DataColumn(property.Name, propertyType));
            }

            foreach (var entity in entities)
            {
                table.Rows.Add(
                    properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
            }

            bulkCopy.BulkCopyTimeout = 0;
            bulkCopy.WriteToServer(table);
        }
    }

    public string GetTableName(Type type)
    {
        var metadata = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;
        var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));

        var entityType = metadata
                .GetItems<EntityType>(DataSpace.OSpace)
                .Single(e => objectItemCollection.GetClrType(e) == type);

        var entitySet = metadata
            .GetItems<EntityContainer>(DataSpace.CSpace)
            .Single()
            .EntitySets
            .Single(s => s.ElementType.Name == entityType.Name);

        var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace)
                .Single()
                .EntitySetMappings
                .Single(s => s.EntitySet == entitySet);

        var table = mapping
            .EntityTypeMappings.Single()
            .Fragments.Single()
            .StoreEntitySet;

        return (string)table.MetadataProperties["Table"].Value ?? table.Name;
    }
}

Vous pouvez l'utiliser contre n'importe quelle collection héritée de IEnumerable, comme ceci:

await context.BulkInsertAllAsync(items);
6
Guilherme

Essayez d'utiliser une Procédure stockée qui obtiendra un XML des données que vous souhaitez insérer.

5
Maxim

J'ai créé une extension générique de l'exemple de @Slauma ci-dessus;

public static class DataExtensions
{
    public static DbContext AddToContext<T>(this DbContext context, object entity, int count, int commitCount, bool recreateContext, Func<DbContext> contextCreator)
    {
        context.Set(typeof(T)).Add((T)entity);

        if (count % commitCount == 0)
        {
            context.SaveChanges();
            if (recreateContext)
            {
                context.Dispose();
                context = contextCreator.Invoke();
                context.Configuration.AutoDetectChangesEnabled = false;
            }
        }
        return context;
    }
}

Usage:

public void AddEntities(List<YourEntity> entities)
{
    using (var transactionScope = new TransactionScope())
    {
        DbContext context = new YourContext();
        int count = 0;
        foreach (var entity in entities)
        {
            ++count;
            context = context.AddToContext<TenancyNote>(entity, count, 100, true,
                () => new YourContext());
        }
        context.SaveChanges();
        transactionScope.Complete();
    }
}
4
Sgedda

Je cherche le moyen le plus rapide de s'inscrire dans Entity Framework

Certaines bibliothèques tierces prenant en charge l'insertion en bloc sont disponibles:

  • Z.EntityFramework.Extensions (Recommandé _)
  • EFUtilities
  • EntityFramework.BulkInsert

Voir: Bibliothèque d'insertion en bloc d'Entity Framework

Faites attention lorsque vous choisissez une bibliothèque d'insertion en bloc. Seules Entity Framework Extensions prennent en charge tous les types d'associations et d'héritages. C'est le seul qui soit toujours pris en charge.


Disclaimer: Je suis le propriétaire de Entity Framework Extensions

Cette bibliothèque vous permet d'effectuer toutes les opérations en bloc dont vous avez besoin pour vos scénarios:

  • Sauvegarde en vracChangements
  • Insert en vrac
  • Suppression en masse
  • Mise à jour en vrac
  • Fusion en vrac

Exemple

// Easy to use
context.BulkSaveChanges();

// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);

// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);

// Customize Primary Key
context.BulkMerge(customers, operation => {
   operation.ColumnPrimaryKeyExpression = 
        customer => customer.Code;
});
3
Jonathan Magnan

Utilisez SqlBulkCopy:

void BulkInsert(GpsReceiverTrack[] gpsReceiverTracks)
{
    if (gpsReceiverTracks == null)
    {
        throw new ArgumentNullException(nameof(gpsReceiverTracks));
    }

    DataTable dataTable = new DataTable("GpsReceiverTracks");
    dataTable.Columns.Add("ID", typeof(int));
    dataTable.Columns.Add("DownloadedTrackID", typeof(int));
    dataTable.Columns.Add("Time", typeof(TimeSpan));
    dataTable.Columns.Add("Latitude", typeof(double));
    dataTable.Columns.Add("Longitude", typeof(double));
    dataTable.Columns.Add("Altitude", typeof(double));

    for (int i = 0; i < gpsReceiverTracks.Length; i++)
    {
        dataTable.Rows.Add
        (
            new object[]
            {
                    gpsReceiverTracks[i].ID,
                    gpsReceiverTracks[i].DownloadedTrackID,
                    gpsReceiverTracks[i].Time,
                    gpsReceiverTracks[i].Latitude,
                    gpsReceiverTracks[i].Longitude,
                    gpsReceiverTracks[i].Altitude
            }
        );
    }

    string connectionString = (new TeamTrackerEntities()).Database.Connection.ConnectionString;
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        using (var transaction = connection.BeginTransaction())
        {
            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = dataTable.TableName;
                foreach (DataColumn column in dataTable.Columns)
                {
                    sqlBulkCopy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
                }

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
    }

    return;
}
2
Amir Saniyan

L’un des moyens les plus rapides de sauvegarder une listevous devez appliquer le code suivant

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

AutoDetectChangesEnabled = false

Add, AddRange & SaveChanges: Ne détecte pas les modifications.

ValidateOnSaveEnabled = false;

Ne détecte pas le suivi des modifications

Vous devez ajouter une pépite 

Install-Package Z.EntityFramework.Extensions

Maintenant, vous pouvez utiliser le code suivant

var context = new MyContext();

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

context.BulkInsert(list);
context.BulkSaveChanges();
2
Reza Jenabi

Une autre option consiste à utiliser SqlBulkTools disponible auprès de Nuget. Il est très facile à utiliser et dispose de puissantes fonctionnalités. 

Exemple:

var bulk = new BulkOperations();
var books = GetBooks();

using (TransactionScope trans = new TransactionScope())
{
    using (SqlConnection conn = new SqlConnection(ConfigurationManager
    .ConnectionStrings["SqlBulkToolsTest"].ConnectionString))
    {
        bulk.Setup<Book>()
            .ForCollection(books)
            .WithTable("Books") 
            .AddAllColumns()
            .BulkInsert()
            .Commit(conn);
    }

    trans.Complete();
}

Voir la documentation pour plus d'exemples et une utilisation avancée. Disclaimer: Je suis l'auteur de cette bibliothèque et toutes les opinions sont de mon opinion. 

2
Greg R Taylor

Voici une comparaison de performances entre l'utilisation d'Entity Framework et celle de la classe SqlBulkCopy sur un exemple réaliste: Procédure d'insertion en masse d'objets complexes dans la base de données SQL Server

Comme d'autres l'ont déjà souligné, les ORM ne sont pas destinés à être utilisés en vrac. Ils offrent souplesse, séparation des préoccupations et autres avantages, mais les opérations en bloc (à l'exception de la lecture en bloc) n'en font pas partie.

2
Zoran Horvat

Avez-vous déjà essayé d’insérer via un travailleur ou une tâche en arrière-plan?

Dans mon cas, insérant 7760 registres, répartis dans 182 tables différentes avec des relations de clé étrangère (par NavigationProperties).

Sans la tâche, cela prenait 2 minutes et demie . Dans une tâche (Task.Factory.StartNew(...)), cela prenait 15 secondes.

Je ne fais que le SaveChanges() après avoir ajouté toutes les entités au contexte. (pour assurer l'intégrité des données)

1
Rafael A. M. S.

Toutes les solutions écrites ici ne sont d'aucune aide, car lorsque vous effectuez SaveChanges (), les instructions d'insertion sont envoyées une à une à la base de données, c'est ainsi que fonctionne Entity. 

Et si votre voyage en base de données et retour est de 50 ms par exemple, le temps nécessaire à l’insertion est le nombre d’enregistrements x 50 ms.

Vous devez utiliser BulkInsert, voici le lien: https://efbulkinsert.codeplex.com/

J'ai utilisé le temps d'insertion réduit de 5-6 minutes à 10-12 secondes.

1
Aleksa

À ma connaissance, il existe no BulkInsert dans EntityFramework pour augmenter les performances des énormes plaquettes.

Dans ce scénario, vous pouvez utiliser SqlBulkCopy in ADO.net pour résoudre votre problème.

1
anishMarokey

[NOUVELLE SOLUTION POUR POSTGRESQL] .__ Hé, je sais que c’est un article assez ancien, mais j’ai récemment rencontré un problème similaire, mais nous utilisions Postgresql. Je voulais utiliser bulkinsert efficace, ce qui s’est avéré assez difficile. Je n'ai pas trouvé de bibliothèque libre appropriée pour le faire sur cette base de données. J'ai uniquement trouvé cette aide: https://bytefish.de/blog/postgresql_bulk_insert/ , Qui est également disponible sur Nuget. J'ai écrit un petit mappeur, qui mappe automatiquement les propriétés de la manière Entity Framework:

public static PostgreSQLCopyHelper<T> CreateHelper<T>(string schemaName, string tableName)
        {
            var helper = new PostgreSQLCopyHelper<T>("dbo", "\"" + tableName + "\"");
            var properties = typeof(T).GetProperties();
            foreach(var prop in properties)
            {
                var type = prop.PropertyType;
                if (Attribute.IsDefined(prop, typeof(KeyAttribute)) || Attribute.IsDefined(prop, typeof(ForeignKeyAttribute)))
                    continue;
                switch (type)
                {
                    case Type intType when intType == typeof(int) || intType == typeof(int?):
                        {
                            helper = helper.MapInteger("\"" + prop.Name + "\"",  x => (int?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type stringType when stringType == typeof(string):
                        {
                            helper = helper.MapText("\"" + prop.Name + "\"", x => (string)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type dateType when dateType == typeof(DateTime) || dateType == typeof(DateTime?):
                        {
                            helper = helper.MapTimeStamp("\"" + prop.Name + "\"", x => (DateTime?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type decimalType when decimalType == typeof(decimal) || decimalType == typeof(decimal?):
                        {
                            helper = helper.MapMoney("\"" + prop.Name + "\"", x => (decimal?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type doubleType when doubleType == typeof(double) || doubleType == typeof(double?):
                        {
                            helper = helper.MapDouble("\"" + prop.Name + "\"", x => (double?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type floatType when floatType == typeof(float) || floatType == typeof(float?):
                        {
                            helper = helper.MapReal("\"" + prop.Name + "\"", x => (float?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type guidType when guidType == typeof(Guid):
                        {
                            helper = helper.MapUUID("\"" + prop.Name + "\"", x => (Guid)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                }
            }
            return helper;
        }

Je l'utilise de la manière suivante (j'avais l'entité nommée Engagement):

var undertakingHelper = BulkMapper.CreateHelper<Model.Undertaking>("dbo", nameof(Model.Undertaking));
undertakingHelper.SaveAll(transaction.UnderlyingTransaction.Connection as Npgsql.NpgsqlConnection, undertakingsToAdd));

J'ai montré un exemple avec transaction, mais cela peut également être fait avec une connexion normale extraite du contexte. engagementsToAdd est énumérable des enregistrements d'entité normaux, que je veux regrouper dans DB.

Cette solution, à laquelle j’ai eu recours après quelques heures de recherches et d’essais, est comme on pouvait s’y attendre beaucoup plus rapidement et enfin, facile à utiliser et gratuite! Je vous conseille vivement d'utiliser cette solution, non seulement pour les raisons mentionnées ci-dessus, mais également parce que c'est la seule solution pour laquelle je n'ai eu aucun problème avec Postgresql, de nombreuses autres solutions fonctionnent parfaitement, par exemple avec SqlServer.

1
Michał Pilarek

Vous pouvez utiliser Bulk package library. La version Bulk Insert 1.0.0 est utilisée dans les projets ayant Entity Framework> = 6.0.0.

Plus de description peut être trouvée ici- Code source Bulkoperation

1
Mohd Nadeem

SqlBulkCopy est super rapide

Ceci est ma mise en œuvre:

// at some point in my calling code, I will call:
var myDataTable = CreateMyDataTable();
myDataTable.Rows.Add(Guid.NewGuid,tableHeaderId,theName,theValue); // e.g. - need this call for each row to insert

var efConnectionString = ConfigurationManager.ConnectionStrings["MyWebConfigEfConnection"].ConnectionString;
var efConnectionStringBuilder = new EntityConnectionStringBuilder(efConnectionString);
var connectionString = efConnectionStringBuilder.ProviderConnectionString;
BulkInsert(connectionString, myDataTable);

private DataTable CreateMyDataTable()
{
    var myDataTable = new DataTable { TableName = "MyTable"};
// this table has an identity column - don't need to specify that
    myDataTable.Columns.Add("MyTableRecordGuid", typeof(Guid));
    myDataTable.Columns.Add("MyTableHeaderId", typeof(int));
    myDataTable.Columns.Add("ColumnName", typeof(string));
    myDataTable.Columns.Add("ColumnValue", typeof(string));
    return myDataTable;
}

private void BulkInsert(string connectionString, DataTable dataTable)
{
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        SqlTransaction transaction = null;
        try
        {
            transaction = connection.BeginTransaction();

            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = dataTable.TableName;
                foreach (DataColumn column in dataTable.Columns) {
                    sqlBulkCopy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
                }

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction?.Rollback();
            throw;
        }
    }
}
0
Philip Johnson

comme il n’a jamais été mentionné ici, je souhaite recommander EFCore.BulkExtensions ici

context.BulkInsert(entitiesList);                 context.BulkInsertAsync(entitiesList);
context.BulkUpdate(entitiesList);                 context.BulkUpdateAsync(entitiesList);
context.BulkDelete(entitiesList);                 context.BulkDeleteAsync(entitiesList);
context.BulkInsertOrUpdate(entitiesList);         context.BulkInsertOrUpdateAsync(entitiesList);         // Upsert
context.BulkInsertOrUpdateOrDelete(entitiesList); context.BulkInsertOrUpdateOrDeleteAsync(entitiesList); // Sync
context.BulkRead(entitiesList);                   context.BulkReadAsync(entitiesList);
0
Manfred Wippel

Mais, pour plus de (+4 000) insertions, il est recommandé d'utiliser une procédure stockée. attaché le temps écoulé . Je l'ai inséré 11,788 lignes dans 20 "  enter image description here

thats it code

 public void InsertDataBase(MyEntity entity)
    {
        repository.Database.ExecuteSqlCommand("sp_mystored " +
                "@param1, @param2"
                 new SqlParameter("@param1", entity.property1),
                 new SqlParameter("@param2", entity.property2));
    }
0
Marinpietri

Le secret consiste à insérer dans une table de transfert vierge identique. Les inserts s'éclaircissent rapidement. Ensuite, exécutez un single insert à partir de cela dans votre grande table principale. Tronquez ensuite la table de préparation pour le prochain lot.

c'est à dire.

insert into some_staging_table using Entity Framework.

-- Single insert into main table (this could be a tiny stored proc call)
insert into some_main_already_large_table (columns...)
   select (columns...) from some_staging_table
truncate table some_staging_table
0
Simon Hughes

Utilisez une procédure stockée qui prend des données d'entrée sous la forme de XML pour insérer des données.

Depuis votre code c #, insérez des données au format xml.

par exemple, en c #, la syntaxe serait la suivante:

object id_application = db.ExecuteScalar("procSaveApplication", xml)
0
arun tiwari