Zapytanie LINQ w DataTable

1031

Próbuję wykonać zapytanie LINQ na obiekcie DataTable i dziwnie stwierdzam, że wykonywanie takich zapytań na DataTables nie jest proste. Na przykład:

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

To jest niedozwolone. Jak mogę uzyskać coś takiego?

Dziwi mnie, że zapytania LINQ nie są dozwolone w tabelach danych!

Calanus
źródło
3
Więcej przykładów LINQ / Lambda można znaleźć na stronie webmingle.blogspot.com/2010_09_01_archive.html

Odpowiedzi:

1279

Nie można kwerendy przeciwko DataTable„s Wiersze kolekcji, ponieważ DataRowCollectionnie realizuje IEnumerable<T>. Musisz użyć AsEnumerable()rozszerzenia dla DataTable. Tak jak:

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

I jak mówi @Keith , musisz dodać odwołanie do System.Data.DataSetExtensions

AsEnumerable()zwraca IEnumerable<DataRow>. Jeśli chcesz przekonwertować IEnumerable<DataRow>na a DataTable, użyj CopyToDataTable()rozszerzenia.

Poniżej znajduje się zapytanie z wyrażeniem Lambda,

var result = myDataTable
    .AsEnumerable()
    .Where(myRow => myRow.Field<int>("RowNo") == 1);
Collin K.
źródło
8
Wersja VB: Dim wyniki = Z myRow W myDataTable.AsEnumerable _ Gdzie myRow.Field („RowNo”) = 1 _ Wybierz myRow
Jeff
15
Miałem już odniesienie do wspomnianej biblioteki dll, ale jej nie byłousing System.Data;
Luke Duddridge
5
Wersja VB musi wstawić (Of String) między myRow.Field i („RowNo”). Ta część powinna brzmieć: myRow.Field (Of String) („RowNo”) = 1 - Odwołanie do komentarza @Cros.
yougotiger
8
to rozwiązanie jest niepotrzebnie skomplikowane. Użyj myDataTable.Rowszamiast tego, jak sugerował @JoelFan.
The Conspiracy
10
@ Markus Aby wyjaśnić, powodem, dla którego rozwiązanie @ JoelFan działa, myDataTable.Rowsjest to, że myRowzmienna jest jawnie rzutowana na DataRow. Po skompilowaniu zapytanie jest przepisywane na myDataTable.Rows.Cast<DataRow>().Where(myRow => (int)myRow["RowNo"] == 1). Osobiście nie uważam, że wezwanie do AsEnumerable()czegoś bardziej skomplikowanego niż wezwanie do Cast<DataRow>(). O ile mi wiadomo, wydajność jest taka sama, więc jest to tylko kwestia preferencji.
Collin K,
129
var results = from DataRow myRow in myDataTable.Rows
    where (int)myRow["RowNo"] == 1
    select myRow
JoelFan
źródło
2
A co z wyborem wielu wierszy zamiast tylko pierwszego?
Przyłącz się
2
Wystarczy usunąć wiersz „gdzie”, a dostaniesz wszystkie wiersze
JoelFan
1
Tak, właśnie tak to robię, z wyjątkiem zastępowania (int)myRow["RowNo"]formularzem ogólnym, myRow.Field<int>("RowNo")aby wygodniej obsługiwać typy zerowalne.
Jonas,
69

Nie chodzi o to, że celowo nie były dozwolone w DataTables, po prostu DataTables wcześniej niż IQueryable i ogólne IEnumerable konstrukcje, na których można wykonywać zapytania Linq.

Oba interfejsy wymagają pewnego rodzaju weryfikacji bezpieczeństwa typu. Tabele danych nie są mocno wpisane. Jest to ten sam powód, dla którego ludzie nie mogą na przykład zapytać o ArrayList.

Aby Linq działał, musisz zmapować swoje wyniki względem obiektów bezpiecznych dla typu i zapytać w zamian.

Jon Limjap
źródło
49

Jak powiedział @ 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

Musisz także dodać odwołanie do projektu System.Data.DataSetExtensions

