Query LINQ su una DataTable


1031

Sto cercando di eseguire una query LINQ su un oggetto DataTable e, stranamente, sto scoprendo che eseguire tali query su DataTables non è semplice. Per esempio:

var results = from myRow in myDataTable
where results.Field("RowNo") == 1
select results;

Questo non è permesso Come faccio a far funzionare qualcosa del genere?

Sono sorpreso che le query LINQ non siano consentite su DataTables!


3
Puoi trovare altri esempi LINQ / Lambda da webmingle.blogspot.com/2010_09_01_archive.html

Risposte:


1279

Non è possibile eseguire una query contro la DataTable's Righe raccolta, dal momento che DataRowCollectionnon implementa IEnumerable<T>. È necessario utilizzare l' AsEnumerable()estensione per DataTable. Così:

var results = from myRow in myDataTable.AsEnumerable()
where myRow.Field<int>("RowNo") == 1
select myRow;

E come dice @Keith , dovrai aggiungere un riferimento a System.Data.DataSetExtensions

AsEnumerable()ritorna IEnumerable<DataRow>. Se devi convertire IEnumerable<DataRow>in a DataTable, usa l' CopyToDataTable()estensione.

Di seguito è una query con Lambda Expression,

var result = myDataTable
    .AsEnumerable()
    .Where(myRow => myRow.Field<int>("RowNo") == 1);

8
Versione VB: Dim results = From myRow In myDataTable.AsEnumerable _ Where myRow.Field ("RowNo") = 1 _ Seleziona myRow
Jeff

15
Avevo già un riferimento alla DLL menzionata, ma mancavausing System.Data;
Luke Duddridge

5
La versione VB deve inserire (Of String) tra myRow.Field e ("RowNo"). Quella parte dovrebbe contenere: myRow.Field (Of String) ("RowNo") = 1 - Commento di riferimento @Cros.
yougotiger,

8
questa soluzione è inutilmente complicata. Usa myDataTable.Rowsinvece come suggerito da @JoelFan.
The Conspiracy,

10
@Markus Solo per chiarire, il motivo per cui la soluzione di @ JoelFan funziona myDataTable.Rowsè perché la myRowvariabile viene esplicitamente lanciata DataRow. Quando viene compilato, la query viene riscritta myDataTable.Rows.Cast<DataRow>().Where(myRow => (int)myRow["RowNo"] == 1). Personalmente, non trovo la chiamata a AsEnumerable()più complicata della chiamata a Cast<DataRow>(). Per quanto ne so, le prestazioni sono le stesse, quindi è solo una questione di preferenza.
Collin K,

129
var results = from DataRow myRow in myDataTable.Rows
    where (int)myRow["RowNo"] == 1
    select myRow

2
Che ne dici di selezionare più righe, anziché solo la riga 1?
Adeguamento

2
Basta rimuovere la riga "where" e otterrai tutte le righe
JoelFan

1
Sì, è così che lo uso per farlo, tranne che per la sostituzione (int)myRow["RowNo"]con il modulo generico myRow.Field<int>("RowNo")per supportare più comodamente i tipi annullabili.
Jonas,

69

Non è che non siano stati deliberatamente ammessi su DataTables, è solo che DataTables precede i costrutti IEneryable generici e IQueryable su cui è possibile eseguire query Linq.

Entrambe le interfacce richiedono una sorta di convalida della sicurezza del tipo. DataTables non sono fortemente tipizzati. Questo è lo stesso motivo per cui le persone non possono interrogare contro una ArrayList, per esempio.

Affinché Linq funzioni, è necessario mappare i risultati con oggetti sicuri per i tipi e interrogarli invece.


49

Come diceva @ ch00k:

using System.Data; //needed for the extension methods to work

...

var results = 
    from myRow in myDataTable.Rows 
    where myRow.Field<int>("RowNo") == 1 
    select myRow; //select the thing you want, not the collection

È inoltre necessario aggiungere un riferimento al progetto System.Data.DataSetExtensions


