Consulta LINQ em um DataTable

1031

Estou tentando executar uma consulta LINQ em um objeto DataTable e, estranhamente, estou descobrindo que executar essas consultas no DataTables não é simples. Por exemplo:

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

Isso não é permitido. Como obtenho algo assim funcionando?

Estou surpreso que as consultas LINQ não sejam permitidas no DataTables!

Calanus
fonte
3
Você pode encontrar mais exemplos de LINQ / Lambda em webmingle.blogspot.com/2010_09_01_archive.html

Respostas:

1279

Você não pode consultar contra o DataTable's linhas coleção, desde DataRowCollectionnão implementa IEnumerable<T>. Você precisa usar a AsEnumerable()extensão para DataTable. Igual a:

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

E como @Keith diz, você precisará adicionar uma referência ao System.Data.DataSetExtensions

AsEnumerable()retorna IEnumerable<DataRow>. Se você precisar converter IEnumerable<DataRow>para a DataTable, use a CopyToDataTable()extensão

Abaixo está uma consulta com a expressão Lambda,

var result = myDataTable
    .AsEnumerable()
    .Where(myRow => myRow.Field<int>("RowNo") == 1);
Collin K
fonte
8
Versão VB: Dim results = From myRow Em myDataTable.AsEnumerable _ Where myRow.Field ("RowNo") = 1 _ Selecione myRow
Jeff
15
Eu já tinha uma referência à DLL mencionada, mas estava faltandousing System.Data;
Luke Duddridge
5
A versão VB precisa inserir (Of String) entre myRow.Field e ("RowNo"). Essa parte deve ser: myRow.Field (Of String) ("RowNo") = 1 - Referência ao comentário do @ros.
yougotiger
8
esta solução é desnecessariamente complicada. Use myDataTable.Rowscomo o @JoelFan sugeriu.
The Conspiracy
10
@ Markus Apenas para esclarecer, a razão pela qual a solução do @ JoelFan trabalha myDataTable.Rowsé porque a myRowvariável é convertida explicitamente DataRow. Quando é compilada, essa consulta é reescrita para myDataTable.Rows.Cast<DataRow>().Where(myRow => (int)myRow["RowNo"] == 1). Pessoalmente, não acho a ligação AsEnumerable()mais complicada do que a ligação para Cast<DataRow>(). Até onde eu sei, o desempenho é o mesmo, então é apenas uma questão de preferência.
Collin K
129
var results = from DataRow myRow in myDataTable.Rows
    where (int)myRow["RowNo"] == 1
    select myRow
JoelFan
fonte
2
Que tal selecionar várias linhas, em vez de apenas a linha 1?
Adjit
2
Basta remover a linha "where" e você obterá todas as linhas
JoelFan
1
Sim, é assim que eu costumo fazer isso, exceto pela substituição (int)myRow["RowNo"]pelo formulário genérico myRow.Field<int>("RowNo")para oferecer suporte mais conveniente a tipos anuláveis.
Jonas19
69

Não é que eles não tenham sido deliberadamente permitidos no DataTables, apenas o DataTables é anterior às construções IQueryable e IEnumerable genéricas nas quais as consultas do Linq podem ser executadas.

Ambas as interfaces requerem alguma validação de segurança de tipo de classificação. DataTables não são fortemente tipadas. Esse é o mesmo motivo pelo qual as pessoas não podem consultar um ArrayList, por exemplo.

Para que o Linq funcione, você precisa mapear seus resultados em relação a objetos com segurança de tipo e, em vez disso, consultar esses itens.

Jon Limjap
fonte
49

Como @ ch00k disse:

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

Você também precisa adicionar uma referência de projeto ao System.Data.DataSetExtensions

Keith
fonte
1
Se você tentar fazer isso, você vai encontrá-lo não funcionará a menos que você colocar um tipo específico sobre myRowou utilização Cast<DataRow>()em Rows. Melhor usar AsEnumerable().
NetMage 15/01
1
A @NetMage funcionou há 12 anos quando a publiquei. Contanto que você tem System.Linqe System.Data.DataSetExtensions, em seguida, myDataTable.Rowsretorna uma coleção enumerável de DataRowqualquer maneira. Isso pode ter mudado, faz uma década desde que eu o usei.
Keith
1
Interessante - acho que foi alterado em algum momento, pois não funciona no .Net ou no .Net Core agora.
NetMage 16/01
1
@ NetMage sim, não estou surpreso que as DataSetextensões não entrem no .NET Core ou .NET Standard, elas já estavam desatualizadas quando publiquei esta resposta. Eu realmente não usaria DataSetem novos projetos, existem modelos de acesso a dados muito melhores, tanto para facilitar a codificação quanto para o desempenho.
Keith
1
Eles estão lá, mas DataRowCollectionnão implementam IEnumerable<T>apenas IEnumerablee, portanto, não funcionam com LINQ fortemente tipado.
NetMage 16/01
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")                         
                    };

os campos nome e idade agora fazem parte do objeto de consulta e podem ser acessados ​​da seguinte maneira: Console.WriteLine (query.name);

Ravi
fonte
Como eu uso o nome? Por exemplo, MessageBox.Show(name)é indefinido.
35

Sei que isso foi respondido algumas vezes, mas apenas para oferecer outra abordagem:

Eu gosto de usar o .Cast<T>()método, ele me ajuda a manter a sanidade ao ver o tipo explícito definido e, no fundo, acho que .AsEnumerable()chama assim mesmo:

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

