วิธีใดเป็นวิธีที่ดีที่สุดในการนำเข้าไฟล์ CSV ไปยังโครงสร้างข้อมูลที่พิมพ์มากเกินไป
วิธีใดเป็นวิธีที่ดีที่สุดในการนำเข้าไฟล์ CSV ไปยังโครงสร้างข้อมูลที่พิมพ์มากเกินไป
คำตอบ:
TextFieldParserของ Microsoft มีความเสถียรและเป็นไปตามRFC 4180สำหรับไฟล์ CSV อย่าถูกMicrosoft.VisualBasic
เนมสเปซเลื่อนออกไป เป็นส่วนประกอบมาตรฐานใน. NET Framework เพียงเพิ่มการอ้างอิงไปยังMicrosoft.VisualBasic
แอสเซมบลีส่วนกลาง
หากคุณกำลังคอมไพล์สำหรับ Windows (ซึ่งตรงข้ามกับ Mono) และไม่คิดว่าจะต้องแยกวิเคราะห์ไฟล์ CSV ที่ "เสีย" (ไม่สอดคล้องกับ RFC) นี่จะเป็นตัวเลือกที่ชัดเจนเนื่องจากเป็นไฟล์ฟรีไม่ จำกัด และเสถียร และได้รับการสนับสนุนอย่างแข็งขันซึ่งส่วนใหญ่ไม่สามารถพูดได้สำหรับ FileHelpers
ดูเพิ่มเติม: วิธีการ: อ่านจากไฟล์ข้อความที่คั่นด้วยจุลภาคใน Visual Basicสำหรับตัวอย่างโค้ด VB
TextFieldParser
จะทำงานให้คั่นด้วยแท็บและอื่น ๆ ที่แปลก cruft Excel สร้างเกินไป ฉันตระหนักดีว่าคำตอบก่อนหน้าของคุณไม่ได้อ้างว่าห้องสมุด VB-เฉพาะก็แค่มาข้ามให้ฉันเป็นหมายความว่ามันเป็นจริงความหมายสำหรับ VB และไม่ตั้งใจที่จะใช้จาก C # ซึ่งผมไม่คิดว่าเป็น ในกรณีนี้ - มีคลาสที่มีประโยชน์มากใน MSVB
ใช้การเชื่อมต่อ OleDB
String sConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\InputDirectory\\;Extended Properties='text;HDR=Yes;FMT=Delimited'";
OleDbConnection objConn = new OleDbConnection(sConnectionString);
objConn.Open();
DataTable dt = new DataTable();
OleDbCommand objCmdSelect = new OleDbCommand("SELECT * FROM file.csv", objConn);
OleDbDataAdapter objAdapter1 = new OleDbDataAdapter();
objAdapter1.SelectCommand = objCmdSelect;
objAdapter1.Fill(dt);
objConn.Close();
หากคุณกำลังคาดหวังว่าสถานการณ์ที่ซับซ้อนอย่างเป็นธรรมสำหรับการแยก CSV, ไม่ได้คิดขึ้นจากการกลิ้งตัวแยกวิเคราะห์ของเราเอง มีจำนวนมากของเครื่องมือที่ยอดเยี่ยมออกมีเช่นFileHelpersหรือคนแม้จะมาจากCodeProject
ประเด็นนี้เป็นปัญหาที่พบได้บ่อยและคุณสามารถเดิมพันได้ว่านักพัฒนาซอฟต์แวร์จำนวนมากได้คิดและแก้ไขปัญหานี้แล้ว
Brian เป็นทางออกที่ดีสำหรับการแปลงเป็นคอลเลกชันที่พิมพ์มาก
วิธีการแยกวิเคราะห์ CSV ส่วนใหญ่ที่ระบุไม่ได้คำนึงถึงการหลีกเลี่ยงช่องหรือรายละเอียดปลีกย่อยอื่น ๆ ของไฟล์ CSV (เช่นช่องการตัดแต่ง) นี่คือรหัสที่ฉันใช้เป็นการส่วนตัว มันค่อนข้างหยาบรอบ ๆ ขอบและไม่มีการรายงานข้อผิดพลาดเลย
public static IList<IList<string>> Parse(string content)
{
IList<IList<string>> records = new List<IList<string>>();
StringReader stringReader = new StringReader(content);
bool inQoutedString = false;
IList<string> record = new List<string>();
StringBuilder fieldBuilder = new StringBuilder();
while (stringReader.Peek() != -1)
{
char readChar = (char)stringReader.Read();
if (readChar == '\n' || (readChar == '\r' && stringReader.Peek() == '\n'))
{
// If it's a \r\n combo consume the \n part and throw it away.
if (readChar == '\r')
{
stringReader.Read();
}
if (inQoutedString)
{
if (readChar == '\r')
{
fieldBuilder.Append('\r');
}
fieldBuilder.Append('\n');
}
else
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
records.Add(record);
record = new List<string>();
inQoutedString = false;
}
}
else if (fieldBuilder.Length == 0 && !inQoutedString)
{
if (char.IsWhiteSpace(readChar))
{
// Ignore leading whitespace
}
else if (readChar == '"')
{
inQoutedString = true;
}
else if (readChar == ',')
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
}
else
{
fieldBuilder.Append(readChar);
}
}
else if (readChar == ',')
{
if (inQoutedString)
{
fieldBuilder.Append(',');
}
else
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
}
}
else if (readChar == '"')
{
if (inQoutedString)
{
if (stringReader.Peek() == '"')
{
stringReader.Read();
fieldBuilder.Append('"');
}
else
{
inQoutedString = false;
}
}
else
{
fieldBuilder.Append(readChar);
}
}
else
{
fieldBuilder.Append(readChar);
}
}
record.Add(fieldBuilder.ToString().TrimEnd());
records.Add(record);
return records;
}
โปรดทราบว่าสิ่งนี้ไม่ได้จัดการกรณีขอบของเขตข้อมูลที่ไม่ได้ถูกคั่นด้วยเครื่องหมายคำพูดคู่ แต่ meerley มีสตริงที่ยกมาอยู่ข้างใน ดูโพสต์นี้เพื่อดูการขยายที่ดีขึ้นและลิงก์ไปยังไลบรารีที่เหมาะสมบางส่วน
ผมเห็นด้วยกับ @ NotMyself FileHelpersได้รับการทดสอบอย่างดีและจัดการกับเคสขอบทุกประเภทที่คุณจะต้องจัดการในที่สุดหากคุณทำด้วยตัวเอง ลองดูว่า FileHelpers ทำอะไรและเขียนของคุณเองก็ต่อเมื่อคุณแน่ใจจริงๆว่า (1) คุณไม่จำเป็นต้องจัดการกับกรณีขอบ FileHelpers หรือ (2) คุณชอบเขียนสิ่งประเภทนี้และกำลังจะ มีความสุขมากเมื่อคุณต้องแยกวิเคราะห์สิ่งต่างๆเช่นนี้:
1, "Bill", "Smith", "Supervisor", "No Comment"
2, 'Drake,', 'O'Malley', "ภารโรง,
อ๊ะฉันไม่ได้ยกมา แต่ฉันขึ้นบรรทัดใหม่!
ฉันรู้สึกเบื่อดังนั้นฉันจึงแก้ไขบางสิ่งที่ฉันเขียน พยายามที่จะห่อหุ้มการแยกวิเคราะห์ในลักษณะ OO เพื่อลดจำนวนการวนซ้ำผ่านไฟล์โดยจะวนซ้ำเพียงครั้งเดียวที่ foreach ด้านบน
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// usage:
// note this wont run as getting streams is not Implemented
// but will get you started
CSVFileParser fileParser = new CSVFileParser();
// TO Do: configure fileparser
PersonParser personParser = new PersonParser(fileParser);
List<Person> persons = new List<Person>();
// if the file is large and there is a good way to limit
// without having to reparse the whole file you can use a
// linq query if you desire
foreach (Person person in personParser.GetPersons())
{
persons.Add(person);
}
// now we have a list of Person objects
}
}
public abstract class CSVParser
{
protected String[] deliniators = { "," };
protected internal IEnumerable<String[]> GetRecords()
{
Stream stream = GetStream();
StreamReader reader = new StreamReader(stream);
String[] aRecord;
while (!reader.EndOfStream)
{
aRecord = reader.ReadLine().Split(deliniators,
StringSplitOptions.None);
yield return aRecord;
}
}
protected abstract Stream GetStream();
}
public class CSVFileParser : CSVParser
{
// to do: add logic to get a stream from a file
protected override Stream GetStream()
{
throw new NotImplementedException();
}
}
public class CSVWebParser : CSVParser
{
// to do: add logic to get a stream from a web request
protected override Stream GetStream()
{
throw new NotImplementedException();
}
}
public class Person
{
public String Name { get; set; }
public String Address { get; set; }
public DateTime DOB { get; set; }
}
public class PersonParser
{
public PersonParser(CSVParser parser)
{
this.Parser = parser;
}
public CSVParser Parser { get; set; }
public IEnumerable<Person> GetPersons()
{
foreach (String[] record in this.Parser.GetRecords())
{
yield return new Person()
{
Name = record[0],
Address = record[1],
DOB = DateTime.Parse(record[2]),
};
}
}
}
}
มีสองบทความใน CodeProject ที่ให้สำหรับวิธีการแก้ปัญหาหนึ่งที่ใช้เป็นStreamReaderและหนึ่งที่นำเข้า CSV ข้อมูลโดยใช้ไดร์เวอร์ข้อความไมโครซอฟท์
วิธีง่ายๆที่ดีในการทำคือเปิดไฟล์และอ่านแต่ละบรรทัดลงในอาร์เรย์รายการที่เชื่อมโยงโครงสร้างข้อมูลที่คุณเลือก ระมัดระวังเกี่ยวกับการจัดการบรรทัดแรกแม้ว่า
นี้อาจจะเหนือหัวของคุณ แต่ดูเหมือนว่าจะมีทางตรงในการเข้าถึงพวกเขาเช่นกันโดยใช้สายเชื่อมต่อ
ทำไมไม่ลองใช้ Python แทน C # หรือ VB ล่ะ? มีโมดูล CSV ที่ดีในการนำเข้าซึ่งจะช่วยยกของหนักทั้งหมดให้กับคุณ
ฉันต้องใช้ตัวแยกวิเคราะห์ CSV ใน. NET สำหรับโปรเจ็กต์ในช่วงฤดูร้อนนี้และตัดสินใน Microsoft Jet Text Driver คุณระบุโฟลเดอร์โดยใช้สตริงการเชื่อมต่อจากนั้นค้นหาไฟล์โดยใช้คำสั่ง SQL Select คุณสามารถระบุประเภทที่รัดกุมโดยใช้ไฟล์ schema.ini ฉันไม่ได้ทำในตอนแรก แต่แล้วฉันก็ได้รับผลลัพธ์ที่ไม่ดีโดยที่ประเภทของข้อมูลไม่ปรากฏในทันทีเช่นหมายเลข IP หรือรายการเช่น "XYQ 3.9 SP1"
ข้อ จำกัด อย่างหนึ่งที่ฉันพบคือไม่สามารถจัดการชื่อคอลัมน์ที่สูงกว่า 64 อักขระได้ มันตัดทอน นี่ไม่น่าจะเป็นปัญหายกเว้นว่าฉันกำลังจัดการกับข้อมูลอินพุตที่ออกแบบมาไม่ดี ส่งคืนชุดข้อมูล ADO.NET
นี่เป็นทางออกที่ดีที่สุดที่ฉันพบ ฉันจะระมัดระวังในการเปลี่ยนโปรแกรมแยกวิเคราะห์ CSV ของตัวเองเนื่องจากฉันอาจจะพลาดบางกรณีและฉันไม่พบแพ็คเกจการแยกวิเคราะห์ CSV ฟรีสำหรับ. NET
แก้ไข: นอกจากนี้ยังสามารถมีไฟล์ schema.ini ได้เพียงไฟล์เดียวต่อไดเรกทอรีดังนั้นฉันจึงต่อท้ายแบบไดนามิกเพื่อพิมพ์คอลัมน์ที่จำเป็นอย่างมาก โดยจะพิมพ์เฉพาะคอลัมน์ที่ระบุอย่างชัดเจนและอนุมานสำหรับฟิลด์ที่ไม่ได้ระบุ ฉันชื่นชมสิ่งนี้มากเนื่องจากฉันกำลังจัดการกับการนำเข้า CSV ที่เป็นของเหลว 70+ คอลัมน์และไม่ต้องการระบุแต่ละคอลัมน์มีเพียงคอลัมน์ที่ทำงานไม่ดีเท่านั้น
ฉันพิมพ์รหัส ผลลัพธ์ใน datagridviewer ดูดี จะแยกวิเคราะห์ข้อความบรรทัดเดียวไปยังรายการอาร์เรย์ของวัตถุ
enum quotestatus
{
none,
firstquote,
secondquote
}
public static System.Collections.ArrayList Parse(string line,string delimiter)
{
System.Collections.ArrayList ar = new System.Collections.ArrayList();
StringBuilder field = new StringBuilder();
quotestatus status = quotestatus.none;
foreach (char ch in line.ToCharArray())
{
string chOmsch = "char";
if (ch == Convert.ToChar(delimiter))
{
if (status== quotestatus.firstquote)
{
chOmsch = "char";
}
else
{
chOmsch = "delimiter";
}
}
if (ch == Convert.ToChar(34))
{
chOmsch = "quotes";
if (status == quotestatus.firstquote)
{
status = quotestatus.secondquote;
}
if (status == quotestatus.none )
{
status = quotestatus.firstquote;
}
}
switch (chOmsch)
{
case "char":
field.Append(ch);
break;
case "delimiter":
ar.Add(field.ToString());
field.Clear();
break;
case "quotes":
if (status==quotestatus.firstquote)
{
field.Clear();
}
if (status== quotestatus.secondquote)
{
status =quotestatus.none;
}
break;
}
}
if (field.Length != 0)
{
ar.Add(field.ToString());
}
return ar;
}
หากคุณสามารถรับประกันได้ว่ามีเครื่องหมายจุลภาคในข้อมูลไม่มีแล้ววิธีที่ง่ายที่สุดอาจจะใช้String.split
ตัวอย่างเช่น:
String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);
อาจมีไลบรารีที่คุณสามารถใช้เพื่อช่วยได้ แต่อาจจะง่ายอย่างที่คุณจะได้รับ ตรวจสอบให้แน่ใจว่าคุณไม่มีเครื่องหมายจุลภาคในข้อมูลมิฉะนั้นคุณจะต้องแยกวิเคราะห์ให้ดีขึ้น