1
Se si tenta questo, vi accorgerete che non funziona a meno che non si mette un tipo specifico su myRowo l'uso Cast<DataRow>()su Rows. Meglio usare AsEnumerable().
NetMage

1
@NetMage ha funzionato 12 anni fa quando l'ho pubblicato. Finché hai System.Linqe System.Data.DataSetExtensionsquindi myDataTable.Rowsrestituisce una raccolta enumerabile di DataRowcomunque. Potrebbe essere cambiato, è passato un decennio da quando l'ho usato.
Keith il

1
Interessante - Immagino che sia stato cambiato ad un certo punto, in quanto non funziona su .Net o .Net Core ora.
NetMage

1
@NetMage sì, non mi sorprende che le DataSetestensioni non siano arrivate a .NET Core o .NET Standard, erano già obsolete quando ho pubblicato questa risposta. Non userei davvero DataSetin nuovi progetti, ci sono modelli di accesso ai dati molto migliori, sia per facilità di codifica che per prestazioni.
Keith

1
Sono lì, ma DataRowCollectionnon implementano IEnumerable<T>solo IEnumerablee quindi non funzionano con LINQ fortemente tipizzato.
NetMage

39
var query = from p in dt.AsEnumerable()
                    where p.Field<string>("code") == this.txtCat.Text
                    select new
                    {
                        name = p.Field<string>("name"),
                        age= p.Field<int>("age")                         
                    };

i campi nome ed età sono ora parte dell'oggetto query e sono accessibili in questo modo: Console.WriteLine (nome query.);


Come uso il nome? Ad esempio, MessageBox.Show(name)non è definito.

35

Mi rendo conto che è stato risposto più volte, ma solo per offrire un altro approccio:

Mi piace usare il .Cast<T>()metodo, mi aiuta a mantenere la sanità mentale nel vedere il tipo esplicito definito e in fondo penso .AsEnumerable()che lo chiami comunque:

var results = from myRow in myDataTable.Rows.Cast<DataRow>() 
                  where myRow.Field<int>("RowNo") == 1 select myRow;

o

var results = myDataTable.Rows.Cast<DataRow>()
                      .FirstOrDefault(x => x.Field<int>("RowNo") == 1);

Come notato nei commenti, non sono necessari altri assiemi poiché fa parte di Linq ( riferimento )


5
Funziona senza fare riferimento a System.Data.DataSetExtensions.
user423430

29

Utilizzo di LINQ per manipolare i dati in DataSet / DataTable

var results = from myRow in tblCurrentStock.AsEnumerable()
              where myRow.Field<string>("item_name").ToUpper().StartsWith(tbSearchItem.Text.ToUpper())
              select myRow;
DataView view = results.AsDataView();

1
AsDataView non appare in Intellisense per me. Ho incluso l'uso di System.Data.Linq e l'utilizzo di System.Linq ma non funziona ancora. Sai cosa mi sto perdendo? Grazie in anticipo.
Naomi,

@Naomi Viene System.Data.DataSetExtensions.
Louis Waweru,

29
//Create DataTable 
DataTable dt= new DataTable();
dt.Columns.AddRange(new DataColumn[]
{
   new DataColumn("ID",typeof(System.Int32)),
   new DataColumn("Name",typeof(System.String))

});

//Fill with data

dt.Rows.Add(new Object[]{1,"Test1"});
dt.Rows.Add(new Object[]{2,"Test2"});

//Now  Query DataTable with linq
//To work with linq it should required our source implement IEnumerable interface.
//But DataTable not Implement IEnumerable interface
//So we call DataTable Extension method  i.e AsEnumerable() this will return EnumerableRowCollection<DataRow>


// Now Query DataTable to find Row whoes ID=1

DataRow drow = dt.AsEnumerable().Where(p=>p.Field<Int32>(0)==1).FirstOrDefault();
 // 

22

Prova questa semplice linea di query:

