Quando si legge un file CSV utilizzando un DataReader e il provider di dati Jet OLEDB, come posso controllare i tipi di dati delle colonne?
Domanda
Nella mia applicazione C # sto usando il provider di dati OLEDB di Microsoft Jet per leggere un file CSV. La stringa di connessione è simile alla seguente:
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\Data;Extended Properties="text;HDR=Yes;FMT=Delimited
Apro un OleDbConnection ADO.NET usando quella stringa di connessione e seleziono tutte le righe dal file CSV con il comando:
select * from Data.csv
Quando apro un OleDbDataReader ed esamino i tipi di dati delle colonne che restituisce, trovo che qualcosa nello stack ha provato a indovinare i tipi di dati in base alla prima riga di dati nel file. Ad esempio, supponiamo che il file CSV contenga:
House,Street,Town
123,Fake Street,Springfield
12a,Evergreen Terrace,Springfield
La chiamata al metodo OleDbDataReader.GetDataTypeName per la colonna House rivelerà che alla colonna è stato assegnato il tipo di dati "DBTYPE_I4", quindi tutti i valori letti da essa vengono interpretati come numeri interi. Il mio problema è che House dovrebbe essere una stringa: quando provo a leggere il valore House dalla seconda riga, OleDbDataReader restituisce null.
Come posso dire al provider di database Jet o al OleDbDataReader di interpretare una colonna come stringhe anziché numeri?
Soluzione
C'è un file di schema che puoi creare che direbbe ad ADO.NET come interpretare il CSV - in effetti dandogli una struttura.
Prova questo: http://www.aspdotnetcodes.com/Importing_CSV_Database_Schema.ini.aspx
Altri suggerimenti
Per espandere la risposta di Marc, devo creare un file di testo chiamato Schema.ini e inserirlo nella stessa directory del file CSV. Oltre ai tipi di colonna, questo file può specificare il formato del file, il formato della data e dell'ora, le impostazioni internazionali e i nomi delle colonne se non sono inclusi nel file.
Per far funzionare l'esempio fornito nella domanda, il file Schema dovrebbe apparire così:
[Data.csv]
ColNameHeader=True
Col1=House Text
Col2=Street Text
Col3=Town Text
Potrei anche provare questo per fare in modo che il fornitore di dati esamini tutte le righe nel file prima di provare a indovinare i tipi di dati:
[Data.csv]
ColNameHeader=true
MaxScanRows=0
Nella vita reale, la mia applicazione importa i dati da file con nomi dinamici, quindi devo creare un file Schema.ini al volo e scriverlo nella stessa directory del file CSV prima di aprire la mia connessione.
Ulteriori dettagli sono disponibili qui - http: // msdn.microsoft.com/en-us/library/ms709353(VS.85).aspx - o cercando nella libreria MSDN il file " Schema.ini " ;.
Controlla
using (var reader = new CsvReader("data.csv"))
{
reader.ReadHeaderRecord();
foreach (var record in reader.DataRecords)
{
var name = record["Name"];
var age = record["Age"];
}
}
Devi dire al driver di scansionare tutte le righe per determinare lo schema. Altrimenti se le prime poche righe sono numeriche e le restanti sono alfanumeriche, le celle alfanumeriche saranno vuote.
Come Rory , ho scoperto che dovevo creare un file schema.ini in modo dinamico perché non c'è modo di programmarlo dire al conducente di scansionare tutte le righe. (questo non è il caso dei file Excel)
Devi avere MaxScanRows = 0
nel tuo schema.ini
Ecco un esempio di codice:
public static DataTable GetDataFromCsvFile(string filePath, bool isFirstRowHeader = true)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException("The path: " + filePath + " doesn't exist!");
}
if (!(Path.GetExtension(filePath) ?? string.Empty).ToUpper().Equals(".CSV"))
{
throw new ArgumentException("Only CSV files are supported");
}
var pathOnly = Path.GetDirectoryName(filePath);
var filename = Path.GetFileName(filePath);
var schemaIni =
<*>quot;[{filename}]{Environment.NewLine}" +
<*>quot;Format=CSVDelimited{Environment.NewLine}" +
<*>quot;ColNameHeader={(isFirstRowHeader ? "True" : "False")}{Environment.NewLine}" +
<*>quot;MaxScanRows=0{Environment.NewLine}" +
<*>quot; ; scan all rows for data type{Environment.NewLine}" +
<*>quot; ; This file was automatically generated";
var schemaFile = pathOnly != null ? Path.Combine(pathOnly, "schema.ini") : "schema.ini";
File.WriteAllText(schemaFile, schemaIni);
try
{
var sqlCommand = $@"SELECT * FROM [{filename}]";
var oleDbConnString =
<*>quot;Provider=Microsoft.Jet.OLEDB.4.0;Data Source={pathOnly};Extended Properties=\"Text;HDR={(isFirstRowHeader ? "Yes" : "No")}\"";
using (var oleDbConnection = new OleDbConnection(oleDbConnString))
using (var adapter = new OleDbDataAdapter(sqlCommand, oleDbConnection))
using (var dataTable = new DataTable())
{
adapter.FillSchema(dataTable, SchemaType.Source);
adapter.Fill(dataTable);
return dataTable;
}
}
finally
{
if (File.Exists(schemaFile))
{
File.Delete(schemaFile);
}
}
}
Dovrai apportare alcune modifiche se lo stai eseguendo nella stessa directory in più thread contemporaneamente.