EntityFrameworkCore批量插入(PgSQL篇)

寫在前面

除了批量新增之前Z.EntityFramework.Plus都實現(xiàn)了愧沟,只有批量新增是收費的臭杰,所以這里只介紹如何實現(xiàn)批量新增。

思路詳解

一般的數(shù)據(jù)庫驅動都是帶批量新增功能的谚中,但是EntityFramework支持多種數(shù)據(jù)庫渴杆,要根據(jù)業(yè)務去實現(xiàn)不同的數(shù)據(jù)庫的批量插入,就只要找到數(shù)據(jù)庫驅動的支持文檔就大概可以知道用哪個函數(shù)可以實現(xiàn)批量插入的功能了。現(xiàn)在EfCore中的DbContext是可以拿到DbConnection宪塔,能拿到這個對象那就意味著我可以直接使用Ado.net去干這個事情磁奖,而且Ef還會幫我管理銷毀和連接池等一系列的臟活。

PgSQL批量新增

PgSQL的.NET驅動一般是Npgsql,那么找到它的文檔就應該可以找到對應的批量操作函數(shù)某筐,然后按照文檔大概就可以知道怎么做能搞定這個事情比搭。

Npgsql官網(wǎng)

image.png
核心代碼分析

思路就是先根據(jù)EF給到的EntityType構建列的映射,然后再將數(shù)據(jù)一個一個的塞DataTable中南誊。
最后再使用BeginBinaryImport語法一次性插入

 var pgConnection = dbContext.Database.GetDbConnection() as NpgsqlConnection;
// 這里是構建Copy的SQL語句
 var commandFormat = string.Format("COPY \"{0}\"({1}) FROM STDIN BINARY", tableName, string.Join(",", fields));

 // 主要就是用的這函數(shù) BeginBinaryImport
 using (var writer = pgConnection.BeginBinaryImport(commandFormat))
 {
     foreach (DataRow item in dataTable.Rows)
     {
         await writer.WriteRowAsync(cancellationToken, item.ItemArray); // 異步寫入數(shù)據(jù)庫
     }
     await writer.CompleteAsync(cancellationToken); // 寫完之后一次性提交
 }

  
示例代碼(鄙人框架是ABP的,所以這里是用的ABP框架作為示例代碼)
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.Logging;
using Npgsql;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Guids;
using Volo.Abp.ObjectExtending;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Uow;

namespace EntityFrameworkCore.BulkOperationProvider
{
    [ExposeServices(typeof(IEfCoreBulkOperationProvider))]
    public class PgsqlEfCoreBulkOperationProvider : IEfCoreBulkOperationProvider
    {
        protected ILogger<PgsqlEfCoreBulkOperationProvider> Logger { get; private set; }
        protected IGuidGenerator GuidGenerator { get; private set; }

        public PgsqlEfCoreBulkOperationProvider(ILogger<PgsqlEfCoreBulkOperationProvider> logger, IGuidGenerator guidGenerator)
        {
            Logger = logger;
            GuidGenerator = guidGenerator;
        }

