婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av

主頁 > 知識庫 > SQL Server 批量插入數據的完美解決方案

SQL Server 批量插入數據的完美解決方案

熱門標簽:武漢AI電銷機器人 地圖標注如何弄全套標 南京電銷外呼系統哪家好 在電子版地圖標注要收費嗎 電銷機器人 深圳 實體店地圖標注怎么標 外呼系統會封嗎 股票配資電銷機器人 萬利達綜合醫院地圖標注點

一、Sql Server插入方案介紹

關于 SqlServer 批量插入的方式,有三種比較常用的插入方式,InsertBatchInsertSqlBulkCopy,下面我們對比以下三種方案的速度

1.普通的Insert插入方法

public static void Insert(IEnumerablePerson> persons)
{
  using (var con = new SqlConnection("Server=.;Database=DemoDataBase;User ID=sa;Password=8888;"))
  {
    con.Open();
    foreach (var person in persons)
    {
      using (var com = new SqlCommand(
        "INSERT INTO dbo.Person(Id,Name,Age,CreateTime,Sex)VALUES(@Id,@Name,@Age,@CreateTime,@Sex)",
        con))
      {
        com.Parameters.AddRange(new[]
        {
          new SqlParameter("@Id", SqlDbType.BigInt) {Value = person.Id},
          new SqlParameter("@Name", SqlDbType.VarChar, 64) {Value = person.Name},
          new SqlParameter("@Age", SqlDbType.Int) {Value = person.Age},
          new SqlParameter("@CreateTime", SqlDbType.DateTime)
            {Value = person.CreateTime ?? (object) DBNull.Value},
          new SqlParameter("@Sex", SqlDbType.Int) {Value = (int)person.Sex},
        });
        com.ExecuteNonQuery();
      }
    }
  }
}

2.拼接BatchInsert插入語句

public static void BatchInsert(Person[] persons)
{
  using (var con = new SqlConnection("Server=.;Database=DemoDataBase;User ID=sa;Password=8888;"))
  {
    con.Open();
    var pageCount = (persons.Length - 1) / 1000 + 1;
    for (int i = 0; i  pageCount; i++)
    {
      var personList = persons.Skip(i * 1000).Take(1000).ToArray();
      var values = personList.Select(p =>
        $"({p.Id},'{p.Name}',{p.Age},{(p.CreateTime.HasValue ? $"'{p.CreateTime:yyyy-MM-dd HH:mm:ss}'" : "NULL")},{(int) p.Sex})");
      var insertSql =
        $"INSERT INTO dbo.Person(Id,Name,Age,CreateTime,Sex)VALUES{string.Join(",", values)}";
      using (var com = new SqlCommand(insertSql, con))
      {
        com.ExecuteNonQuery();
      }
    }
  }
}

3.SqlBulkCopy插入方案

public static void BulkCopy(IEnumerablePerson> persons)
{
  using (var con = new SqlConnection("Server=.;Database=DemoDataBase;User ID=sa;Password=8888;"))
  {
    con.Open();
    var table = new DataTable();
    table.Columns.AddRange(new []
    {
      new DataColumn("Id", typeof(long)), 
      new DataColumn("Name", typeof(string)), 
      new DataColumn("Age", typeof(int)), 
      new DataColumn("CreateTime", typeof(DateTime)), 
      new DataColumn("Sex", typeof(int)), 
    });
    foreach (var p in persons)
    {
      table.Rows.Add(new object[] {p.Id, p.Name, p.Age, p.CreateTime, (int) p.Sex});
    }

    using (var copy = new SqlBulkCopy(con))
    {
      copy.DestinationTableName = "Person";
      copy.WriteToServer(table);
    }
  }
}

3.三種方案速度對比

方案 數量 時間
Insert 1千條 145.4351ms
BatchInsert 1千條 103.9061ms
SqlBulkCopy 1千條 7.021ms
Insert 1萬條 1501.326ms
BatchInsert 1萬條 850.6274ms
SqlBulkCopy 1萬條 30.5129ms
Insert 10萬條 13875.4934ms
BatchInsert 10萬條 8278.9056ms
SqlBulkCopy 10萬條 314.8402ms