var result=myDataTable.AsEnumerable().Where(myRow => myRow.Field<int>("RowNo") == 1);

4
Preferisco il " Metodo di concatenamento " (come hai fatto qui) rispetto alla " Sintassi della query " (nella risposta accettata) semplicemente perché questa è una clausola where di base che si adatta su una riga ed è ancora molto leggibile. A ognuno il suo.
MikeTeeVee,

16

Puoi usare LINQ per oggetti nella raccolta Rows, in questo modo:

var results = from myRow in myDataTable.Rows where myRow.Field("RowNo") == 1 select myRow;

1
Perché DataTable.Rowsnon implementa IEnumerable, non riesco a vedere come questa query potrebbe essere compilata.
Onedayquando l'

@onedayquando ho appena visto che questo è stato fatto in qualche codice e si compila. Sto cercando di capire perché adesso.
BVernon,

... oppure puoi semplicemente usare un'espressione di filtro nel metodo Select: var results = myDataTable.Select ("RowNo = 1"); Ciò restituisce un array DataRow.
Ishikawa,

12

Questo è un modo semplice che funziona per me e usa espressioni lambda:

var results = myDataTable.Select("").FirstOrDefault(x => (int)x["RowNo"] == 1)

Quindi se vuoi un valore particolare:

if(results != null) 
    var foo = results["ColName"].ToString()

11

Prova questo

var row = (from result in dt.AsEnumerable().OrderBy( result => Guid.NewGuid()) select result).Take(3) ; 

11

Molto probabilmente, le classi per DataSet, DataTable e DataRow sono già definite nella soluzione. In tal caso, non sarà necessario il riferimento DataSetExtensions.

Ex. Nome classe DataSet-> CustomSet, nome classe DataRow-> CustomTableRow (con colonne definite: RowNo, ...)

var result = from myRow in myDataTable.Rows.OfType<CustomSet.CustomTableRow>()
             where myRow.RowNo == 1
             select myRow;

O (come preferisco)

var result = myDataTable.Rows.OfType<CustomSet.CustomTableRow>().Where(myRow => myRow.RowNo);

9
var results = from myRow in myDataTable
where results.Field<Int32>("RowNo") == 1
select results;

Questa risposta contiene molti problemi.
Anderson,

8

