【问题标题】:How to take a CSV field and write to columns in SQL如何获取 CSV 字段并写入 SQL 中的列
【发布时间】:2014-06-07 15:38:35
【问题描述】:

我有以下代码,它采用 CSV 并写入控制台:

using (CsvReader csv = new CsvReader(
           new StreamReader("data.csv"), true))
    {
        // missing fields will not throw an exception,
        // but will instead be treated as if there was a null value
        csv.MissingFieldAction = MissingFieldAction.ReplaceByNull;
        // to replace by "" instead, then use the following action:
        //csv.MissingFieldAction = MissingFieldAction.ReplaceByEmpty;
        int fieldCount = csv.FieldCount;
        string[] headers = csv.GetFieldHeaders();
        while (csv.ReadNextRecord())
        {
            for (int i = 0; i < fieldCount; i++)
                Console.Write(string.Format("{0} = {1};",
                              headers[i],
                              csv[i] == null ? "MISSING" : csv[i]));
            Console.WriteLine();
        }
    }

CSV 文件有 7 个标题,我的 SQL 表中有 7 个列。

获取每个csv[i] 并为每一列写入一行然后移动到下一行的最佳方法是什么?

我尝试将ccsv[i] 添加到字符串数组中,但没有成功。

我还尝试了以下方法:

SqlCommand sql = new SqlCommand("INSERT INTO table1 [" + csv[i] + "]", mysqlconnectionstring);
sql.ExecuteNonQuery();

我的桌子(table1)是这样的:

name address city zipcode phone fax device

