asp.net-mvc 使用 C# (ASP.NET MVC) 上传 csv 文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13963253/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 02:28:52  来源:igfitidea点击:

Uploading csv file using C# (ASP.NET MVC)

asp.net-mvc

提问by Spidey

I have a CSV file which contain the following:

我有一个包含以下内容的 CSV 文件:

ProductName,EmployeeID,EmployeeName,ContactNo,Adddress
iPad,1233,Tom,89897898,34 Pitt st
iPad,1573,Hyman,8978 9689,50 George st
iPad,1893,Peter,8878 8989,32 Martin st 

The following code will insert into one table. What I am trying to achieve is to insert into 2 tables:

以下代码将插入到一张表中。我想要实现的是插入 2 个表:

Product table (parent table)
ProductId(Pk), ProductName

Employee Table (child table)
EmployeeId(Pk), ProductId(fk), EmployeeName, ContactNo, Address

So I need to basically insert the record first into Product table and then into Employee table from the CSV file.

所以我需要首先将记录插入到 Product 表中,然后再从 CSV 文件中插入到 Employee 表中。

Controller.cs

控制器.cs

[HttpPost]
public ActionResult Index(HttpPostedFileBase FileUpload)
{
    // Set up DataTable place holder 

    Guid ProductId= Guid.NewGuid();
    using (SqlConnection conn = new SqlConnection(connString))
    {
        conn.Open();

        using (SqlCommand cmd = new SqlCommand(
               "INSERT INTO Product VALUES(" + "@ReferralListID,  @ProductName)", conn))
        {
            //Note product name need to read from csv file
            cmd.Parameters.AddWithValue("@ProductId", ProductId);
            cmd.Parameters.AddWithValue("@ProductName", ProductName); 

            int rows = cmd.ExecuteNonQuery();

            //rows number of record got inserted
        }
    }

    DataTable dt = new DataTable();

    //check we have a file 
    if (FileUpload.ContentLength > 0)
    {
        //Workout our file path
        string fileName = Path.GetFileName(FileUpload.FileName);
        string path = Path.Combine(Server.MapPath("~/App_Data/uploads"), fileName);

        //Try and upload
        try
        {
            FileUpload.SaveAs(path);
            //Process the CSV file and capture the results to our DataTable place holder
            dt = ProcessCSV(path);

            //Process the DataTable and capture the results to our SQL Bulk copy
            ViewData["Feedback"] = ProcessBulkCopy(dt);
        }
        catch (Exception ex)
        {
            //Catch errors
            ViewData["Feedback"] = ex.Message;
        }
    }
    else
    {
        //Catch errors
        ViewData["Feedback"] = "Please select a file";
    }

    //Tidy up
    dt.Dispose();

    return View("Index", ViewData["Feedback"]);
}

/// <summary>
/// Process the file supplied and process the CSV to a dynamic datatable
/// </summary>
/// <param name="fileName">String</param>
/// <returns>DataTable</returns>
private static DataTable ProcessCSV(string fileName)
{
    //Set up our variables 
    string Feedback = string.Empty;
    string line = string.Empty;
    string[] strArray;  
    DataTable dt = new DataTable();
    DataRow row;

    // work out where we should split on comma, but not in a sentance
    Regex r = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");

    //Set the filename in to our stream
    StreamReader sr = new StreamReader(fileName);

    //Read the first line and split the string at , with our regular express in to an array
    line = sr.ReadLine();
    strArray = r.Split(line);

    //For each item in the new split array, dynamically builds our Data columns. Save us having to worry about it.
    Array.ForEach(strArray, s => dt.Columns.Add(new DataColumn()));


    //Read each line in the CVS file until it's empty
    while ((line = sr.ReadLine()) != null)
    {
        row = dt.NewRow();

        //add our current value to our data row
        row.ItemArray = r.Split(line);
        dt.Rows.Add(row);
    }

    //Tidy Streameader up
    sr.Dispose();

    //return a the new DataTable
    return dt;


}

/// <summary>
/// Take the DataTable and using WriteToServer(DataTable) send it all to the database table "BulkImportDetails" in one go
/// </summary>
/// <param name="dt">DataTable</param>
/// <returns>String</returns>
private static String ProcessBulkCopy(DataTable dt)
{
    string Feedback = string.Empty;
    string connString = ConfigurationManager.ConnectionStrings["DataBaseConnectionString"].ConnectionString;

    //make our connection and dispose at the end    
    using(  SqlConnection conn = new SqlConnection(connString))
    {
        //make our command and dispose at the end
        using (var copy = new SqlBulkCopy(conn))
        {
            //Open our connection
            conn.Open();

            //Set target table and tell the number of rows
            copy.DestinationTableName = "Employee";
            copy.BatchSize = dt.Rows.Count;
            try
            {
                //Send it to the server
                copy.WriteToServer(dt);
                Feedback = "Upload complete";
            }
            catch (Exception ex)
            {
                Feedback = ex.Message;
            }
        }
    }

    return Feedback;
}

View.aspx

