LINQ-запрос к DataTable

1031

Я пытаюсь выполнить запрос LINQ к объекту DataTable, и странным образом обнаруживаю, что выполнение таких запросов к объектам DataTable не является простым. Например:

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

Это не разрешено Как я могу получить что-то вроде этого работает?

Я поражен, что запросы LINQ не разрешены в DataTables!

калянус
источник
3
Вы можете найти больше примеров LINQ / Lambda от webmingle.blogspot.com/2010_09_01_archive.html

Ответы:

1279

Вы не можете запрос против DataTable«ы Rows коллекции, так как DataRowCollectionне выполняет IEnumerable<T>. Вам нужно использовать AsEnumerable()расширение для DataTable. Вот так:

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

И, как говорит @Keith , вам нужно добавить ссылку на System.Data.DataSetExtensions

AsEnumerable()возвращается IEnumerable<DataRow>. Если вам нужно конвертировать IEnumerable<DataRow>в a DataTable, используйте CopyToDataTable()расширение.

Ниже приведен запрос с лямбда-выражением,

var result = myDataTable
    .AsEnumerable()
    .Where(myRow => myRow.Field<int>("RowNo") == 1);
Коллин К
источник
8
Версия VB: Dim results = From myRow В myDataTable.AsEnumerable _ Где myRow.Field ("RowNo") = 1 _ Выбрать myRow
Джефф
15
У меня уже была ссылка на упомянутую dll, но она отсутствовалаusing System.Data;
Люк Даддридж
5
Версия VB необходимо вставить (Of String) между myRow.Field и ("RowNo"). Эта часть должна выглядеть следующим образом: myRow.Field (Of String) ("RowNo") = 1 - ссылка @Cros комментарий.
переговоров
8
это решение излишне сложно. Используйте myDataTable.Rowsвместо этого, как предложил @JoelFan.
Заговор
10
@Markus Просто для пояснения, причина, по которой работает решение @ JoelFan, myDataTable.Rowsзаключается в том, что myRowпеременная явно приведена к DataRow. Когда он компилируется, этот запрос переписывается в myDataTable.Rows.Cast<DataRow>().Where(myRow => (int)myRow["RowNo"] == 1). Лично я не считаю вызов AsEnumerable()более сложным, чем вызов Cast<DataRow>(). Насколько я знаю, производительность одинакова, так что это просто вопрос предпочтений.
Коллин К
129
var results = from DataRow myRow in myDataTable.Rows
    where (int)myRow["RowNo"] == 1
    select myRow
JoelFan
источник
2
А как насчет выбора нескольких строк, а не только строки 1?
Отрегулируйте
2
Просто удалите строку «где», и вы получите все строки
JoelFan
1
Да, это то, как я использую это, за исключением замены (int)myRow["RowNo"]на общую форму myRow.Field<int>("RowNo")для более удобной поддержки типов, допускающих обнуление.
Джонас
69

Дело не в том, что они были намеренно запрещены в DataTables, просто DataTable предшествуют IQueryable и универсальным конструкциям IEnumerable, для которых могут выполняться запросы Linq.

Оба интерфейса требуют некоторой проверки безопасности типа. DataTables не являются строго типизированными. Это та же самая причина, по которой люди, например, не могут запрашивать ArrayList.

Чтобы Linq работал, вам нужно сопоставить результаты с типобезопасными объектами и вместо этого выполнить запрос.

Джон Лимджап
источник
49

Как сказал @ 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

Вам также необходимо добавить ссылку на проект в System.Data.DataSetExtensions

Кит
источник
1
Если вы попробуете это, вы увидите , что не будет работать , если вы не поставить типа конкретного на myRowили использовании Cast<DataRow>()на Rows. Лучше использовать AsEnumerable().
NetMage
1
@NetMage это сработало 12 лет назад, когда я его опубликовал. Пока у вас есть, System.Linqа System.Data.DataSetExtensionsзатем myDataTable.Rowsвозвращает перечислимую коллекцию в DataRowлюбом случае. Это могло измениться, прошло уже десять лет с тех пор, как я его использовал.
Кит
1
Интересно - я думаю, что он был изменен в какой-то момент, так как теперь он не работает на .Net или .Net Core.
NetMage
1
@NetMage да, я не удивлен, что DataSetрасширения не попали в .NET Core или .NET Standard, они уже устарели, когда я опубликовал этот ответ. Я действительно не буду использовать DataSetв новых проектах, есть гораздо лучшие модели доступа к данным, как для простоты кодирования и производительности.
Кит
1
Они есть, но DataRowCollectionне реализуются IEnumerable<T>просто IEnumerableи поэтому не работают со строго типизированным LINQ.
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")                         
                    };

поля name и age теперь являются частью объекта запроса и доступны следующим образом: Console.WriteLine (query.name);

Ravi
источник
Как я использую имя? Например, MessageBox.Show(name)не определено.
35

Я понимаю, что на это уже отвечали несколько раз, но просто чтобы предложить другой подход:

Мне нравится использовать этот .Cast<T>()метод, он помогает мне сохранять здравый смысл в том, чтобы видеть явный тип, заданный и в глубине, я думаю, в .AsEnumerable()любом случае вызывает его:

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

или

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