兩者插入效率對比,Insert明顯比SqlBulkCopy要慢太多,大概20~40倍性能差距,下面我們將SqlBulkCopy封裝一下,讓批量插入更加方便

二、SqlBulkCopy封裝代碼

1.方法介紹

批量插入擴展方法簽名

方法 方法參數 介紹
BulkCopy 同步的批量插入方法
SqlConnection connection sql server 連接對象
IEnumerableT> source 需要批量插入的數據源
string tableName = null 插入表名稱【為NULL默認為實體名稱】
int bulkCopyTimeout = 30 批量插入超時時間
int batchSize = 0 寫入數據庫一批數量【如果為0代表全部一次性插入】最合適數量【這取決于您的環境,尤其是行數和網絡延遲。就個人而言,我將從BatchSize屬性設置為1000行開始,然后看看其性能如何。如果可行,那么我將使行數加倍(例如增加到2000、4000等),直到性能下降或超時。否則,如果超時發生在1000,那么我將行數減少一半(例如500),直到它起作用為止?!?/td>
SqlBulkCopyOptions options = SqlBulkCopyOptions.Default 批量復制參數
SqlTransaction externalTransaction = null 執行的事務對象
BulkCopyAsync 異步的批量插入方法
SqlConnection connection sql server 連接對象
IEnumerableT> source 需要批量插入的數據源
string tableName = null 插入表名稱【為NULL默認為實體名稱】
int bulkCopyTimeout = 30 批量插入超時時間
int batchSize = 0 寫入數據庫一批數量【如果為0代表全部一次性插入】最合適數量【這取決于您的環境,尤其是行數和網絡延遲。就個人而言,我將從BatchSize屬性設置為1000行開始,然后看看其性能如何。如果可行,那么我將使行數加倍(例如增加到2000、4000等),直到性能下降或超時。否則,如果超時發生在1000,那么我將行數減少一半(例如500),直到它起作用為止?!?/td>
SqlBulkCopyOptions options = SqlBulkCopyOptions.Default 批量復制參數
SqlTransaction externalTransaction = null 執行的事務對象