ou

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

Conforme observado nos comentários, nenhum outro conjunto é necessário, pois faz parte do Linq ( Referência )

vandsh
fonte
5
Isso funciona sem fazer referência a System.Data.DataSetExtensions.
user423430
29

Usando o LINQ para manipular dados no 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
fonte
1
O AsDataView não aparece no Intellisense para mim. Incluí usando System.Data.Linq e System.Linq, mas ainda não está funcionando. Você sabe o que estou perdendo? Desde já, obrigado.
Naomi
@Naomi Vem 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
fonte
22

Tente esta linha de consulta simples:

var result=myDataTable.AsEnumerable().Where(myRow => myRow.Field<int>("RowNo") == 1);
Mohit Verma
fonte
4
Prefiro o " Encadeamento de métodos " (como você fez aqui) sobre a " Sintaxe de consulta " (na resposta aceita) simplesmente porque essa é uma cláusula where básica que se encaixa em uma linha e ainda é muito legível. Cada um com sua mania.
MikeTeeVee
16

Você pode usar o LINQ para objetos na coleção Rows, da seguinte maneira:

var results = from myRow in myDataTable.Rows where myRow.Field("RowNo") == 1 select myRow;
David Wengier
fonte
1
Como DataTable.Rowsnão é implementado IEnumerable, não consigo ver como essa consulta pode ser compilada.
onedaywhen
@onedaywhen Acabei de ver isso sendo feito em algum código e ele é compilado. Tentando descobrir o porquê agora.
precisa saber é o seguinte
... ou você pode simplesmente usar uma expressão de filtro no método Select: var results = myDataTable.Select ("RowNo = 1"); Isso retorna uma matriz DataRow.
Ishikawa
12

Esta é uma maneira simples que funciona para mim e usa expressões lambda:

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

Então, se você deseja um valor específico:

if(results != null) 
    var foo = results["ColName"].ToString()
Matt Kemp
fonte
11

Tente isto

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

Provavelmente, as classes para o DataSet, DataTable e DataRow já estão definidas na solução. Se for esse o caso, você não precisará da referência DataSetExtensions.

Ex. Nome da classe DataSet-> CustomSet, Nome da classe DataRow-> CustomTableRow (com colunas definidas: RowNo, ...)

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

Ou (como eu prefiro)

var result = myDataTable.Rows.OfType<CustomSet.CustomTableRow>().Where(myRow => myRow.RowNo);
xadriel
fonte
9
var results = from myRow in myDataTable
where results.Field<Int32>("RowNo") == 1
select results;
Vinay
fonte
Esta resposta tem muitos problemas.
Anderson Anderson
8

No meu aplicativo, descobri que o uso de LINQ to Datasets com a extensão AsEnumerable () para DataTable, conforme sugerido na resposta, era extremamente lento. Se você estiver interessado em otimizar a velocidade, use a biblioteca Json.Net de 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);
LandedGently
fonte
Duvido que seja mais rápido, nos casos gerais. Possui a sobrecarga de duas operações de serialização, uma desserialização e uma análise. Independentemente disso, diminuí a votação porque não é conciso, ou seja, a serialização / desserialização não deixa claro que a intenção é filtrar uma lista.
08
@an phu, usando o método de extensão .AsEnumerable cria uma coleção de System.Data.DataRowobjetos pesados . A tabela de dados serializados e analisados ​​cria dados leves que consistem apenas nos nomes e valores das colunas de cada linha. Quando a consulta é executada, ela carrega os dados na memória, o que para um grande conjunto de dados pode envolver a troca. Às vezes, a sobrecarga de várias operações é menor que a sobrecarga de copiar grandes quantidades de dados dentro e fora da memória.
LandedGently
7

Para VB.NET O código será semelhante a este:

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

Exemplo de como conseguir isso fornecido abaixo:

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
fonte
6

Tente isso ...

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
fonte
5

Você pode fazê-lo funcionar de maneira elegante via linq assim:

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

Ou como o linq dinâmico, este (o AsDynamic é chamado diretamente no DataSet):

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

Prefiro a última abordagem, enquanto is é a mais flexível. PS: Não esqueça de conectar System.Data.DataSetExtensions.dllreferência

AuthorProxy
fonte
5

você pode tentar isso, mas deve ter certeza de que o tipo de valores para cada coluna

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
fonte
O mundo enlouqueceu? O que há de errado com o sql? DataRow [] drs = dt.Select ("id = 1"); Talvez isso seja fácil demais.
Programnik 23/09/18
0

Proponho a seguinte solução:

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

Observando a documentação do DataView , a primeira coisa que podemos ver é:

Representa uma exibição personalizada e vinculável de um DataTable para classificação, filtragem, pesquisa, edição e navegação.

O que estou obtendo disso é que o DataTable se destina apenas a armazenar dados e o DataView nos permite "consultar" o DataTable.

Aqui está como isso funciona neste caso específico:

Você tenta implementar a instrução SQL

SELECT *
FROM myDataTable
WHERE RowNo = 1

no "idioma DataTable". Em C #, lemos assim:

FROM myDataTable
WHERE RowNo = 1
SELECT *

que fica em c # assim:

DataView view = new DataView(myDataTable);  //FROM myDataTable
view.RowFilter = "RowNo = 1";  //WHERE RowNo = 1
DataTable results = view.ToTable(true);  //SELECT *
Alan
fonte
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
fonte