Keith
źródło
1
Jeśli spróbujesz tego, przekonasz się, że nie będzie działać, chyba że nałożysz określony typ myRowlub użyjesz Cast<DataRow>()go Rows. Lepszy w użyciu AsEnumerable().
NetMage
1
@NetMage działało to 12 lat temu, kiedy go opublikowałem. Tak długo, jak masz, System.Linqa System.Data.DataSetExtensionsnastępnie myDataTable.Rowszwraca wymienną kolekcję DataRow. To mogło się zmienić, minęło dziesięć lat, odkąd go użyłem.
Keith
1
Interesujące - myślę, że w pewnym momencie zostało to zmienione, ponieważ nie działa teraz w .Net lub .Net Core.
NetMage
1
@NetMage tak, nie dziwię się, że DataSetrozszerzenia nie znalazły się w .NET Core lub .NET Standard, były już nieaktualne, kiedy opublikowałem tę odpowiedź. Naprawdę nie używałbym DataSetw nowych projektach, istnieją znacznie lepsze modele dostępu do danych, zarówno pod względem łatwości kodowania, jak i wydajności.
Keith
1
Są tam, ale DataRowCollectionnie implementują IEnumerable<T>po prostu IEnumerablei dlatego nie działają z silnie wpisanym LINQ.
NetMage
38
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")                         
                    };

pola nazwy i wieku są teraz częścią obiektu zapytania i można uzyskać do nich dostęp w następujący sposób: Console.WriteLine (query.name);

Ravi
źródło
Jak używać imienia? Na przykład MessageBox.Show(name)jest niezdefiniowany.
35

Zdaję sobie sprawę, że kilkakrotnie na to odpowiedziano, ale aby zaoferować inne podejście:

Lubię używać tej .Cast<T>()metody, pomaga mi to zachować zdrowy rozsądek, widząc wyraźny typ zdefiniowany głęboko i myślę, .AsEnumerable()że i tak go nazywa:

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

lub

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

Jak zauważono w komentarzach, żadne inne zespoły nie są potrzebne, ponieważ jest to część Linq (Materiały referencyjne )

Vandsh
źródło
5
Działa to bez odwoływania się do System.Data.DataSetExtensions.
user423430,
29

Używanie LINQ do manipulowania danymi w 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();
Salim
źródło
1
AsDataView nie pojawia się dla mnie w Intellisense. Dołączyłem używanie System.Data.Linq i System.Linq, ale nadal nie działa. Czy wiesz, czego mi brakuje? Z góry dziękuję.
Naomi
@Naomi Pochodzi z 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();
 // 
sushil pandey
źródło
22

Wypróbuj tę prostą linię zapytania:

var result=myDataTable.AsEnumerable().Where(myRow => myRow.Field<int>("RowNo") == 1);
Mohit Verma
źródło
4
Wolę „ Tworzenie łańcuchów metod ” (jak już to zrobiłeś) niż „ Składnię zapytań ” (w przyjętej odpowiedzi) po prostu dlatego, że jest to podstawowa klauzula where, która pasuje do jednego wiersza i jest nadal bardzo czytelna. Do każdego własnego.
MikeTeeVee,
16

Możesz użyć LINQ do obiektów w kolekcji Rows, na przykład:

var results = from myRow in myDataTable.Rows where myRow.Field("RowNo") == 1 select myRow;
David Wengier
źródło
1
Ponieważ DataTable.Rowsnie implementuje IEnumerable, nie widzę, jak to zapytanie mogłoby się skompilować.
dniu
@onedaywhen właśnie zobaczyłem, jak to się dzieje w jakimś kodzie i kompiluje się. Próbuję dowiedzieć się, dlaczego właśnie teraz.
BVernon
... lub możesz po prostu użyć wyrażenia filtrującego w ramach metody Select: var results = myDataTable.Select („RowNo = 1”); Zwraca tablicę DataRow.
Ishikawa,
12

Jest to prosty sposób, który działa dla mnie i wykorzystuje wyrażenia lambda:

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

Jeśli chcesz konkretną wartość:

if(results != null) 
    var foo = results["ColName"].ToString()
Matt Kemp
źródło
11

Spróbuj tego

var row = (from result in dt.AsEnumerable().OrderBy( result => Guid.NewGuid()) select result).Take(3) ; 
midhun sankar
źródło
11

Najprawdopodobniej klasy dla DataSet, DataTable i DataRow są już zdefiniowane w rozwiązaniu. W takim przypadku nie będzie potrzebne odwołanie do DataSetExtensions.