        [UnitOfWork(IsDisabled = true)]
        public async Task<int> BulkInsertAsync<TDbContext, TEntity>(TDbContext dbContext, IEnumerable<TEntity> entities, CancellationToken cancellationToken = default)
            where TEntity : class, IEntity
            where TDbContext : IEfCoreDbContext
        {
            if (entities.Count() < 1) return 0;

            var dbSet = dbContext.Set<TEntity>();
            var entityType = dbSet.EntityType;
            var entityProps = entityType.GetProperties();

            var tableName = dbSet.EntityType.GetTableName();
            var storeObjectIdentifier = StoreObjectIdentifier.Table(tableName, dbSet.EntityType.GetSchema());

            var pgConnection = dbContext.Database.GetDbConnection() as NpgsqlConnection;
            if (pgConnection == null)
                throw new Exception("DbConnecion is not assignable to [NpgsqlConnection]");

            try
            {
                int curIndex = 0;
                int batchSize = 10000;
                int totalCount = entities.Count();

                var dataTable = new DataTable();
                var fields = new List<string>();
                var needHandleExtraProps = typeof(TEntity).IsAssignableTo<IHasExtraProperties>();

                // 構建字段與列頭
                foreach (var item in entityProps)
                {
                    var colName = item.GetColumnName(storeObjectIdentifier);

                    //var s = item.GetTypeMapping().ClrType;

                    var propertyType = item.PropertyInfo.PropertyType;
                    if (needHandleExtraProps && item.Name == nameof(IHasExtraProperties.ExtraProperties))
                        propertyType = typeof(string);

                    var typeMapping = Nullable.GetUnderlyingType(propertyType) ?? propertyType;

                    fields.Add($"\"{colName}\""); // 構建字段
                    dataTable.Columns.Add(new DataColumn(colName, typeMapping)); // 構建DataTable的列
                }

                // 構建導入SQL
                var commandFormat = string.Format("COPY \"{0}\"({1}) FROM STDIN BINARY", tableName, string.Join(",", fields));
                while (curIndex < totalCount)
                {
                    dataTable.Clear(); // 每次搞完一批之后都要清空DataTable,否則會報錯

                    var batchEntities = entities.Skip(curIndex).Take(batchSize);
                    foreach (var item in batchEntities)
                    {
                        CheckAndSetId(item); // 為Guid賦值
                        ArrayList tempList = new ArrayList();
                        foreach (var entityProp in entityProps)
                        {
                            object obj = entityProp.PropertyInfo.GetValue(item, null);
                            if (needHandleExtraProps && entityProp.PropertyInfo.Name == nameof(IHasExtraProperties.ExtraProperties))
                                obj = SerializeExtraObject((item as IHasExtraProperties).ExtraProperties, typeof(TEntity));

                            tempList.Add(obj);
                        }
                        dataTable.LoadDataRow(tempList.ToArray(), true);
                    }

                    using (var writer = pgConnection.BeginBinaryImport(commandFormat))
                    {
                        foreach (DataRow item in dataTable.Rows)
                        {
                            await writer.WriteRowAsync(cancellationToken, item.ItemArray);
                        }
                        await writer.CompleteAsync(cancellationToken);
                    }
                    curIndex += batchSize;
                }

            }
            catch (Exception ex)
            {
                Logger.LogError(ex, $"PG批量插入出錯,Error->{ex.Message}");
                throw ex;
            }

            return entities.Count();
        }

        protected virtual void CheckAndSetId<TEntity>(TEntity entity)
        {
            if (entity is IEntity<Guid> entityWithGuidId)
            {
                TrySetGuidId(entityWithGuidId);
            }
        }

        protected virtual void TrySetGuidId(IEntity<Guid> entity)
        {
            if (entity.Id != default)
            {
                return;
            }

            EntityHelper.TrySetId(
                entity,
                () => GuidGenerator.Create(),
                true
            );
        }

        protected virtual string SerializeExtraObject(ExtraPropertyDictionary extraProperties, Type entityType)
        {
            var copyDictionary = new Dictionary<string, object>(extraProperties);

            if (entityType != null)
            {
                var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType);
                if (objectExtension != null)
                {
                    foreach (var property in objectExtension.GetProperties())
                    {
                        if (property.IsMappedToFieldForEfCore())
                        {
                            copyDictionary.Remove(property.Name);
                        }
                    }
                }
            }

            return JsonSerializer.Serialize(copyDictionary);
        }
    }
}


?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末身诺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子抄囚,更是在濱河造成了極大的恐慌霉赡,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幔托,死亡現(xiàn)場離奇詭異穴亏,居然都是意外死亡,警方通過查閱死者的電腦和手機重挑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門嗓化,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谬哀,你說我怎么就攤上這事刺覆。” “怎么了玻粪?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵隅津,是天一觀的道長诬垂。 經(jīng)常有香客問我,道長伦仍,這世上最難降的妖魔是什么结窘? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮充蓝,結果婚禮上隧枫,老公的妹妹穿的比我還像新娘。我一直安慰自己谓苟,他們只是感情好官脓,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涝焙,像睡著了一般卑笨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仑撞,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天赤兴,我揣著相機與錄音,去河邊找鬼隧哮。 笑死桶良,一個胖子當著我的面吹牛,可吹牛的內容都是我干的沮翔。 我是一名探鬼主播陨帆,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼采蚀!你這毒婦竟也來了疲牵?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤搏存,失蹤者是張志新(化名)和其女友劉穎瑰步,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體璧眠,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡缩焦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了责静。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袁滥。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖灾螃,靈堂內的尸體忽然破棺而出题翻,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布嵌赠,位于F島的核電站塑荒,受9級特大地震影響,放射性物質發(fā)生泄漏姜挺。R本人自食惡果不足惜齿税,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望炊豪。 院中可真熱鬧凌箕,春花似錦、人聲如沸词渤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缺虐。三九已至芜壁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間高氮,已是汗流浹背沿盅。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纫溃,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓韧掩,卻偏偏與公主長得像紊浩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疗锐,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容