Nella mia applicazione ho scoperto che l'utilizzo di LINQ to Dataset con l'estensione AsEnumerable () per DataTable come suggerito nella risposta è stato estremamente lento. Se sei interessato a ottimizzare la velocità, utilizza la libreria Json.Net di James Newtonking ( http://james.newtonking.com/json/help/index.html )

// Serialize the DataTable to a json string
string serializedTable = JsonConvert.SerializeObject(myDataTable);    
Jarray dataRows = Jarray.Parse(serializedTable);

// Run the LINQ query
List<JToken> results = (from row in dataRows
                    where (int) row["ans_key"] == 42
                    select row).ToList();

// If you need the results to be in a DataTable
string jsonResults = JsonConvert.SerializeObject(results);
DataTable resultsTable = JsonConvert.DeserializeObject<DataTable>(jsonResults);

Dubito che questo sia più veloce, nei casi generali. Ha il sovraccarico di due serializzazioni, una di deserializzazione e una di analisi. Indipendentemente da ciò, ho effettuato il downgrade perché non è conciso, ovvero la serializzazione / deserializzazione non chiarisce che l'intento è quello di filtrare un elenco.
un phu,

@an phu, utilizzando il metodo di estensione .AsEnumerable crea una raccolta di System.Data.DataRowoggetti pesanti . La tabella dei dati serializzati e analizzati crea dati leggeri costituiti solo dai nomi delle colonne e dai valori di ciascuna riga. Quando viene eseguita la query, caricherà i dati in memoria, che per un set di dati di grandi dimensioni può comportare lo scambio. A volte, il sovraccarico di diverse operazioni è inferiore al sovraccarico della copia di grandi quantità di dati dentro e fuori la memoria.
Atterrato delicatamente il

7

Per VB.NET Il codice sarà simile al seguente:

Dim results = From myRow In myDataTable  
Where myRow.Field(Of Int32)("RowNo") = 1 Select myRow

7
IEnumerable<string> result = from myRow in dataTableResult.AsEnumerable()
                             select myRow["server"].ToString() ;

7

Di seguito è riportato un esempio di come ottenere ciò:

DataSet dataSet = new DataSet(); //Create a dataset
dataSet = _DataEntryDataLayer.ReadResults(); //Call to the dataLayer to return the data

//LINQ query on a DataTable
var dataList = dataSet.Tables["DataTable"]
              .AsEnumerable()
              .Select(i => new
              {
                 ID = i["ID"],
                 Name = i["Name"]
               }).ToList();

6

Prova questo...

SqlCommand cmd = new SqlCommand( "Select * from Employee",con);
SqlDataReader dr = cmd.ExecuteReader( );
DataTable dt = new DataTable( "Employee" );
dt.Load( dr );
var Data = dt.AsEnumerable( );
var names = from emp in Data select emp.Field<String>( dt.Columns[1] );
foreach( var name in names )
{
    Console.WriteLine( name );
}

5

Puoi farlo funzionare elegante tramite Linq in questo modo:

from prod in TenMostExpensiveProducts().Tables[0].AsEnumerable()
where prod.Field<decimal>("UnitPrice") > 62.500M
select prod

O come linq dinamico questo (AsDynamic è chiamato direttamente su DataSet):

TenMostExpensiveProducts().AsDynamic().Where (x => x.UnitPrice > 62.500M)

Preferisco l'ultimo approccio mentre è il più flessibile. PS: non dimenticare di collegare il System.Data.DataSetExtensions.dllriferimento


5

puoi provare questo, ma devi essere sicuro del tipo di valori per ogni colonna

List<MyClass> result = myDataTable.AsEnumerable().Select(x=> new MyClass(){
     Property1 = (string)x.Field<string>("ColumnName1"),
     Property2 = (int)x.Field<int>("ColumnName2"),
     Property3 = (bool)x.Field<bool>("ColumnName3"),    
});

Il mondo è diventato pazzo? Cosa c'è che non va in sql? DataRow [] drs = dt.Select ("id = 1"); Forse è troppo facile.
Programnik,

0

Propongo la seguente soluzione:

DataView view = new DataView(myDataTable); 
view.RowFilter = "RowNo = 1";
DataTable results = view.ToTable(true);

Guardando la documentazione di DataView , la prima cosa che possiamo vedere è questa:

Rappresenta una vista di database e personalizzata di una DataTable per l'ordinamento, il filtro, la ricerca, la modifica e la navigazione.

Ciò che sto ottenendo da questo è che DataTable è pensato per archiviare solo dati e DataView ci consente di "interrogare" contro DataTable.

Ecco come funziona in questo caso particolare:

Si tenta di implementare l'istruzione SQL

SELECT *
FROM myDataTable
WHERE RowNo = 1

in "lingua DataTable". In C # lo leggeremmo così:

FROM myDataTable
WHERE RowNo = 1
SELECT *

che appare in C # in questo modo:

DataView view = new DataView(myDataTable);  //FROM myDataTable
view.RowFilter = "RowNo = 1";  //WHERE RowNo = 1
DataTable results = view.ToTable(true);  //SELECT *

0
                    //Json Formating code
                    //DT is DataTable
                    var filter = (from r1 in DT.AsEnumerable()

                                  //Grouping by multiple columns 
                                  group r1 by new
                                  {
                                      EMPID = r1.Field<string>("EMPID"),
                                      EMPNAME = r1.Field<string>("EMPNAME"),

                                  } into g
                                  //Selecting as new type
                                  select new
                                  {

                                      EMPID = g.Key.EMPID,
                                      MiddleName = g.Key.EMPNAME});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.