Как отмечено в комментариях, никакие другие сборки не нужны, так как это часть Linq ( ссылка )

vandsh
источник
5
Это работает без ссылки на System.Data.DataSetExtensions.
user423430
29

Использование LINQ для манипулирования данными в 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 не отображается в Intellisense для меня. Я включил использование System.Data.Linq и использование System.Linq, но все равно он не работает. Вы знаете, что мне не хватает? Заранее спасибо.
Наоми
@Naomi Оно происходит от System.Data.DataSetExtensions.
Луи Ваверу
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

Попробуйте эту простую строку запроса:

var result=myDataTable.AsEnumerable().Where(myRow => myRow.Field<int>("RowNo") == 1);
Мохит Верма
источник
4
Я предпочитаю « Объединение методов » (как вы сделали здесь), а не « Синтаксис запроса » (в принятом ответе) просто потому, что это базовое предложение where, которое помещается в одну строку и все еще очень читабельно. Каждому свое.
MikeTeeVee
16

Вы можете использовать LINQ для объектов в коллекции Rows, например так:

var results = from myRow in myDataTable.Rows where myRow.Field("RowNo") == 1 select myRow;
Дэвид Венжер
источник
1
Потому DataTable.Rowsчто не реализует IEnumerable, я не вижу, как этот запрос может скомпилироваться.
понедельник,
@onedaywhen Я только что видел, как это делается в каком-то коде, и он компилируется. Пытаюсь выяснить почему именно сейчас.
BVernon
... или вы можете просто использовать выражение фильтра в методе Select: var results = myDataTable.Select ("RowNo = 1"); Это возвращает массив DataRow.
Исикава
12

Это простой способ, который работает для меня и использует лямбда-выражения:

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

Тогда, если вы хотите конкретное значение:

if(results != null) 
    var foo = results["ColName"].ToString()
Мэтт Кемп
источник
11

Попробуй это

var row = (from result in dt.AsEnumerable().OrderBy( result => Guid.NewGuid()) select result).Take(3) ; 
мидхун санкар
источник
11

Скорее всего, классы для DataSet, DataTable и DataRow уже определены в решении. Если это так, вам не понадобится ссылка DataSetExtensions.

Ex. Имя класса DataSet-> CustomSet, имя класса DataRow-> CustomTableRow (с определенными столбцами: RowNo, ...)

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

Или (как я предпочитаю)

var result = myDataTable.Rows.OfType<CustomSet.CustomTableRow>().Where(myRow => myRow.RowNo);
xadriel
источник
9
var results = from myRow in myDataTable
where results.Field<Int32>("RowNo") == 1
select results;
Виней
источник
Этот ответ так же много вопросов с ним.
Мистер Андерсон
8

В моем приложении я обнаружил, что использование LINQ to Datasets с расширением AsEnumerable () для DataTable, как было предложено в ответе, было чрезвычайно медленным. Если вы заинтересованы в оптимизации скорости, используйте библиотеку Джеймса Ньютонкинга Json.Net ( 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
источник
Я сомневаюсь, что это быстрее, в общих случаях. Это накладные расходы на две сериализации, одну десериализацию и одну операцию синтаксического анализа. Несмотря на это, я высказался против, потому что это не является кратким, то есть сериализация / десериализация не дает понять, что цель состоит в том, чтобы фильтровать список.
phu
@an phu, используя метод расширения .AsEnumerable создает коллекцию тяжеловесных System.Data.DataRowобъектов. Таблица сериализованных и проанализированных данных создает облегченные данные, состоящие только из имен столбцов и значений каждой строки. Когда запрос выполняется, он загружает данные в память, что для большого набора данных может включать обмен. Иногда накладные расходы на несколько операций меньше, чем накладные расходы на копирование больших объемов данных в память и из нее.
LandedGently
7

Для VB.NET код будет выглядеть так:

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

Пример того, как этого добиться, приведен ниже:

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

Попробуй это...

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
источник
5

Вы можете заставить его работать элегантно через linq следующим образом:

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

Или как динамический linq this (AsDynamic вызывается непосредственно в DataSet):

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

Я предпочитаю последний подход, пока он самый гибкий. PS: не забудьте подключить System.Data.DataSetExtensions.dllссылку

AuthorProxy
источник
5

Вы можете попробовать это, но вы должны быть уверены, что тип значений для каждого столбца

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"),    
});
Габриэль Мартинес Бустос
источник
Мир сошел с ума? Что не так с sql? DataRow [] drs = dt.Select ("id = 1"); Может быть, это слишком просто.
Програмник
0

Я предлагаю следующее решение:

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

Глядя на документацию DataView , первое, что мы можем увидеть, это:

Представляет настраиваемое представление DataTable с возможностью привязки к данным для сортировки, фильтрации, поиска, редактирования и навигации.

Из этого я получаю то, что DataTable предназначен только для хранения данных, а DataView позволяет нам «запрашивать» данные из DataTable.

Вот как это работает в данном конкретном случае:

Вы пытаетесь реализовать оператор SQL

SELECT *
FROM myDataTable
WHERE RowNo = 1

в «DataTable язык». В C # мы читаем это так:

FROM myDataTable
WHERE RowNo = 1
SELECT *

который выглядит в C # так:

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});
Чандра Р.В.
источник