在以IEnumerable执行批量插入/更新时,如何为DataTable中的DBNull值分配默认值?

时间:2016-10-06 08:57:44

标签: c# sql-server asp.net-mvc asp.net-mvc-5

背景

我开发了一个简单的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

当任何数据录入人员在ImportInitPrice列上通过FinalPrice页面上传了他/她的Excel工作表时,其中存在空值(当然,它没有&#39; t用空值填充整个列),它返回带有消息的相同页面:

  

导入数据时发生错误。消息:无法转换对象   类型&#39; System.DBNull&#39;输入&#39; System.Double&#39;。

哪个异常指向InitPrice方法内的FinalPriceSelect分配。

但是,当零值分配为空值时,导入过程成功完成。

要考虑的问题:

  1. Nullable<decimal>IEnumerable列包含{{{{}}列时,如何在相应的InitPrice成员上将默认值(零或空)指定为FinalPrice 1}}来源DBNull上的值?

  2. 如何使用DataTable中存储的现有字段作为DataTable进行批量转发,而无需使用IEnumerable方法声明每个目标列字段?如果不能,可以做哪些变通办法?

  3. 我在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。

0 个答案:

没有答案