视图.aspx

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Home Page
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>CSV Bulk Upload</h2> 

    <% using (Html.BeginForm("","",FormMethod.Post, new {enctype="multipart/form-data"})){ %>

        <input type="file" name="FileUpload" />
        <input type="submit" name="Submit" id="Submit" value="Upload" />
    <% } %>

    <p><%= Html.Encode(ViewData["Feedback"]) %></p> 
</asp:Content>

Stored Procedure

存储过程

USE [BULkDatabase]
GO


SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER OFF
GO



CREATE PROCEDURE [dbo].[InsertProdutInfo] 
(
   @ProductName varchar (50),  
   @EmployeeName varchar (50),
   @EmployeeAddress varchar (50)
)

AS


BEGIN TRAN

   update [dbo.Product] 
   set    [ProductName] = @ProductName
   where  [ProductName] = @ProductName;

   -- get product id
   select ProductId = [ProductId] 
   from   [dbo.Product]
   where  [ProductName] = @ProductName;  

   if @@rowcount = 0
   BEGIN TRAN

      DECLARE @ProductId uniqueidentifier
      -- there's no such product, let's create it
      insert into [dbo.Product]
      values (NEWID(),@ProductName);

      select @ProductId = SCOPE_IDENTITY()
   end

   -- now that we know we have added the product and have the id, let's add the rest
   insert into [dbo.Employees]
   values (NEWID(), @EmployeeName, @EmployeeAddress, @ProductId);

COMMIT TRAN

回答by balexandre

first of all you should decouple the Controller from your database code, just simply create a new Class project and host all database access there, so you can have in your Controller something like:

首先,您应该将控制器与数据库代码分离,只需简单地创建一个新的类项目并在那里托管所有数据库访问,这样您就可以在控制器中拥有如下内容:

[HttpPost]
public ActionResult UploadFile(HttpPostedFileBase FileUpload)
{
    if (FileUpload.ContentLength > 0) {
        // there's a file that needs our attention
        var success = db.UploadProductFile(FileUpload);

        // was everything ok?
        if (success)
            return View("UploadSuccess");
        else
            return View("UploadFail");
    }

    return RedirectToAction("Index", new { error = "Please upload a file..." });
}

public ActionResult Index(string error)
{
    ...
}

This way, the controller does not really care what do you do with the uploaded file as it's not the Controllerconcernto know such thing, it has the task to know that it needs to delegate that job and process the result, that's it.

这样,控制器并不真正关心你对上传的文件做了什么,因为它不Controller关心知道这样的事情,它的任务是知道它需要委派那个工作并处理结果,就是这样。

Please see that the action method is called UploadFileand not Index. It's not a good practice to post to the same action to avoid, when the user refreshes the page, post it again.

请注意 action 方法被调用UploadFile而不是Index。发布到相同的操作并不是一个好习惯,以避免在用户刷新页面时再次发布。

I also suggest you to use ADO.NET Entity Model, there are plenty of Videos out there, in ASP.NET website as well, and it will greatly help you use the database in a simpler and clean way.

我还建议您使用ADO.NET Entity Model,那里有很多视频,在 ASP.NET 网站中也是如此,它将极大地帮助您以更简单和干净的方式使用数据库。

back to your question... Inside your Database class, the method UploadProductFileshould be something like, and assuming that you don't have more than 200 records to process it's better to use the memory to deal with the filerather than spend time to save and read again (for more, you should save the file and process it, like you already do):

回到你的问题......在你的数据库类中,该方法UploadProductFile应该是这样的,假设你没有超过200条记录来处理最好使用内存来处理文件而不是花时间来保存并再次阅读(更多,你应该保存文件并处理它,就像你已经做的那样):

private bool UploadProductFile(HttpPostedFileBase FileUpload)
{
    // get the file stream in a readable way
    StreamReader reader = new StreamReader(FileUpload.InputStream);

    // get a DataTable representing the passed string
    System.Data.DataTable dt = ProcessCSV(reader.ReadToEnd()); 

    // for each row, compose the statement
    bool success = true;
    foreach (System.Data.DataRow row in dt.Rows)
        success = db.InsertProdutInfo(row);

    return success;
}

the method InsertProdutInfowould fire a store procedurethat would be something like:

该方法InsertProdutInfo将触发一个类似于以下内容的存储过程

declare @product_key int

begin tran

   update [tbl_products] 
   set    [name] = @product_name, [last_update] = getdate()
   where  [name] = @product_name;

   -- get product id
   select @product_key = [id] 
   from   [tbl_products]
   where  [name] = @product_name;  

   if @@rowcount = 0
   begin
      -- there's no such product, let's create it
      insert into [tbl_products] (name, last_update)
      values (@product_name, getdate());

      select @product_key = SCOPE_IDENTITY()
   end

   -- now that we know we have added the product and have the id, let's add the rest
   insert into [tbl_Employees] (id, product_id, name, contact, address)
   values (@employee_id, @product_key, @employee_name,
           @employee_contact, @employee_address);

commit tran

this way you will have everything you need.

这样,您将拥有所需的一切。