這個方法主要解決了兩個問題:

  • 免去了手動構建DataTable或者IDataReader接口實現類,手動構建的轉換比較難以維護,如果修改字段就得把這些地方都進行修改,特別是還需要將枚舉類型特殊處理,轉換成他的基礎類型(默認int
  • 不用親自創建SqlBulkCopy對象,和配置數據庫列的映射,和一些屬性的配置

此方案也是在我公司中使用,以滿足公司的批量插入數據的需求,例如第三方的對賬數據此方法使用的是Expression動態生成數據轉換函數,其效率和手寫的原生代碼差不多,和原生手寫代碼相比,多余的轉換損失很小【最大的性能損失都是在值類型拆裝箱上】

此方案和其他網上的方案有些不同的是:不是將List先轉換成DataTable,然后寫入SqlBulkCopy的,而是使用一個實現IDataReader的讀取器包裝List,每往SqlBulkCopy插入一行數據才會轉換一行數據

IDataReader方案和DataTable方案相比優點

效率高:DataTable方案需要先完全轉換后,才能交由SqlBulkCopy寫入數據庫,而IDataReader方案可以邊轉換邊交給SqlBulkCopy寫入數據庫(例如:10萬數據插入速度可提升30%)

占用內存少:DataTable方案需要先完全轉換后,才能交由SqlBulkCopy寫入數據庫,需要占用大量內存,而IDataReader方案可以邊轉換邊交給SqlBulkCopy寫入數據庫,無須占用過多內存

強大:因為是邊寫入邊轉換,而且EnumerableReader傳入的是一個迭代器,可以實現持續插入數據的效果

2.實現原理

① 實體Model與表映射

數據庫表代碼

CREATE TABLE [dbo].[Person](
	[Id] [BIGINT] NOT NULL,
	[Name] [VARCHAR](64) NOT NULL,
	[Age] [INT] NOT NULL,
	[CreateTime] [DATETIME] NULL,
	[Sex] [INT] NOT NULL,
PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

實體類代碼

public class Person
{
  public long Id { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
  public DateTime? CreateTime { get; set; }
  public Gender Sex { get; set; }
}

public enum Gender
{
  Man = 0,
  Woman = 1
}

  • 創建字段映射【如果沒有此字段映射會導致數據填錯位置,如果類型不對還會導致報錯】【因為:沒有此字段映射默認是按照列序號對應插入的】
  • 創建映射使用的SqlBulkCopy類型的ColumnMappings屬性來完成,數據列與數據庫中列的映射
//創建批量插入對象
using (var copy = new SqlBulkCopy(connection, options, externalTransaction))
{
  foreach (var column in ModelToDataTableTModel>.Columns)
  {
    //創建字段映射
    copy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
  }
}

② 實體轉換成數據行

將數據轉換成數據行采用的是:反射+Expression來完成

其中反射是用于獲取編寫Expression所需程序類,屬性等信息

其中Expression是用于生成高效轉換函數其中ModelToDataTableTModel>類型利用了靜態泛型類特性,實現泛型參數的緩存效果

ModelToDataTableTModel>的靜態構造函數中,生成轉換函數,獲取需要轉換的屬性信息,并存入靜態只讀字段中,完成緩存

③ 使用IDataReader插入數據的重載

EnumerableReader是實現了IDataReader接口的讀取類,用于將模型對象,在迭代器中讀取出來,并轉換成數據行,可供SqlBulkCopy讀取

SqlBulkCopy只會調用三個方法:GetOrdinal、ReadGetValue

  • 其中GetOrdinal只會在首行讀取每個列所代表序號【需要填寫:SqlBulkCopy類型的ColumnMappings屬性】
  • 其中Read方法是迭代到下一行,并調用ModelToDataTableTModel>.ToRowData.Invoke()來將模型對象轉換成數據行object[]
  • 其中GetValue方法是獲取當前行指定下標位置的值

3.完整代碼

擴展方法類

 public static class SqlConnectionExtension
  {
    /// summary>
    /// 批量復制
    /// /summary>
    /// typeparam name="TModel">插入的模型對象/typeparam>
    /// param name="source">需要批量插入的數據源/param>
    /// param name="connection">數據庫連接對象/param>
    /// param name="tableName">插入表名稱【為NULL默認為實體名稱】/param>
    /// param name="bulkCopyTimeout">插入超時時間/param>
    /// param name="batchSize">寫入數據庫一批數量【如果為0代表全部一次性插入】最合適數量【這取決于您的環境,尤其是行數和網絡延遲。就個人而言,我將從BatchSize屬性設置為1000行開始,然后看看其性能如何。如果可行,那么我將使行數加倍(例如增加到2000、4000等),直到性能下降或超時。否則,如果超時發生在1000,那么我將行數減少一半(例如500),直到它起作用為止?!?param>
    /// param name="options">批量復制參數/param>
    /// param name="externalTransaction">執行的事務對象/param>
    /// returns>插入數量/returns>
    public static int BulkCopyTModel>(this SqlConnection connection,
      IEnumerableTModel> source,
      string tableName = null,
      int bulkCopyTimeout = 30,
      int batchSize = 0,
      SqlBulkCopyOptions options = SqlBulkCopyOptions.Default,
      SqlTransaction externalTransaction = null)
    {
      //創建讀取器
      using (var reader = new EnumerableReaderTModel>(source))
      {
        //創建批量插入對象
        using (var copy = new SqlBulkCopy(connection, options, externalTransaction))
        {
          //插入的表
          copy.DestinationTableName = tableName ?? typeof(TModel).Name;
          //寫入數據庫一批數量
          copy.BatchSize = batchSize;
          //超時時間
          copy.BulkCopyTimeout = bulkCopyTimeout;
          //創建字段映射【如果沒有此字段映射會導致數據填錯位置,如果類型不對還會導致報錯】【因為:沒有此字段映射默認是按照列序號對應插入的】
          foreach (var column in ModelToDataTableTModel>.Columns)
          {
            //創建字段映射
            copy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
          }
          //將數據批量寫入數據庫
          copy.WriteToServer(reader);
          //返回插入數據數量
          return reader.Depth;
        }
      }
    }

    /// summary>
    /// 批量復制-異步
    /// /summary>
    /// typeparam name="TModel">插入的模型對象/typeparam>
    /// param name="source">需要批量插入的數據源/param>
    /// param name="connection">數據庫連接對象/param>
    /// param name="tableName">插入表名稱【為NULL默認為實體名稱】/param>
    /// param name="bulkCopyTimeout">插入超時時間/param>
    /// param name="batchSize">寫入數據庫一批數量【如果為0代表全部一次性插入】最合適數量【這取決于您的環境,尤其是行數和網絡延遲。就個人而言,我將從BatchSize屬性設置為1000行開始,然后看看其性能如何。如果可行,那么我將使行數加倍(例如增加到2000、4000等),直到性能下降或超時。否則,如果超時發生在1000,那么我將行數減少一半(例如500),直到它起作用為止?!?param>
    /// param name="options">批量復制參數/param>
    /// param name="externalTransaction">執行的事務對象/param>
    /// returns>插入數量/returns>
    public static async Taskint> BulkCopyAsyncTModel>(this SqlConnection connection,
      IEnumerableTModel> source,
      string tableName = null,
      int bulkCopyTimeout = 30,
      int batchSize = 0,
      SqlBulkCopyOptions options = SqlBulkCopyOptions.Default,
      SqlTransaction externalTransaction = null)
    {
      //創建讀取器
      using (var reader = new EnumerableReaderTModel>(source))
      {
        //創建批量插入對象
        using (var copy = new SqlBulkCopy(connection, options, externalTransaction))
        {
          //插入的表
          copy.DestinationTableName = tableName ?? typeof(TModel).Name;
          //寫入數據庫一批數量
          copy.BatchSize = batchSize;
          //超時時間
          copy.BulkCopyTimeout = bulkCopyTimeout;
          //創建字段映射【如果沒有此字段映射會導致數據填錯位置,如果類型不對還會導致報錯】【因為:沒有此字段映射默認是按照列序號對應插入的】
          foreach (var column in ModelToDataTableTModel>.Columns)
          {
            //創建字段映射
            copy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
          }
          //將數據批量寫入數據庫
          await copy.WriteToServerAsync(reader);
          //返回插入數據數量
          return reader.Depth;
        }
      }
    }
  }

封裝的迭代器數據讀取器

 /// summary>
  /// 迭代器數據讀取器
  /// /summary>
  /// typeparam name="TModel">模型類型/typeparam>
  public class EnumerableReaderTModel> : IDataReader
  {
    /// summary>
    /// 實例化迭代器讀取對象
    /// /summary>
    /// param name="source">模型源/param>
    public EnumerableReader(IEnumerableTModel> source)
    {
      _source = source ?? throw new ArgumentNullException(nameof(source));
      _enumerable = source.GetEnumerator();
    }

    private readonly IEnumerableTModel> _source;
    private readonly IEnumeratorTModel> _enumerable;
    private object[] _currentDataRow = Array.Emptyobject>();
    private int _depth;
    private bool _release;

    public void Dispose()
    {
      _release = true;
      _enumerable.Dispose();
    }

    public int GetValues(object[] values)
    {
      if (values == null) throw new ArgumentNullException(nameof(values));
      var length = Math.Min(_currentDataRow.Length, values.Length);
      Array.Copy(_currentDataRow, values, length);
      return length;
    }

    public int GetOrdinal(string name)
    {
      for (int i = 0; i  ModelToDataTableTModel>.Columns.Count; i++)
      {
        if (ModelToDataTableTModel>.Columns[i].ColumnName == name) return i;
      }

      return -1;
    }

    public long GetBytes(int ordinal, long dataIndex, byte[] buffer, int bufferIndex, int length)
    {
      if (dataIndex  0) throw new Exception($"起始下標不能小于0!");
      if (bufferIndex  0) throw new Exception("目標緩沖區起始下標不能小于0!");
      if (length  0) throw new Exception("讀取長度不能小于0!");
      var numArray = (byte[])GetValue(ordinal);
      if (buffer == null) return numArray.Length;
      if (buffer.Length = bufferIndex) throw new Exception("目標緩沖區起始下標不能大于目標緩沖區范圍!");
      var freeLength = Math.Min(numArray.Length - bufferIndex, length);
      if (freeLength = 0) return 0;
      Array.Copy(numArray, dataIndex, buffer, bufferIndex, length);
      return freeLength;
    }

    public long GetChars(int ordinal, long dataIndex, char[] buffer, int bufferIndex, int length)
    {
      if (dataIndex  0) throw new Exception($"起始下標不能小于0!");
      if (bufferIndex  0) throw new Exception("目標緩沖區起始下標不能小于0!");
      if (length  0) throw new Exception("讀取長度不能小于0!");
      var numArray = (char[])GetValue(ordinal);
      if (buffer == null) return numArray.Length;
      if (buffer.Length = bufferIndex) throw new Exception("目標緩沖區起始下標不能大于目標緩沖區范圍!");
      var freeLength = Math.Min(numArray.Length - bufferIndex, length);
      if (freeLength = 0) return 0;
      Array.Copy(numArray, dataIndex, buffer, bufferIndex, length);
      return freeLength;
    }

    public bool IsDBNull(int i)
    {
      var value = GetValue(i);
      return value == null || value is DBNull;
    }
    public bool NextResult()
    {
      //移動到下一個元素
      if (!_enumerable.MoveNext()) return false;
      //行層+1
      Interlocked.Increment(ref _depth);
      //得到數據行
      _currentDataRow = ModelToDataTableTModel>.ToRowData.Invoke(_enumerable.Current);
      return true;
    }

    public byte GetByte(int i) => (byte)GetValue(i);
    public string GetName(int i) => ModelToDataTableTModel>.Columns[i].ColumnName;
    public string GetDataTypeName(int i) => ModelToDataTableTModel>.Columns[i].DataType.Name;
    public Type GetFieldType(int i) => ModelToDataTableTModel>.Columns[i].DataType;
    public object GetValue(int i) => _currentDataRow[i];
    public bool GetBoolean(int i) => (bool)GetValue(i);
    public char GetChar(int i) => (char)GetValue(i);
    public Guid GetGuid(int i) => (Guid)GetValue(i);
    public short GetInt16(int i) => (short)GetValue(i);
    public int GetInt32(int i) => (int)GetValue(i);
    public long GetInt64(int i) => (long)GetValue(i);
    public float GetFloat(int i) => (float)GetValue(i);
    public double GetDouble(int i) => (double)GetValue(i);
    public string GetString(int i) => (string)GetValue(i);
    public decimal GetDecimal(int i) => (decimal)GetValue(i);
    public DateTime GetDateTime(int i) => (DateTime)GetValue(i);
    public IDataReader GetData(int i) => throw new NotSupportedException();
    public int FieldCount => ModelToDataTableTModel>.Columns.Count;
    public object this[int i] => GetValue(i);
    public object this[string name] => GetValue(GetOrdinal(name));
    public void Close() => Dispose();
    public DataTable GetSchemaTable() => ModelToDataTableTModel>.ToDataTable(_source);
    public bool Read() => NextResult();
    public int Depth => _depth;
    public bool IsClosed => _release;
    public int RecordsAffected => 0;
  }

模型對象轉數據行工具類

/// summary>
  /// 對象轉換成DataTable轉換類
  /// /summary>
  /// typeparam name="TModel">泛型類型/typeparam>
  public static class ModelToDataTableTModel>
  {
    static ModelToDataTable()
    {
      //如果需要剔除某些列可以修改這段代碼
      var propertyList = typeof(TModel).GetProperties().Where(w => w.CanRead).ToArray();
      Columns = new ReadOnlyCollectionDataColumn>(propertyList
        .Select(pr => new DataColumn(pr.Name, GetDataType(pr.PropertyType))).ToArray());
      //生成對象轉數據行委托
      ToRowData = BuildToRowDataDelegation(typeof(TModel), propertyList);
    }

    /// summary>
    /// 構建轉換成數據行委托
    /// /summary>
    /// param name="type">傳入類型/param>
    /// param name="propertyList">轉換的屬性/param>
    /// returns>轉換數據行委托/returns>
    private static FuncTModel, object[]> BuildToRowDataDelegation(Type type, PropertyInfo[] propertyList)
    {
      var source = Expression.Parameter(type);
      var items = propertyList.Select(property => ConvertBindPropertyToData(source, property));
      var array = Expression.NewArrayInit(typeof(object), items);
      var lambda = Expression.LambdaFuncTModel, object[]>>(array, source);
      return lambda.Compile();
    }

    /// summary>
    /// 將屬性轉換成數據
    /// /summary>
    /// param name="source">源變量/param>
    /// param name="property">屬性信息/param>
    /// returns>獲取屬性數據表達式/returns>
    private static Expression ConvertBindPropertyToData(ParameterExpression source, PropertyInfo property)
    {
      var propertyType = property.PropertyType;
      var expression = (Expression)Expression.Property(source, property);
      if (propertyType.IsEnum)
        expression = Expression.Convert(expression, propertyType.GetEnumUnderlyingType());
      return Expression.Convert(expression, typeof(object));
    }

    /// summary>
    /// 獲取數據類型
    /// /summary>
    /// param name="type">屬性類型/param>
    /// returns>數據類型/returns>
    private static Type GetDataType(Type type)
    {
      //枚舉默認轉換成對應的值類型
      if (type.IsEnum)
        return type.GetEnumUnderlyingType();
      //可空類型
      if (type.IsGenericType  type.GetGenericTypeDefinition() == typeof(Nullable>))
        return GetDataType(type.GetGenericArguments().First());
      return type;
    }

    /// summary>
    /// 列集合
    /// /summary>
    public static IReadOnlyListDataColumn> Columns { get; }

    /// summary>
    /// 對象轉數據行委托
    /// /summary>
    public static FuncTModel, object[]> ToRowData { get; }

    /// summary>
    /// 集合轉換成DataTable
    /// /summary>
    /// param name="source">集合/param>
    /// param name="tableName">表名稱/param>
    /// returns>轉換完成的DataTable/returns>
    public static DataTable ToDataTable(IEnumerableTModel> source, string tableName = "TempTable")
    {
      //創建表對象
      var table = new DataTable(tableName);
      //設置列
      foreach (var dataColumn in Columns)
      {
        table.Columns.Add(new DataColumn(dataColumn.ColumnName, dataColumn.DataType));
      }

      //循環轉換每一行數據
      foreach (var item in source)
      {
        table.Rows.Add(ToRowData.Invoke(item));
      }

      //返回表對象
      return table;
    }
  }

三、測試封裝代碼

1.測試代碼

創表代碼

CREATE TABLE [dbo].[Person](
	[Id] [BIGINT] NOT NULL,
	[Name] [VARCHAR](64) NOT NULL,
	[Age] [INT] NOT NULL,
	[CreateTime] [DATETIME] NULL,
	[Sex] [INT] NOT NULL,
PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

實體類代碼

定義的實體的屬性名稱需要和SqlServer列名稱類型對應

public class Person
{
  public long Id { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
  public DateTime? CreateTime { get; set; }
  public Gender Sex { get; set; }
}

public enum Gender
{
  Man = 0,
  Woman = 1
}

測試方法

//生成10萬條數據
var persons = new Person[100000];
var random = new Random();
for (int i = 0; i  persons.Length; i++)
{
  persons[i] = new Person
  {
    Id = i + 1,
    Name = "張三" + i,
    Age = random.Next(1, 128),
    Sex = (Gender)random.Next(2),
    CreateTime = random.Next(2) == 0 ? null : (DateTime?) DateTime.Now.AddSeconds(i)
  };
}

//創建數據庫連接
using (var conn = new SqlConnection("Server=.;Database=DemoDataBase;User ID=sa;Password=8888;"))
{
  conn.Open();
  var sw = Stopwatch.StartNew();
  //批量插入數據
  var qty = conn.BulkCopy(persons);
  sw.Stop();
  Console.WriteLine(sw.Elapsed.TotalMilliseconds + "ms");
}

執行批量插入結果

226.4767ms
請按任意鍵繼續. . .

四、代碼下載

GitHub代碼地址:https://github.com/liu-zhen-liang/PackagingComponentsSet/tree/main/SqlBulkCopyComponents

您可能感興趣的文章:
  • SQLServer2008存儲過程實現數據插入與更新
  • Python實現讀取SQLServer數據并插入到MongoDB數據庫的方法示例
  • SQLServer中防止并發插入重復數據的方法詳解
  • 詳解C#批量插入數據到Sqlserver中的四種方式
  • SQL Server批量插入數據案例詳解

標簽:汕頭 濟源 武威 廣東 安徽 臺州 濟寧 泰安

巨人網絡通訊聲明:本文標題《SQL Server 批量插入數據的完美解決方案》,本文關鍵詞  SQL,Server,批量,插入,數據,;如發現本文內容存在版權問題,煩請提供相關信息告之我們,我們將及時溝通與處理。本站內容系統采集于網絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《SQL Server 批量插入數據的完美解決方案》相關的同類信息!
  • 本頁收集關于SQL Server 批量插入數據的完美解決方案的相關信息資訊供網民參考!
  • 推薦文章
    婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av
    裸体健美xxxx欧美裸体表演| 欧美丝袜丝交足nylons| av不卡在线播放| 精品国产自在久精品国产| 日韩和欧美一区二区三区| 欧美美女黄视频| 水蜜桃久久夜色精品一区的特点| 日本高清不卡在线观看| 中文字幕一区二区三区四区| av午夜精品一区二区三区| 国产精品久久久久久久午夜片| 成人久久久精品乱码一区二区三区| 国产视频一区在线观看| av亚洲产国偷v产偷v自拍| 亚洲最大的成人av| 欧美日韩国产三级| 免费成人你懂的| 捆绑调教美女网站视频一区| 久久久亚洲精华液精华液精华液| 国产精品综合一区二区| 国产清纯白嫩初高生在线观看91| 成人午夜激情影院| 秋霞午夜鲁丝一区二区老狼| 国产精品久久久久久亚洲伦| 51精品视频一区二区三区| 国产一区不卡在线| 亚洲综合一区在线| 中文字幕在线一区| 久久综合色综合88| 欧美午夜寂寞影院| 国产九色精品成人porny| 午夜影视日本亚洲欧洲精品| 国产精品久久久久桃色tv| 91精品国产品国语在线不卡| 91网站视频在线观看| 国产成人综合自拍| 激情综合色丁香一区二区| 婷婷久久综合九色综合伊人色| 亚洲色图在线视频| 综合久久一区二区三区| 久久久久国产精品麻豆ai换脸| 在线观看视频欧美| 欧美亚洲国产一区二区三区| 不卡视频一二三四| 99国产精品久久久久| 成人激情免费电影网址| 成人教育av在线| 99精品国产99久久久久久白柏| 91美女片黄在线观看| 欧美亚洲国产一区二区三区 | 久久嫩草精品久久久久| 日韩欧美在线影院| 久久久久久久久久久久久女国产乱| 国产亚洲欧洲一区高清在线观看| 久久久久久夜精品精品免费| 成人激情免费视频| 欧美日韩专区在线| 精品国产乱码久久久久久久| 中文字幕亚洲视频| 一区二区三区在线免费播放| 在线观看av不卡| 欧美精品1区2区| 国产精品网站一区| 亚洲一本大道在线| 久久99精品久久久久久动态图| 成人aa视频在线观看| 91精品免费在线| 国产精品家庭影院| 国产在线播放一区二区三区| 欧美亚洲国产一卡| 国产精品免费网站在线观看| 日韩av一级片| 欧美日韩不卡一区| 国产精品丝袜在线| 日本不卡1234视频| 视频一区二区三区在线| 色综合中文字幕国产 | 日韩限制级电影在线观看| 国产精品免费aⅴ片在线观看| 丝袜国产日韩另类美女| 3751色影院一区二区三区| 国产精品久久久久久福利一牛影视 | 精品黑人一区二区三区久久| 欧美电影免费观看高清完整版在| 日韩av中文字幕一区二区| 久久精品亚洲乱码伦伦中文| 色狠狠桃花综合| 国产精品福利一区二区| 国产成人综合网| 国产清纯美女被跳蛋高潮一区二区久久w| 亚洲视频 欧洲视频| 国产成人精品综合在线观看 | 另类欧美日韩国产在线| 成人午夜私人影院| 精品裸体舞一区二区三区| 天涯成人国产亚洲精品一区av| 色综合久久综合中文综合网| 美腿丝袜亚洲三区| 欧美日韩激情在线| 久久精品久久综合| 五月婷婷久久综合| 欧美成人精品福利| 色婷婷激情综合| 亚洲国产欧美在线| 日韩女优av电影| 成人av片在线观看| 美国十次了思思久久精品导航| 国产精品进线69影院| 久久精品网站免费观看| 日韩欧美亚洲一区二区| 欧美日韩色一区| 欧美日韩亚洲丝袜制服| 在线观看日产精品| 成人av在线电影| 国产精品一级二级三级| 免费观看在线色综合| 一二三四社区欧美黄| 亚洲在线观看免费| 日韩电影在线一区二区| 精品在线免费视频| 成人av影视在线观看| 久久综合av免费| 亚洲欧美日韩国产一区二区三区| 宅男在线国产精品| 成人app网站| 欧美一区二区三区四区在线观看| 久久久亚洲精华液精华液精华液 | 亚洲欧洲日韩女同| 亚洲视频1区2区| 亚洲精品videosex极品| 亚洲一区在线观看视频| 国产日产欧美一区二区视频| 2017欧美狠狠色| 99精品视频在线观看| 欧美日韩一区二区三区免费看| 日韩精品一区二区三区视频播放| 国产日韩欧美精品一区| 亚洲四区在线观看| 日韩国产精品91| 久久婷婷成人综合色| 欧美一级专区免费大片| 日韩精品一区二区三区四区 | 精品美女一区二区| 亚洲欧美一区二区三区孕妇| 亚洲激情自拍偷拍| 日韩av电影天堂| 99久久综合狠狠综合久久| 日韩一区二区三区观看| 国产精品久久毛片| 视频一区二区中文字幕| 成人福利视频网站| 91福利精品第一导航| 精品免费一区二区三区| 中文字幕亚洲一区二区av在线| 日本亚洲电影天堂| 91蜜桃在线观看| 日韩午夜av电影| 亚洲男女一区二区三区| 国产一区欧美日韩| 日韩一区和二区| 亚洲欧美日韩精品久久久久| 久久精品国产第一区二区三区| 不卡在线视频中文字幕| 欧美日产在线观看| 亚洲精品久久久蜜桃| 日本黄色一区二区| 亚洲国产一二三| 日韩亚洲电影在线| 欧美一区二区人人喊爽| 美女性感视频久久| 久久久精品国产免大香伊| 国产乱码精品一区二区三区忘忧草| 久久午夜老司机| 在线成人av网站| 欧美高清视频在线高清观看mv色露露十八 | 久久综合色婷婷| 国产综合久久久久影院| 2019国产精品| 成人免费视频视频| 美女mm1313爽爽久久久蜜臀| 国产精品乱码久久久久久| 欧美日韩国产综合视频在线观看| 国产99久久久久久免费看农村| 日韩在线一区二区三区| 亚洲免费色视频| 亚洲人成小说网站色在线| 日韩午夜激情免费电影| 色国产精品一区在线观看| 国产a久久麻豆| 国产福利一区二区| 精品一区二区成人精品| 亚洲国产色一区| 亚洲一区二区精品视频| 欧美一级淫片007| a级高清视频欧美日韩| 日本欧洲一区二区| 亚洲欧美日韩人成在线播放| 精品国产网站在线观看| 欧美视频完全免费看| 国产一区二区三区在线看麻豆|