• 寻找 IBatisNet 批量插入(批量复制) 的心路历程


    1、IBatisNet本身就是一个比较早的ORM框架,再加上是从java iBatis移值过来的,其流行程度不是特别高资料也不是很多(一年前),不过最近好像搜索比较多了一些(可能是训练的结果吧)

    2、iBatis是有批量插入功能可Net就没有(光这个答案,都搜了好久。MyBatis是可以批量插入的,可公司框架是用的1.5版本的嘞)

    3、无赖只好继续找,大多都说用用 iterate

    <!--批量导入数据-->
        <insert id="AddTmpResource"  parameterClass="ArrayList">
          <iterate conjunction=" " open="" close=""  >
            <![CDATA[  
            insert into $[].TableName$ ([PhoneNum], [Name], [Resource]) values  
                (#[].PhoneNum#, #[].Name#, #[].Resource#)  
            ]]>
          </iterate>
        </insert>

    当然要比foreach一条条插快,但这个有个限制2000个参数,所有(插入的行数=(2000/列数)的限制,而且与后面的批量复制差远了

    4、最后还是找到了一个

    下面就是复制的了

    二、批量插入的变通解决方案
    鉴于常见插入都是针对单表的,本文的示例延续使用“iBATIS.net获取运行时sql语句”中的Person表来讲解。
    1、批量数据插入
      既然没看到iBatis.net有现成的类库可实现批量导入,再次祭出杀手锏,查找ado.net下的批量数据操作。这一次资料就灰常的丰富和全面了。在常见的几种解决方案中,楼猪选择了看上去非常简洁而且效率也不低的SqlBulkCopy对象的实现方式,间接实现了iBatis.net的数据导入:
    (1)、基类中两个虚方法

    /// <summary>
            /// 批量插入(这个方法外部重写)
            /// </summary>
            /// <typeparam name="M"></typeparam>
            /// <param name="listModels"></param>
            /// <returns></returns>
            public virtual bool BatchInsert<M>(IList<M> listModels) where M : class
            {
                bool flag = false;
                return flag;
            }

            /// <summary>
            /// 执行插入命令
            /// </summary>
            /// <param name="connStr">sql连接字符串</param>
            /// <param name="tableName">表名称</param>
            /// <param name="dt">组装好的要批量导入的datatable</param>
            /// <returns></returns>
            protected virtual bool ExecuteInsertCommand(string connStr, string tableName, DataTable dt)
            {
                bool flag = false;
                //SqlTransaction transaction = null;
                //ISqlMapSession sesseion = this.SqlMapper.CreateSqlMapSession();
                try
                {
                    using (System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope())
                    {
                        using (SqlConnection conn = new SqlConnection(connStr))
                        {
                            conn.Open();
                            using (SqlBulkCopy sbc = new SqlBulkCopy(conn))
                            {
                                //sesseion.BeginTransaction();
                                //transaction = conn.BeginTransaction();
                                //服务器上目标表的名称
                                sbc.DestinationTableName = tableName;
                                sbc.BatchSize = 50000;
                                sbc.BulkCopyTimeout = 180;
                                for (int i = 0; i < dt.Columns.Count; i++)
                                {
                                    //列映射定义数据源中的列和目标表中的列之间的关系
                                    sbc.ColumnMappings.Add(dt.Columns[i].ColumnName, dt.Columns[i].ColumnName);
                                }
                                sbc.WriteToServer(dt);
                                flag = true;
                                //throw new Exception("Test...");
                                //transaction.Commit();//无效事务
                                //sesseion.Complete();  //无效事务
                                scope.Complete();//有效的事务
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    //if (transaction != null)
                    //{
                    //    transaction.Rollback();
                    //}
                    //if (sesseion != null)
                    //{
                    //    sesseion.RollBackTransaction();
                    //}
                    flag = false;
                    string errMsg = ex.Message;
                }
                return flag;
            }



            /// <summary>
            /// 批量插入(这个方法外部重写)
            /// </summary>
            /// <typeparam name="M"></typeparam>
            /// <param name="listModels"></param>
            /// <returns></returns>
            public virtual bool BatchInsert<M>(IList<M> listModels) where M : class
            {
                bool flag = false;
                return flag;
            }

            /// <summary>
            /// 执行插入命令
            /// </summary>
            /// <param name="connStr">sql连接字符串</param>
            /// <param name="tableName">表名称</param>
            /// <param name="dt">组装好的要批量导入的datatable</param>
            /// <returns></returns>
            protected virtual bool ExecuteInsertCommand(string connStr, string tableName, DataTable dt)
            {
                bool flag = false;
                //SqlTransaction transaction = null;
                //ISqlMapSession sesseion = this.SqlMapper.CreateSqlMapSession();
                try
                {
                    using (System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope())
                    {
                        using (SqlConnection conn = new SqlConnection(connStr))
                        {
                            conn.Open();
                            using (SqlBulkCopy sbc = new SqlBulkCopy(conn))
                            {
                                //sesseion.BeginTransaction();
                                //transaction = conn.BeginTransaction();
                                //服务器上目标表的名称
                                sbc.DestinationTableName = tableName;
                                sbc.BatchSize = 50000;
                                sbc.BulkCopyTimeout = 180;
                                for (int i = 0; i < dt.Columns.Count; i++)
                                {
                                    //列映射定义数据源中的列和目标表中的列之间的关系
                                    sbc.ColumnMappings.Add(dt.Columns[i].ColumnName, dt.Columns[i].ColumnName);
                                }
                                sbc.WriteToServer(dt);
                                flag = true;
                                //throw new Exception("Test...");
                                //transaction.Commit();//无效事务
                                //sesseion.Complete();  //无效事务
                                scope.Complete();//有效的事务
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    //if (transaction != null)
                    //{
                    //    transaction.Rollback();
                    //}
                    //if (sesseion != null)
                    //{
                    //    sesseion.RollBackTransaction();
                    //}
                    flag = false;
                    string errMsg = ex.Message;
                }
                return flag;
            }

     说明:
    a、从ExecuteInsertCommond的实现代码中,可以清楚地看到,其实我们就是直接利用了ado.net的SqlBulkCopy对象,通过WriteToServer方法实现的。在示例代码中我们需要传递的三个参数分别是数据库连接字符串,表名和一个datatable对象。其中datatable是在外部组装好传递进来给WriteToServer方法使用的,WriteToServer方法还有另外3个重载方法,您可以扩展实现其他形式的参数传递。
    b、在批量插入的代码中,事务的处理楼猪选择了传说已久的TransactionScope,记得刚毕业那会,没少这么写:

    //.Net 2.0 可以这样子: 
    using (TransactionScope trans = new TransactionScope()) 

       //执行你的事务,如果不成功自动回滚

     TransactionScope已经验证通过,而且省事的一塌糊涂。
    至于iBatis自己的事务SqlMapperSession或者ado.net的SqlTransaction,楼猪在注释中写的很清楚,两种事务无一例外地有异常。
    (2)、dao重写批量导入方法

    /// <summary>
            /// 批量插入
            /// </summary>
            /// <typeparam name="M"></typeparam>
            /// <param name="listModels"></param>
            /// <returns></returns>
            public override bool BatchInsert<M>(IList<M> listModels)
            {
                bool flag = false;
                try
                {
                    string connStr = this.SqlMapper.DataSource.ConnectionString;
                    string tbName = typeof(M).Name;
                    DataTable dt = DataTableHelper.CreateTable<M>(listModels);
                    flag = ExecuteInsertCommand(connStr, tbName, dt);
                }
                catch
                {
                    flag = false;
                }
                return flag;
            }
        /// <summary>
            /// 批量插入
            /// </summary>
            /// <typeparam name="M"></typeparam>
            /// <param name="listModels"></param>
            /// <returns></returns>
            public override bool BatchInsert<M>(IList<M> listModels)
            {
                bool flag = false;
                try
                {
                    string connStr = this.SqlMapper.DataSource.ConnectionString;
                    string tbName = typeof(M).Name;
                    DataTable dt = DataTableHelper.CreateTable<M>(listModels);
                    flag = ExecuteInsertCommand(connStr, tbName, dt);
                }
                catch
                {
                    flag = false;
                }
                return flag;
            }

     我们看到,在重写的方法里准备了需要传递的三个参数:数据库连接字符串,表名和一个datatable对象。在datatable组装的时候我们借助了一个辅助类:

    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Reflection;

    namespace IBatisNetApp.DAL.Common
    {
        using IBatisNetApp.DAL.Model;

        public class DataTableHelper
        {
            private static IList<string> CreateModelProperty<T>(T obj) where T : class
            {
                IList<string> listColumns = new List<string>();
                BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
                Type objType = typeof(T);
                PropertyInfo[] propInfoArr = objType.GetProperties(bf);
                foreach (PropertyInfo item in propInfoArr)
                {
                    object[] objAttrs = item.GetCustomAttributes(typeof(TableColumnAttribute), true);
                    if (objAttrs != null && objAttrs.Length > 0)//取出实体对应表的实际列名
                    {
                        listColumns.Add(item.Name);
                    }
                }
                return listColumns;
            }

            private static DataTable CreateTable(IList<string> listColumns)
            {
                DataTable dt = new DataTable();
                for (int i = 0; i < listColumns.Count; i++)
                {
                    dt.Columns.Add(new DataColumn(listColumns[i]));
                }
                return dt;
            }

            public static DataTable CreateTable<T>(IList<T> listModels) where T : class
            {
                T model = default(T);
                IList<string> listProperties = CreateModelProperty<T>(model);
                DataTable dataTable = CreateTable(listProperties);
                BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
                Type objType = typeof(T);
                PropertyInfo[] propInfoArr = objType.GetProperties(bf);
                foreach (T itemModel in listModels)
                {
                    DataRow dataRow = dataTable.NewRow();
                    foreach (PropertyInfo item in propInfoArr)
                    {
                        string propName = item.Name;
                        if (listProperties.Contains(propName))
                        {
                            object value = item.GetValue(itemModel, null);
                            dataRow[propName] = value;
                        }
                    }
                    dataTable.Rows.Add(dataRow);
                }
                return dataTable;
            }
        }
    }


    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Reflection;

    namespace IBatisNetApp.DAL.Common
    {
        using IBatisNetApp.DAL.Model;

        public class DataTableHelper
        {
            private static IList<string> CreateModelProperty<T>(T obj) where T : class
            {
                IList<string> listColumns = new List<string>();
                BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
                Type objType = typeof(T);
                PropertyInfo[] propInfoArr = objType.GetProperties(bf);
                foreach (PropertyInfo item in propInfoArr)
                {
                    object[] objAttrs = item.GetCustomAttributes(typeof(TableColumnAttribute), true);
                    if (objAttrs != null && objAttrs.Length > 0)//取出实体对应表的实际列名
                    {
                        listColumns.Add(item.Name);
                    }
                }
                return listColumns;
            }

            private static DataTable CreateTable(IList<string> listColumns)
            {
                DataTable dt = new DataTable();
                for (int i = 0; i < listColumns.Count; i++)
                {
                    dt.Columns.Add(new DataColumn(listColumns[i]));
                }
                return dt;
            }

            public static DataTable CreateTable<T>(IList<T> listModels) where T : class
            {
                T model = default(T);
                IList<string> listProperties = CreateModelProperty<T>(model);
                DataTable dataTable = CreateTable(listProperties);
                BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
                Type objType = typeof(T);
                PropertyInfo[] propInfoArr = objType.GetProperties(bf);
                foreach (T itemModel in listModels)
                {
                    DataRow dataRow = dataTable.NewRow();
                    foreach (PropertyInfo item in propInfoArr)
                    {
                        string propName = item.Name;
                        if (listProperties.Contains(propName))
                        {
                            object value = item.GetValue(itemModel, null);
                            dataRow[propName] = value;
                        }
                    }
                    dataTable.Rows.Add(dataRow);
                }
                return dataTable;
            }
        }
    }

     必须注意,datatable里的列名顺序不限,但是列名必须对应实际表里的列(datatable里的列可以少于等于实际表里的列,但是不能大于)。示例中Person实体的TableColumn特性就是为了让属性和表的列名匹配,否则,实体继承的一些不是实际表的属性字段也会映射到表里,这样就会发生“给定的 ColumnMapping 与源或目标中的任意列均不匹配”的异常。

    2、批量数据插入并获取所有新插入的自增Id
      其实这个是在问题1的基础上延伸的一个问题。楼猪想了一个笨拙的解决方案,思路就是,将要插入的大数据量的泛型List(设有count项)第一项先单独插入,返回一个自增id设为firstId,然后按照1里的解决方案(有事务的那种,不带事务的话,结果就不是有偏差那么简单了,很可能错的非常离谱),批量插入剩余的count-1项,插入成功后,我们可以断定批量插入的数据Id的范围为firstId<=Id<firstId+count。
    ps,在实际的执行环境中,楼猪还是觉得这个非常不靠谱,一定要慎用。哎,懒得贴代码了。
    最后,期待高手给出iBATIS.net的批量数据操作的完美解决方案。

    【补充】高手你在哪里,顶上去啊?

    demo下载:IBatisNetApp

  • 相关阅读:
    global
    myTimer
    SQL SERVER 2008 阻止保存要求重新创建表的更改
    Singleton
    logger
    多线程编写
    如何:设置 Silverlight 应用程序以进行CodeUI自动化测试
    【Android】Application is not installed on your phone
    【转载】sql2005中判读视图、表、存储过程等是否存在的语句
    Windows7(win7)系统重装与破解
  • 原文地址:https://www.cnblogs.com/xiaoruilin/p/6140525.html
Copyright © 2020-2023  润新知