【问题讨论】:

    标签: c# sql sql-server csv


    【解决方案1】:

    您的问题很简单,但我会更进一步,让您知道解决问题的更好方法。

    当您有问题要出售时,请始终将其分解为多个部分,并以各自的方法应用每个部分。例如,在您的情况下:

    • 1 - 从文件中读取
    • 2 - 创建一个 sql 查询
    • 3 - 运行查询

    您甚至可以向文件添加验证(想象一下您的文件在一行或多行中甚至没有 7 个字段...),并且只有在您的文件永远不会超过 500 行,就好像它通常一样,您应该考虑使用 SQL 语句将您的文件直接输入数据库,它被称为 bulk insert

    1 - 从文件中读取:

    我会使用List&lt;string&gt; 来保存行条目,并且我总是使用StreamReader 来读取文本文件。

                using (StreamReader sr = File.OpenText(this.CsvPath))
                {
                    while ((line = sr.ReadLine()) != null)
                    {
                        splittedLine = line.Split(new string[] { this.Separator }, StringSplitOptions.None);
    
                        if (iLine == 0 && this.HasHeader)
                            // header line
                            this.Header = splittedLine;
                        else
                            this.Lines.Add(splittedLine);
    
                        iLine++;
                    }
                }
    

    2 - 生成 sql

            foreach (var line in this.Lines)
            {
                string entries = string.Concat("'", string.Join("','", line))
                                       .TrimEnd('\'').TrimEnd(','); // remove last ",'" 
    
                this.Query.Add(string.Format(this.LineTemplate, entries));
            }
    

    3 - 运行查询

    SqlCommand sql = new SqlCommand(string.Join("", query), mysqlconnectionstring);
    sql.ExecuteNonQuery();
    

    有一些乐趣我最终做了解决方案,你可以在这里下载它,输出是:

    The code can be found here。它需要更多的调整,但我会把它留给其他人。用 C#,VS 2013 编写的解决方案。

    ExtractCsvIntoSql 类如下:

    public class ExtractCsvIntoSql
    {
        private string CsvPath, Separator;
        private bool HasHeader;
        private List<string[]> Lines;
        private List<string> Query;
    
        /// <summary>
        /// Header content of the CSV File
        /// </summary>
        public string[] Header { get; private set; }
    
        /// <summary>
        /// Template to be used in each INSERT Query statement
        /// </summary>
        public string LineTemplate { get; set; }
    
        public ExtractCsvIntoSql(string csvPath, string separator, bool hasHeader = false)
        {
            this.CsvPath = csvPath;
            this.Separator = separator;
            this.HasHeader = hasHeader;
            this.Lines = new List<string[]>();
    
            // you can also set this
            this.LineTemplate = "INSERT INTO [table1] SELECT ({0});";
        }
    
        /// <summary>
        /// Generates the SQL Query
        /// </summary>
        /// <returns></returns>
        public List<string> Generate()
        {
            if(this.CsvPath == null)
                throw new ArgumentException("CSV Path can't be empty");
    
            // extract csv into object
            Extract();
            // generate sql query 
            GenerateQuery();
    
            return this.Query;
        }
    
        private void Extract()
        {
            string line;
            string[] splittedLine;
            int iLine = 0;
    
            try
            {
                using (StreamReader sr = File.OpenText(this.CsvPath))
                {
                    while ((line = sr.ReadLine()) != null)
                    {
                        splittedLine = line.Split(new string[] { this.Separator }, StringSplitOptions.None);
    
                        if (iLine == 0 && this.HasHeader)
                            // header line
                            this.Header = splittedLine;
                        else
                            this.Lines.Add(splittedLine);
    
                        iLine++;
                    }
                }
            }
            catch (Exception ex)
            {
                if(ex.InnerException != null)
                    while (ex.InnerException != null)
                        ex = ex.InnerException;
    
                throw ex;
            }
    
            // Lines will have all rows and each row, the column entry
        }
    
        private void GenerateQuery()
        {
            foreach (var line in this.Lines)
            {
                string entries = string.Concat("'", string.Join("','", line))
                                       .TrimEnd('\'').TrimEnd(','); // remove last ",'" 
    
                this.Query.Add(string.Format(this.LineTemplate, entries));
            }
        }
    }
    

    你可以这样运行它:

    class Program
    {
        static void Main(string[] args)
        {
            string file = Ask("What is the CSV file path? (full path)");
            string separator = Ask("What is the current separator? (; or ,)");
    
            var extract = new ExtractCsvIntoSql(file, separator);
            var sql = extract.Generate();
    
            Output(sql);
        }
    
        private static void Output(IEnumerable<string> sql)
        {
            foreach(var query in sql)
                Console.WriteLine(query);
    
            Console.WriteLine("*******************************************");
            Console.Write("END ");
            Console.ReadLine();
        }
    
        private static string Ask(string question)
        {
            Console.WriteLine("*******************************************");
            Console.WriteLine(question);
            Console.Write("= ");
            return Console.ReadLine();
        }
    }
    

    【讨论】:

    • 男人!!这么大的答案:)
    • 感谢您的回答。我一定会测试一下。我遇到的问题是某些字段中有逗号,因此它扰乱了我的整个 csv 阅读。
    • 因此我提到了验证...如果您测试必须是您想要的splittedLine.Length,很容易得到该错误。在Extract() 方法内部。
    • 我在这一行得到一个错误:this.Query.Add(string.Format(this.LineTemplate, entries)); 错误:Object reference not set to an instance of an object。我在想沿着这条线的某个地方我必须添加连接字符串,但不确定在哪里......
    • 如果调试,可以看到在添加对象之前,Query属性还没有初始化,所以还是null。您还需要学习如何调试代码!我在提议时忘记了。只需在构造函数中添加this.Query = new List&lt;string&gt;();,在this.Lines = new List&lt;string[]&gt;(); 之后...
    【解决方案2】:

    通常我喜欢更通用一点,所以我会尝试解释我不时使用的一个非常基本的流程:

    我不喜欢硬编码的态度,所以即使您的代码可以工作,它也会专门用于一种类型。我更喜欢简单的反思,首先了解它是什么 DTO,然后了解我应该使用什么存储库来操作它:

    例如:

    public class ImportProvider
    {
        private readonly string _path;
        private readonly ObjectResolver _objectResolver;
    
        public ImportProvider(string path)
        {
            _path = path;
            _objectResolver = new ObjectResolver();
        }
    
        public void Import()
        {
            var filePaths = Directory.GetFiles(_path, "*.csv");
            foreach (var filePath in filePaths)
            {
                var fileName = Path.GetFileName(filePath);
                var className = fileName.Remove(fileName.Length-4);
                using (var reader = new CsvFileReader(filePath))
                {
                    var row = new CsvRow();
                    var repository = (DaoBase)_objectResolver.Resolve("DAL.Repository", className + "Dao");
                    while (reader.ReadRow(row))
                    {
                        var dtoInstance = (DtoBase)_objectResolver.Resolve("DAL.DTO", className + "Dto");
                        dtoInstance.FillInstance(row.ToArray());
                        repository.Save(dtoInstance);
                    }
                }
            }
        }
    }
    

    上面是一个非常基本的类,负责导入数据。不过这段代码如何解析CSV文件(CsvFileReader),重要的是“CsvRow”是一个简单的List。

    下面是ObjectResolver的实现:

    public class ObjectResolver
    {
        private readonly Assembly _myDal;
        public ObjectResolver()
        {
           _myDal = Assembly.Load("DAL");
    
        }
        public object Resolve(string nameSpace,  string name)
        {
            var myLoadClass = _myDal.GetType(nameSpace + "." + name); 
            return Activator.CreateInstance(myLoadClass);
        }
    
    }
    

    这个想法是简单地遵循命名约定,在我的例子中,使用“Dto”后缀来反映实例,使用“Dao”后缀来反映负责的 dao。 Dto 或 Dao 的全名可以取自 csv 名称或标题(如您所愿)

    下一步是填充 Dto,每个 dto 或实现以下简单抽象:

    public abstract class DtoBase
    {
        public abstract void FillInstance(params string[] parameters);
    }
    

    由于每个Dto“知道”他的结构(就像你知道在数据库中创建一个合适的表一样),它可以很容易地实现FillInstanceMethod,这里是一个简单的Dto示例:

    public class ProductDto : DtoBase
    {
        public int ProductId { get; set; }
        public double Weight { get; set; }
        public int FamilyId { get; set; }
    
        public override void FillInstance(params string[] parameters)
        {
            ProductId = int.Parse(parameters[0]);
            Weight = double.Parse(parameters[1]);
            FamilyId = int.Parse(parameters[2]);
        }
    }
    

    在你的 Dto 填充数据之后,你应该找到合适的 Dao 来处理它 这基本上发生在 Import() 方法的这一行中的反射中:

    var repository = (DaoBase)_objectResolver.Resolve("DAL.Repository", className + "Dao");
    

    在我的例子中,Dao 实现了一个抽象基类——但这与您的问题无关,您的 DaoBase 可以是一个带有单个 Save() 方法的简单抽象。 这样你就有了一个专门的 Dao 来 CRUD 你的 Dto - 每个 Dao 都知道如何为它的相关 Dto 保存。下面是 ProductDto 对应的 ProductDao:

    public class ProductDao : DaoBase
    {
        private const string InsertProductQuery = @"SET foreign_key_checks = 0;
                                                    Insert into product (productID, weight, familyID)
                                                    VALUES (@productId, @weight, @familyId);
                                                    SET foreign_key_checks = 1;";
    
    
        public override void Save(DtoBase dto)
        {
            var productToSave = dto as ProductDto;
            var saveproductCommand = GetDbCommand(InsertProductQuery);
            if (productToSave != null)
            {
                saveproductCommand.Parameters.Add(CreateParameter("@productId", productToSave.ProductId));
                saveproductCommand.Parameters.Add(CreateParameter("@weight", productToSave.Weight));
                saveproductCommand.Parameters.Add(CreateParameter("@familyId", productToSave.FamilyId));
                ExecuteNonQuery(ref saveproductCommand);
            }
    
        }
    }
    

    请忽略 CreateParameter() 方法,因为它是基类的抽象。您可以只使用 CreateSqlParameter 或 CreateDataParameter 等。

    请注意,这是一个真正幼稚的实现 - 您可以轻松地对其进行更好的改造,具体取决于您的需求。

    【讨论】:

      【解决方案3】:

      从您对问题的第一印象来看,我猜您将拥有大量记录(超过 lacs)。如果是,我会考虑 SQL 批量复制一个选项。如果记录会更少,请继续单记录。插入。您插入不起作用的原因是您没有提供表的所有列,并且还有一些语法错误。

      【讨论】:

      • 请提供一个正确的答案,不要说“有一些语法错误”指出在哪里以及如何解决这个问题......这是一个答案。你写的应该是一个简单的评论。
      猜你喜欢
      • 1970-01-01
      • 2020-06-22
      • 2020-03-15
      • 1970-01-01
      • 2023-04-07
      • 1970-01-01
      • 2018-03-19
      • 2018-08-15
      • 1970-01-01
      相关资源
      最近更新 更多