C # kullanarak CSV dosyalarını okuma


171

Basit bir ithalat uygulaması yazıyorum ve bir CSV dosyasını okumak, sonucu DataGridgöstermek ve başka bir kılavuzda CSV dosyasının bozuk satırlarını göstermek gerekiyor. Örneğin, başka bir ızgarada 5 değerden daha kısa satırları gösterin. Bunu böyle yapmaya çalışıyorum:

StreamReader sr = new StreamReader(FilePath);
importingData = new Account();
string line;
string[] row = new string [5];
while ((line = sr.ReadLine()) != null)
{
    row = line.Split(',');

    importingData.Add(new Transaction
    {
        Date = DateTime.Parse(row[0]),
        Reference = row[1],
        Description = row[2],
        Amount = decimal.Parse(row[3]),
        Category = (Category)Enum.Parse(typeof(Category), row[4])
    });
}

ancak bu durumda dizilerde çalışmak çok zordur. Değerleri bölmenin daha iyi bir yolu var mı?


Çözümünüz için teşekkürler. Bir cevap postası olarak göndermeyi düşünün - soruya eklemek okunabilirliğine yardımcı olmaz.
BartoszKP

Yanıtlar:


364

Tekerleği yeniden icat etme. Zaten .NET BCL'de olanlardan yararlanın.

  • bir başvuru ekleyin Microsoft.VisualBasic(evet, VisualBasic diyor ama C # da çalışıyor - sonunda tüm IL olduğunu unutmayın)
  • Microsoft.VisualBasic.FileIO.TextFieldParserCSV dosyasını ayrıştırmak için sınıfı kullanma

İşte örnek kod:

using (TextFieldParser parser = new TextFieldParser(@"c:\temp\test.csv"))
{
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    while (!parser.EndOfData) 
    {
        //Processing row
        string[] fields = parser.ReadFields();
        foreach (string field in fields) 
        {
            //TODO: Process field
        }
    }
}

C # projelerimde benim için harika çalışıyor.

İşte bazı bağlantılar / bilgiler:


18
GERÇEKTEN VB kütüphaneleri kullanmayan bir yol olsaydı, ama bu mükemmel çalıştı! Teşekkür ederim!
gillonba

5
+1: lumenworks Fast CSV okuyucuyu 53Mb'lik bir dosyada kırdım. Görünüşe göre satır önbelleği 43.000 satırdan sonra başarısız oldu ve arabelleği karıştırdı. VB'yi denedi TextFieldParserve hile yaptı. Teşekkürler
Gone Kodlama

10
+1 Çok cevap, çünkü birçok insanın bu sınıfın var olduğunu bilmediğini fark ediyorum. Gelecekteki görüntüleyenlerin not etmesi gereken bir şey , yöntem özelliği sizin için ayarladığından, parser.TextFieldType = FieldType.Delimited;çağırırsanız ayarın gerekli olmamasıdır . parser.SetDelimiters(",");TextFieldType
Brian

10
Ayrıca şunu da kontrol edin: dotnetperls.com/textfieldparser . TextFieldParser, String.Split ve StreamReader'dan daha kötü performansa sahiptir. Ancak, string.Split ve TextFieldParser arasında büyük bir fark vardır. TextFieldParser bir sütunda virgül kullanmak gibi garip durumları işler: gibi bir sütunu adlandırabilir "text with quote"", and comma"ve text with quote", and commayanlış ayrılmış değerler yerine doğru değeri alabilirsiniz . Eğer csv çok basitse Yani String.Split tercih etmek isteyebilirsiniz.
Yongwei Wu

5
Bunu kullanmak için Microsoft.VisualBasic'e bir başvuru eklemeniz gerekebilir. Visual Studio'da projenizi sağ tıklatın, sonra Ekle> Başvuru'yu seçin ve Microsoft.VisualBasic kutusunu işaretleyin.
Derek Kurth

37

Deneyimlerim, birçok farklı csv formatı olması. Özellikle bir alandaki tırnak işaretleri ve sınırlayıcılardan kaçmayı nasıl başardıkları.

Bunlar karşılaştığım varyantlar:

  • alıntılar alıntılanır ve ikiye katlanır (excel) yani 15 "-> alan1," 15 "" ", alan3
  • alan başka bir nedenden dolayı alıntı yapılmadığı sürece tırnak işaretleri değiştirilmez. yani 15 "-> alan1,15", alanlar3
  • tırnak işaretleri \ ile kaçar. yani 15 "-> alan1," 15 \ "", alan3
  • tırnak işaretleri hiç değiştirilmez (bu doğru şekilde ayrıştırmak her zaman mümkün değildir)
  • sınırlayıcı alıntılanır (excel). yani a, b -> alan1, "a, b", alan3
  • sınırlayıcı \ ile kaçtı. yani a, b -> alan1, a \, b, alan3

Mevcut csv ayrıştırıcılarının çoğunu denedim ama karşılaştığım varyantları işleyebilecek tek bir tane yok. Ayrıştırıcıların desteklediği değişkenlerden kaçan dokümantasyondan da bulmak zordur.

Projelerimde artık VB TextFieldParser veya özel bir ayırıcı kullanıyorum.


1
Verdiğiniz test senaryoları için bu cevabı seviyorum!
Matthew Rodatus

2
Temel sorun, uygulamaların çoğunun CSV formatını ve sınırlayıcılardan nasıl kaçılması gerektiğini tanımlayan RFC 4180'i umursamamasıdır.
Jenny O'Reilly

RFC-4180 2005'ten beri, şimdi eski gibi görünüyor, ancak unutmayın: .Net çerçevesi ilk olarak 2001'de çıktı. Ayrıca, RFC'ler her zaman resmi standartlar değildir ve bu durumda, , ISO-8601 veya RFC-761.
Joel Coehoorn

23

Ben tavsiye Nuget dan CsvHelper .

(Microsoft.VisualBasic'e bir başvuru eklemek doğru gelmiyor, sadece çirkin değil, muhtemelen çapraz platform da değil.)


2
Tam olarak C # kadar platformlar arası.
PRMan

yanlış, Linux'ta Microsoft.VisualBasic.dll, Microsoft'tan farklı bir uygulamaya sahip olan Mono kaynaklarından gelir ve uygulanmayan bazı şeyler vardır, örneğin: stackoverflow.com/questions/6644165/…
knocte

(Ayrıca, VB dili hiçbir zaman Mono projesini oluşturma / geliştirme sürecine dahil olan şirketler altında hiç odaklanmadı, bu yüzden C # ekosistem /
araçlara

1
Her ikisiyle de oynadıktan sonra, CsvHelpersınıf haritacısına yerleşik bir satırla birlikte gelir; (varsa) sütun başlıklarında değişikliklere ve hatta sütun düzeninde görünüşte varyasyonlara izin verir (gerçi kendimi test etmedim). Sonuçta daha çok "üst düzey" hissediyor TextFieldParser.
David

1
Evet, Microsoft.VisualBasic ad alanı .NET Core 2.1
N4ppeL

13

Bazen tekerleği yeniden icat etmek istemediğinizde kütüphaneleri kullanmak iyidir, ancak bu durumda aynı işi daha az kod satırı ile yapabilir ve kütüphaneleri kullanmaya kıyasla daha kolay okunur. İşte çok kolay bulduğum farklı bir yaklaşım.

  1. Bu örnekte, dosyayı okumak için StreamReader kullanıyorum
  2. Her satırdan ayırıcıyı saptamak için normal ifadeyi kullanın.
  3. 0'dan n dizine kadar sütunları toplamak için bir dizi

using (StreamReader reader = new StreamReader(fileName))
    {
        string line; 

        while ((line = reader.ReadLine()) != null)
        {
            //Define pattern
            Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");

            //Separating columns to array
            string[] X = CSVParser.Split(line);

            /* Do something with X */
        }
    }

4
Elbette bu yeni satırlar içeren verilerle ilgili sorunlara sahip mi?
Doogal

Artık CSV veri dosyaları veriler arasında boş satırlar içerdiğini bilmiyorlar, ancak bunu yapan bir kaynağınız varsa, bu durumda basitçe okuyucuyu çalıştırmadan önce beyaz boşlukları veya hiçbir şey içermeyen satırları kaldırmak için basit bir regex testi yapardım. farklı örnekler için buraya bakın: stackoverflow.com/questions/7647716/…
Mana

1
Şüphesiz, bu tür bir sorun için karakter temelli bir yaklaşım normalden daha doğaldır. Tırnak işaretlerinin mevcudiyetine bağlı olarak davranışın farklı olması beklenir.
Casey

6

CSV karmaşık gerçek hızlı alabilirsiniz .

Sağlam ve iyi test edilmiş bir şey kullanın:
FileHelpers: www.filehelpers.net

FileHelpers, dosyalar, dizeler veya akışlardaki sabit uzunluktaki veya sınırlandırılmış kayıtlardan veri almak / vermek için ücretsiz ve kullanımı kolay bir .NET kütüphanesidir.


5
Bence FileHelper bir seferde çok şey yapmaya çalışıyor. Dosyaları ayrıştırma, satırları önce alanlara böldüğünüz ve sonra alanları verilere ayrıştırdığınız 2 adımlı bir işlemdir. İşlevleri birleştirmek, ana ayrıntı ve satır filtreleme gibi şeyleri işlemeyi zorlaştırır.
adrianm


4

Bu listeye bir başka, Cinchoo ETL - CSV dosyalarını okumak ve yazmak için açık kaynaklı bir kütüphane

Aşağıdaki örnek bir CSV dosyası için

Id, Name
1, Tom
2, Mark

Hızlı bir şekilde aşağıdaki gibi kitaplığı kullanarak yükleyebilirsiniz

using (var reader = new ChoCSVReader("test.csv").WithFirstLineHeader())
{
   foreach (dynamic item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

CSV dosyasıyla eşleşen POCO sınıfınız varsa

public class Employee
{
   public int Id { get; set; }
   public string Name { get; set; }
}

CSV dosyasını aşağıdaki gibi yüklemek için kullanabilirsiniz

using (var reader = new ChoCSVReader<Employee>("test.csv").WithFirstLineHeader())
{
   foreach (var item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

Lütfen CodeProject'in nasıl kullanılacağına dair makalelere göz atın .

Feragatname: Bu kütüphanenin yazarıyım


Merhaba, Sql tablosuna csv yükleyebilir miyim - CSV tablosundaki başlığı önceden bilmiyorum. Sadece Sql tablo için csv whats ayna
aggie 15:18

Evet yapabilirsin. lütfen bu bağlantıya bakın stackoverflow.com/questions/20759302/…
RajN

2
private static DataTable ConvertCSVtoDataTable(string strFilePath)
        {
            DataTable dt = new DataTable();
            using (StreamReader sr = new StreamReader(strFilePath))
            {
                string[] headers = sr.ReadLine().Split(',');
                foreach (string header in headers)
                {
                    dt.Columns.Add(header);
                }
                while (!sr.EndOfStream)
                {
                    string[] rows = sr.ReadLine().Split(',');
                    DataRow dr = dt.NewRow();
                    for (int i = 0; i < headers.Length; i++)
                    {
                        dr[i] = rows[i];
                    }
                    dt.Rows.Add(dr);
                }

            }

            return dt;
        }

        private static void WriteToDb(DataTable dt)
        {
            string connectionString =
                "Data Source=localhost;" +
                "Initial Catalog=Northwind;" +
                "Integrated Security=SSPI;";

            using (SqlConnection con = new SqlConnection(connectionString))
                {
                    using (SqlCommand cmd = new SqlCommand("spInsertTest", con))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;

                        cmd.Parameters.Add("@policyID", SqlDbType.Int).Value = 12;
                        cmd.Parameters.Add("@statecode", SqlDbType.VarChar).Value = "blagh2";
                        cmd.Parameters.Add("@county", SqlDbType.VarChar).Value = "blagh3";

                        con.Open();
                        cmd.ExecuteNonQuery();
                    }
                }

         }

bu çözümü nereden kopyaladın?
MindRoasterMir

0

Her şeyden önce CSV'nin ne olduğunu ve nasıl yazıldığını anlamalıdır.

  1. Sonraki her dize ( /r/n) sonraki "tablo" satırıdır.
  2. "Tablo" hücreleri bazı sınırlayıcı sembolleriyle ayrılır. En sık kullanılan semboller \tveya,
  3. Her hücre muhtemelen bu sınırlayıcı sembolünü içerebilir (hücre tırnak işaretiyle başlamalı ve bu durumda bu sembolle bitmelidir)
  4. Her hücre muhtemelen /r/nsemboller içerebilir (hücre tırnak simgesi ile başlamalı ve bu durumda bu sembolle bitmelidir)

C # / Visual Basic'in CSV dosyalarıyla çalışmasının en kolay yolu standart Microsoft.VisualBasickitaplık kullanmaktır . Sadece sınıfınıza gerekli referansı ve aşağıdaki dizeyi eklemeniz gerekir:

using Microsoft.VisualBasic.FileIO;

Evet, C # ile kullanabilirsiniz, endişelenmeyin. Bu kütüphane nispeten büyük dosyaları okuyabilir ve gerekli tüm kuralları destekler, böylece tüm CSV dosyalarıyla çalışabilirsiniz.

Bir süre önce bu kütüphaneye dayanarak CSV okuma / yazma için basit bir sınıf yazmıştım. Bu basit sınıfı kullanarak 2 boyutlu dizide olduğu gibi CSV ile çalışabileceksiniz. Sınıfımı aşağıdaki bağlantıdan bulabilirsiniz: https://github.com/ukushu/DataExporter

Basit kullanım örneği:

Csv csv = new Csv("\t");//delimiter symbol

csv.FileOpen("c:\\file1.csv");

var row1Cell6Value = csv.Rows[0][5];

csv.AddRow("asdf","asdffffff","5")

csv.FileSave("c:\\file2.csv");

0

Önceki yanıtları tamamlamak için CSV Dosyasından TextFieldParserveya string.Splityöntemle ayrıştırılan bir nesne koleksiyonuna ihtiyaç duyulabilir ve ardından her satır Yansıma yoluyla bir nesneye dönüştürülür. Öncelikle CSV dosyasının satırlarıyla eşleşen bir sınıf tanımlamanız gerekir.

Ben burada bulunan Michael Kropat basit CSV Serializer kullandım: Genel sınıf CSV (tüm özellikler) ve diledi sınıfın alanlarını ve özelliklerini almak için yöntemlerini yeniden.

Aşağıdaki yöntemle CSV dosyamın serisini kaldırıyorum:

public static IEnumerable<T> ReadCsvFileTextFieldParser<T>(string fileFullPath, string delimiter = ";") where T : new()
{
    if (!File.Exists(fileFullPath))
    {
        return null;
    }

    var list = new List<T>();
    var csvFields = GetAllFieldOfClass<T>();
    var fieldDict = new Dictionary<int, MemberInfo>();

    using (TextFieldParser parser = new TextFieldParser(fileFullPath))
    {
        parser.SetDelimiters(delimiter);

        bool headerParsed = false;

        while (!parser.EndOfData)
        {
            //Processing row
            string[] rowFields = parser.ReadFields();
            if (!headerParsed)
            {
                for (int i = 0; i < rowFields.Length; i++)
                {
                    // First row shall be the header!
                    var csvField = csvFields.Where(f => f.Name == rowFields[i]).FirstOrDefault();
                    if (csvField != null)
                    {
                        fieldDict.Add(i, csvField);
                    }
                }
                headerParsed = true;
            }
            else
            {
                T newObj = new T();
                for (int i = 0; i < rowFields.Length; i++)
                {
                    var csvFied = fieldDict[i];
                    var record = rowFields[i];

                    if (csvFied is FieldInfo)
                    {
                        ((FieldInfo)csvFied).SetValue(newObj, record);
                    }
                    else if (csvFied is PropertyInfo)
                    {
                        var pi = (PropertyInfo)csvFied;
                        pi.SetValue(newObj, Convert.ChangeType(record, pi.PropertyType), null);
                    }
                    else
                    {
                        throw new Exception("Unhandled case.");
                    }
                }
                if (newObj != null)
                {
                    list.Add(newObj);
                }
            }
        }
    }
    return list;
}

public static IEnumerable<MemberInfo> GetAllFieldOfClass<T>()
{
    return
        from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        where new[] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
        let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
        orderby orderAttr == null ? int.MaxValue : orderAttr.Order, mi.Name
        select mi;            
}

0

CsvHelper kullanmanızı şiddetle tavsiye ediyorum.

İşte kısa bir örnek:

public class csvExampleClass
{
    public string Id { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}

var items = DeserializeCsvFile<List<csvExampleClass>>( csvText );

public static List<T> DeserializeCsvFile<T>(string text)
{
    CsvReader csv = new CsvReader( new StringReader( text ) );
    csv.Configuration.Delimiter = ",";
    csv.Configuration.HeaderValidated = null;
    csv.Configuration.MissingFieldFound = null;
    return (List<T>)csv.GetRecords<T>();
}

Tüm belgeleri şu adreste bulabilirsiniz: https://joshclose.github.io/CsvHelper

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.