背景
我开发了一个简单的MVC 5应用程序,它能够使用Entity Framework 6和SqlBulkTools(coreutils)将Excel文件导入SQL Server 2012数据库。代码结构如下所示。
模型(Project.Models)
using SqlBulkTools;
[Route("File")]
public class FileController : Controller
{
// class-level single datatable
DataTable dt = new DataTable();
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString);
// GET
public ViewResult Import()
{
return View();
}
// POST
[HttpPost]
public ActionResult Import(FileModel model)
{
// when file name is empty, execute these lines below
if (String.IsNullOrEmpty(model.FileName)
{
foreach (String file in Request.Files)
{
model.FileToUpload = this.Request.Files[file];
}
if (model.FileToUpload != null && model.FileToUpload.ContentLength > 0)
{
model.FileName = Path.GetFileName(FileToUpload.FileName);
}
}
var path = Path.Combine(Server.MapPath("~/Files/Imported"), model.FileName);
if (System.IO.File.Exists(path))
{
System.IO.File.Delete(path);
}
model.FileToUpload.SaveAs(path);
String oleconstring = @"Provider=Microsoft.ACE.OLEDB.12.0; Data Source=" + path + "; Extended Properties=\"Excel 12.0; HDR=Yes; IMEX=2\"; Persist Security Info=False";
String olecmdstring = "SELECT * FROM [" + model.SheetName + "$]";
using (var oleda = new OleDbDataAdapter())
{
using (var olecon = new OleDbConnection(oleconstring))
{
try
{
oleda.SelectCommand = new OleDbCommand(olecmdstring, olecon);
oleda.Fill(dt);
// remove all "null" values from Excel worksheet if any
dt = dt.Rows.Cast<DataRow>().Where(r => !r.ItemArray.All(f => f is DBNull || f as String == null || String.Compare((f as String).Trim(), String.Empty) == 0)).CopyToDataTable();
// trim all whitespaces after column names
foreach (DataColumn cols in dt.Columns)
{
cols.ColumnName = cols.ColumnName.Trim();
}
if (dt != null && dt.Rows.Count > 0)
{
switch (model.TableName)
{
case "Product":
for (int i = 0; i < dt.Rows.Count; i++)
{
if (dt.Rows[i]["TaxNote"].ToString().Equals("None", StringComparison.OrdinalIgnoreCase))
{
dt.Rows[i]["TaxNote"] = DBNull.Value;
}
else
{
if (dt.Rows[i]["TaxNote"] is DateTime)
{
dt.Rows[i]["TaxNote"] = String.Format("{0:yyyy-mm-dd}", dt.Rows[i]["TaxNote"]);
}
else
{
dt.Rows[i]["TaxNote"] = DBNull.Value;
}
}
}
var bulkOperation = new BulkOperations();
// convert DataTable into IEnumerable for bulk upsert
var productList = dt.AsEnumerable().Select(x => new Product()
{
SessionNo = x.Field<double>("SessionNo").ToString(),
ProductName = x.Field<String>("ProductName"),
Date = x.Field<DateTime>("Date"),
LotNumber = x.Field<String>("LotNumber"),
RegNumber = x.Field<String>("RegNumber"),
// this won't work if source column in Excel contains null
InitPrice = (decimal)(x.Field<Nullable<double>>("InitPrice") != null ? x.Field<Nullable<double>>("InitPrice") : 0),
// this won't work if source column in Excel contains null
FinalPrice = (decimal)(x.Field<Nullable<double>>("FinalPrice") != null ? x.Field<Nullable<double>>("FinalPrice") : 0),
TaxNote = x.Field<String>("TaxNote")
});
bulkOperation.Setup<Product>()
.ForCollection(productList) // requires IEnumerable to work with destination table
.WithTable("Product")
.AddAllColumns()
.BulkInsertOrUpdate()
.SetIdentityColumn(x => x.ProductId)
.MatchTargetOn(x => x.SessionNo)
.MatchTargetOn(x => x.LotNumber)
.MatchTargetOn(x => x.RegNumber);
bulkOperation.CommitTransaction(conn);
break;
// other unrelated case stuffs
}
}
else
{
// Error: DataTable is null or empty
ViewBag.Error = "No data present."
return View(model);
}
}
catch (Exception e)
{
ViewBag.Error = "An error occurred when importing data. Message: " + e.Message;
return View(model);
}
}
}
return RedirectToAction("Success", "Notify");
}
}
Controller(Project.Controllers.FileController)
@{
ViewBag.Title = "Data Import Example";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@using Project.Models
@model FileModel
<div>
@using (Html.BeginForm("Import", "File", FormMethod.Post))
{
<p>File name:</p>
@Html.TextBoxFor(m => m.FileName)
<br />
<p>Worksheet name:</p>
@Html.TextBoxFor(m => m.SheetName)
<br />
<p>SQL table name:</p>
@Html.TextBoxFor(m => m.TableName)
<br />
<p>File to upload:</p>
@Html.TextBoxFor(m => m.FileToUpload, new { type = "file" })
<br /><br />
<input type="submit" value="Import to Database" />
}
</div>
<div>@ViewBag.Error</div>
查看(Import.cshtml)
DataTable
问题陈述
应用程序将Excel工作表中的数据导入InitPrice
,其目标是使用批量upsert过程的Product表(如果找到现有数据则更新,如果没有匹配数据则插入)。
Excel工作表表结构与数据库表和模型类完全相同,但是值由其他人提交,因此我无法更改工作表内容,可能FinalPrice
和{{1} }列具有空值,可能转换为DBNull
。所有其他数值都被视为double
。
当任何数据录入人员在Import
或InitPrice
列上通过FinalPrice
页面上传了他/她的Excel工作表时,其中存在空值(当然,它没有&#39; t用空值填充整个列),它返回带有消息的相同页面:
导入数据时发生错误。消息:无法转换对象 类型&#39; System.DBNull&#39;输入&#39; System.Double&#39;。
哪个异常指向InitPrice
方法内的FinalPrice
或Select
分配。
但是,当零值分配为空值时,导入过程成功完成。
要考虑的问题:
当Nullable<decimal>
或IEnumerable
列包含{{{{}}列时,如何在相应的InitPrice
成员上将默认值(零或空)指定为FinalPrice
1}}来源DBNull
上的值?
如何使用DataTable
中存储的现有字段作为DataTable
进行批量转发,而无需使用IEnumerable
方法声明每个目标列字段?如果不能,可以做哪些变通办法?
我在Github&amp; How to perform Update and Insert in SQL Server 2012 using SQL Bulk insert C# and ignore the duplicate values if already present in database,但是这些问题使用普通的SqlBulkCopy而不是SqlBulkTools或者使用存储过程批量upsert。