Dawny. Nazwa klasy DataSet-> CustomSet, nazwa klasy DataRow-> CustomTableRow (ze zdefiniowanymi kolumnami: RowNo, ...)

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

Lub (jak wolę)

var result = myDataTable.Rows.OfType<CustomSet.CustomTableRow>().Where(myRow => myRow.RowNo);
Xadriel
źródło
9
var results = from myRow in myDataTable
where results.Field<Int32>("RowNo") == 1
select results;
Vinay
źródło
Ta odpowiedź zawiera wiele problemów.
Pan Anderson
8

W mojej aplikacji stwierdziłem, że użycie LINQ do zestawów danych z rozszerzeniem AsEnumerable () dla DataTable, jak sugerowano w odpowiedzi, było bardzo wolne. Jeśli chcesz zoptymalizować szybkość, skorzystaj z biblioteki Json.Net Jamesa Newtonkinga ( 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);
Wylądował delikatnie
źródło
Wątpię, aby było to szybsze w ogólnych przypadkach. Ma narzut dwóch operacji serializacji, jednej deserializacji i jednej analizy składniowej. Niezależnie od tego odrzuciłem głos, ponieważ nie jest on zwięzły, tj. Serializacja / deserializacja nie wyjaśnia, że ​​celem jest filtrowanie listy.
phu
@an phu przy użyciu metody rozszerzenia .AsEnumerable tworzy kolekcję ciężkich System.Data.DataRowprzedmiotów. Serializowana i analizowana tabela danych tworzy lekkie dane składające się tylko z nazw kolumn i wartości każdego wiersza. Po uruchomieniu zapytania ładuje dane do pamięci, co w przypadku dużego zestawu danych może wymagać wymiany. Czasami narzut związany z kilkoma operacjami jest mniejszy niż narzut związany z kopiowaniem dużych ilości danych do i z pamięci.
Landed Delikatnie
7

W przypadku VB.NET Kod będzie wyglądał następująco:

Dim results = From myRow In myDataTable  
Where myRow.Field(Of Int32)("RowNo") = 1 Select myRow
Abdul Saboor
źródło
7
IEnumerable<string> result = from myRow in dataTableResult.AsEnumerable()
                             select myRow["server"].ToString() ;
Iman
źródło
7

Przykład, jak to osiągnąć, podany poniżej:

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();
Ryan Gavin
źródło
6

Spróbuj tego...

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 );
}
Uthaiah
źródło
5

Możesz sprawić, aby działał elegancko za pośrednictwem linq w następujący sposób:

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

Lub jak dynamiczny linq to (AsDynamic jest wywoływany bezpośrednio w DataSet):

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

Wolę ostatnie podejście, podczas gdy jest najbardziej elastyczne. PS: Nie zapomnij połączyć System.Data.DataSetExtensions.dllodniesienia

AutorProxy
źródło
5

możesz spróbować, ale musisz mieć pewność, że typ wartości dla każdej kolumny

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"),    
});
Gabriel Martinez Bustos
źródło
Czy świat oszalał? Co jest nie tak z SQL? DataRow [] drs = dt.Select ("id = 1"); Może to jest zbyt łatwe.
Programnik
0

Proponuję następujące rozwiązanie:

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

Patrząc na dokumentację DataView , pierwszą rzeczą, którą możemy zobaczyć, jest:

Reprezentuje dostosowywany do danych, dostosowany widok DataTable do sortowania, filtrowania, wyszukiwania, edycji i nawigacji.

Z tego, co otrzymuję, jest to, że DataTable służy wyłącznie do przechowywania danych, a DataView umożliwia nam „zapytanie” względem DataTable.

Oto jak to działa w tym konkretnym przypadku:

Próbujesz zaimplementować instrukcję SQL

SELECT *
FROM myDataTable
WHERE RowNo = 1

w „języku DataTable”. W C # czytamy to w ten sposób:

FROM myDataTable
WHERE RowNo = 1
SELECT *

który wygląda w C # w ten sposób:

DataView view = new DataView(myDataTable);  //FROM myDataTable
view.RowFilter = "RowNo = 1";  //WHERE RowNo = 1
DataTable results = view.ToTable(true);  //SELECT *
Alan
źródło
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});
chandra rv
źródło