jjzjj

c# - 将大量记录(批量插入)写入 .NET/C# 中的 Access

coder 2023-07-08 原文

从 .NET 向 MS Access 数据库执行批量插入的最佳方法是什么?使用 ADO.NET,写出一个大型数据集需要一个多小时。

请注意,在我“重构”它之前,我的原始帖子在问题部分既有问题又有答案。我接受了 Igor Turman 的建议并将其分为两部分重新编写 - 上面的问题和我的回答。

最佳答案

我发现以特定方式使用 DAO 大约比使用 ADO.NET 快 30 倍。我正在分享这个答案中的代码和结果。作为背景,下面的测试是写出一个 20 列表的 100 000 条记录。

技术和时间的总结 - 从最好到最坏:

  • 02.8 秒:使用DAO,使用DAO.Field是指表列
  • 02.8 秒:写出到文本文件,使用自动化将文本导入 Access
  • 11.0 秒:使用DAO,使用列索引来引用表列。
  • 17.0 秒:使用DAO,引用名称列
  • 79.0 秒:使用 ADO.NET,为每一行生成 INSERT 语句
  • 86.0 秒:使用 ADO.NET,使用 DataTable 到 DataAdapter 进行“批量”插入

  • 作为背景,有时我需要对相当大量的数据进行分析,我发现 Access 是最好的平台。分析涉及许多查询,通常还涉及大量 VBA 代码。

    由于各种原因,我想使用 C# 而不是 VBA。典型的方式是使用OleDB连接Access。我使用了 OleDbDataReader抓取数百万条记录,而且效果很好。但是将结果输出到表格时,需要很长时间。一个多小时。

    首先,让我们讨论从 C# 向 Access 写入记录的两种典型方式。这两种方式都涉及 OleDB 和 ADO.NET。第一种是一次生成一个 INSERT 语句并执行它们,100 000 条记录需要 79 秒。代码是:
    public static double TestADONET_Insert_TransferToAccess()
    {
      StringBuilder names = new StringBuilder();
      for (int k = 0; k < 20; k++)
      {
        string fieldName = "Field" + (k + 1).ToString();
        if (k > 0)
        {
          names.Append(",");
        }
        names.Append(fieldName);
      }
    
      DateTime start = DateTime.Now;
      using (OleDbConnection conn = new OleDbConnection(Properties.Settings.Default.AccessDB))
      {
        conn.Open();
        OleDbCommand cmd = new OleDbCommand();
        cmd.Connection = conn;
    
        cmd.CommandText = "DELETE FROM TEMP";
        int numRowsDeleted = cmd.ExecuteNonQuery();
        Console.WriteLine("Deleted {0} rows from TEMP", numRowsDeleted);
    
        for (int i = 0; i < 100000; i++)
        {
          StringBuilder insertSQL = new StringBuilder("INSERT INTO TEMP (")
            .Append(names)
            .Append(") VALUES (");
    
          for (int k = 0; k < 19; k++)
          {
            insertSQL.Append(i + k).Append(",");
          }
          insertSQL.Append(i + 19).Append(")");
          cmd.CommandText = insertSQL.ToString();
          cmd.ExecuteNonQuery();
        }
        cmd.Dispose();
      }
      double elapsedTimeInSeconds = DateTime.Now.Subtract(start).TotalSeconds;
      Console.WriteLine("Append took {0} seconds", elapsedTimeInSeconds);
      return elapsedTimeInSeconds;
    }
    

    请注意,我在 Access 中没有发现允许批量插入的方法。

    然后我想也许使用带有数据适配器的数据表会被证明是有用的。特别是因为我认为我可以使用 UpdateBatchSize 进行批量插入数据适配器的属性。但是,显然只有 SQL Server 和 Oracle 支持,而 Access 不支持。最长的时间是 86 秒。我使用的代码是:
    public static double TestADONET_DataTable_TransferToAccess()
    {
      StringBuilder names = new StringBuilder();
      StringBuilder values = new StringBuilder();
      DataTable dt = new DataTable("TEMP");
      for (int k = 0; k < 20; k++)
      {
        string fieldName = "Field" + (k + 1).ToString();
        dt.Columns.Add(fieldName, typeof(int));
        if (k > 0)
        {
          names.Append(",");
          values.Append(",");
        }
        names.Append(fieldName);
        values.Append("@" + fieldName);
      }
    
      DateTime start = DateTime.Now;
      OleDbConnection conn = new OleDbConnection(Properties.Settings.Default.AccessDB);
      conn.Open();
      OleDbCommand cmd = new OleDbCommand();
      cmd.Connection = conn;
    
      cmd.CommandText = "DELETE FROM TEMP";
      int numRowsDeleted = cmd.ExecuteNonQuery();
      Console.WriteLine("Deleted {0} rows from TEMP", numRowsDeleted);
    
      OleDbDataAdapter da = new OleDbDataAdapter("SELECT * FROM TEMP", conn);
    
      da.InsertCommand = new OleDbCommand("INSERT INTO TEMP (" + names.ToString() + ") VALUES (" + values.ToString() + ")");
      for (int k = 0; k < 20; k++)
      {
        string fieldName = "Field" + (k + 1).ToString();
        da.InsertCommand.Parameters.Add("@" + fieldName, OleDbType.Integer, 4, fieldName);
      }
      da.InsertCommand.UpdatedRowSource = UpdateRowSource.None;
      da.InsertCommand.Connection = conn;
      //da.UpdateBatchSize = 0;
    
      for (int i = 0; i < 100000; i++)
      {
        DataRow dr = dt.NewRow();
        for (int k = 0; k < 20; k++)
        {
          dr["Field" + (k + 1).ToString()] = i + k;
        }
        dt.Rows.Add(dr);
      }
      da.Update(dt);
      conn.Close();
    
      double elapsedTimeInSeconds = DateTime.Now.Subtract(start).TotalSeconds;
      Console.WriteLine("Append took {0} seconds", elapsedTimeInSeconds);
      return elapsedTimeInSeconds;
    }
    

    然后我尝试了非标准的方法。首先,我写出一个文本文件,然后使用自动化将其导入。这很快 - 2.8 秒 - 并列第一。但我认为这很脆弱,原因有很多: 输出日期字段很棘手。我必须专门格式化它们( someDate.ToString("yyyy-MM-dd HH:mm") ),然后设置一个特殊的“导入规范”,以这种格式进行编码。导入规范还必须正确设置“引用”分隔符。在下面的示例中,只有整数字段,不需要导入规范。

    文本文件对于“国际化”也很脆弱,其中使用逗号作为小数点分隔符、不同的日期格式、可能使用 unicode。

    请注意,第一条记录包含字段名称,因此列顺序不依赖于表,并且我们使用自动化来执行文本文件的实际导入。
    public static double TestTextTransferToAccess()
    {
      StringBuilder names = new StringBuilder();
      for (int k = 0; k < 20; k++)
      {
        string fieldName = "Field" + (k + 1).ToString();
        if (k > 0)
        {
          names.Append(",");
        }
        names.Append(fieldName);
      }
    
      DateTime start = DateTime.Now;
      StreamWriter sw = new StreamWriter(Properties.Settings.Default.TEMPPathLocation);
    
      sw.WriteLine(names);
      for (int i = 0; i < 100000; i++)
      {
        for (int k = 0; k < 19; k++)
        {
          sw.Write(i + k);
          sw.Write(",");
        }
        sw.WriteLine(i + 19);
      }
      sw.Close();
    
      ACCESS.Application accApplication = new ACCESS.Application();
      string databaseName = Properties.Settings.Default.AccessDB
        .Split(new char[] { ';' }).First(s => s.StartsWith("Data Source=")).Substring(12);
    
      accApplication.OpenCurrentDatabase(databaseName, false, "");
      accApplication.DoCmd.RunSQL("DELETE FROM TEMP");
      accApplication.DoCmd.TransferText(TransferType: ACCESS.AcTextTransferType.acImportDelim,
      TableName: "TEMP",
      FileName: Properties.Settings.Default.TEMPPathLocation,
      HasFieldNames: true);
      accApplication.CloseCurrentDatabase();
      accApplication.Quit();
      accApplication = null;
    
      double elapsedTimeInSeconds = DateTime.Now.Subtract(start).TotalSeconds;
      Console.WriteLine("Append took {0} seconds", elapsedTimeInSeconds);
      return elapsedTimeInSeconds;
    }
    

    最后,我尝试了 DAO。许多网站都给出了关于使用 DAO 的巨大警告。然而,事实证明它是 Access 和 .NET 之间交互的最佳方式,尤其是当您需要写出大量记录时。此外,它还可以 Access 表的所有属性。我在某处读到使用 DAO 而不是 ADO.NET 对事务进行编程是最容易的。

    请注意,有几行代码被注释。他们将很快被解释。
    public static double TestDAOTransferToAccess()
    {
    
      string databaseName = Properties.Settings.Default.AccessDB
        .Split(new char[] { ';' }).First(s => s.StartsWith("Data Source=")).Substring(12);
    
      DateTime start = DateTime.Now;
      DAO.DBEngine dbEngine = new DAO.DBEngine();
      DAO.Database db = dbEngine.OpenDatabase(databaseName);
    
      db.Execute("DELETE FROM TEMP");
    
      DAO.Recordset rs = db.OpenRecordset("TEMP");
    
      DAO.Field[] myFields = new DAO.Field[20];
      for (int k = 0; k < 20; k++) myFields[k] = rs.Fields["Field" + (k + 1).ToString()];
    
      //dbEngine.BeginTrans();
      for (int i = 0; i < 100000; i++)
      {
        rs.AddNew();
        for (int k = 0; k < 20; k++)
        {
          //rs.Fields[k].Value = i + k;
          myFields[k].Value = i + k;
          //rs.Fields["Field" + (k + 1).ToString()].Value = i + k;
        }
        rs.Update();
        //if (0 == i % 5000)
        //{
          //dbEngine.CommitTrans();
          //dbEngine.BeginTrans();
        //}
      }
      //dbEngine.CommitTrans();
      rs.Close();
      db.Close();
    
      double elapsedTimeInSeconds = DateTime.Now.Subtract(start).TotalSeconds;
      Console.WriteLine("Append took {0} seconds", elapsedTimeInSeconds);
      return elapsedTimeInSeconds;
    }
    

    在此代码中,我们为每一列 ( myFields[k] ) 创建了 DAO.Field 变量,然后使用它们。耗时 2.8 秒。或者,可以直接 Access 注释行 rs.Fields["Field" + (k + 1).ToString()].Value = i + k; 中找到的那些字段。这将时间增加到 17 秒。将代码包装在事务中(请参阅注释行)将其缩短到 14 秒。使用整数索引 rs.Fields[k].Value = i + k;将其降至 11 秒。使用 DAO.Field ( myFields[k] ) 和交易实际上需要更长的时间,将时间增加到 3.1 秒。

    最后,为了完整起见,所有这些代码都在一个简单的静态类中,而 using陈述是:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using ACCESS = Microsoft.Office.Interop.Access; // USED ONLY FOR THE TEXT FILE METHOD
    using DAO = Microsoft.Office.Interop.Access.Dao; // USED ONLY FOR THE DAO METHOD
    using System.Data; // USED ONLY FOR THE ADO.NET/DataTable METHOD
    using System.Data.OleDb; // USED FOR BOTH ADO.NET METHODS
    using System.IO;  // USED ONLY FOR THE TEXT FILE METHOD
    

    关于c# - 将大量记录(批量插入)写入 .NET/C# 中的 Access,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7070011/

    有关c# - 将大量记录(批量插入)写入 .NET/C# 中的 Access的更多相关文章

    1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    2. ruby - 其他文件中的 Rake 任务 - 2

      我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

    3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

      作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

    4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

      Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

    5. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

      我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

    6. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

      我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

    7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

      刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

    8. ruby - 如何模拟 Net::HTTP::Post? - 2

      是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

    9. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

      我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

    10. Ruby 写入和读取对象到文件 - 2

      好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

    随机推荐