LINQ to DataSet
The DataSet family of objects—DataTable, DataRow, and DataColumn—forms the backbone of data access in many .NET applications. LINQ provides powerful querying capabilities over these objects, enabling developers to write type-safe, expressive queries against tabular data.
Required Namespaces
using System.Data;
using System.Linq;
Converting DataTable to Queryable Sequence
The AsEnumerable() extension method transforms a DataTable into an IEnumerable<DataRow> sequence, opening the door to LINQ operations.
var dataTable = new DataTable();
dataTable.Columns.Add("Id", typeof(int));
var firstRow = dataTable.NewRow();
var secondRow = dataTable.NewRow();
firstRow["Id"] = 100;
secondRow["Id"] = 200;
dataTable.Rows.Add(firstRow);
dataTable.Rows.Add(secondRow);
IEnumerable<DataRow> rowSequence = dataTable.AsEnumerable();
foreach (DataRow currentRow in rowSequence)
Console.WriteLine($"Value: {currentRow["Id"]}");
This conversion is essential because DataTable itself does not implement standard LINQ query operators.
Set Operations on DataRow Sequences
Distinct — Removing duplicate rows requires DataRowComparer.Default, as standard equality comparison compares references rather than values.
var table = new DataTable();
table.Columns.Add("Score", typeof(int));
table.Rows.Add(new object[] { 85 });
table.Rows.Add(new object[] { 92 });
table.Rows.Add(new object[] { 85 });
IEnumerable<DataRow> allRows = table.AsEnumerable();
IEnumerable<DataRow> uniqueRows = allRows.Distinct(DataRowComparer.Default);
foreach (var r in uniqueRows)
Console.WriteLine(r["Score"]);
// Output:
// 85
// 92
Except — Finding rows present in one sequence but not another:
var sourceTable = new DataTable();
sourceTable.Columns.Add("Value", typeof(int));
var filterTable = new DataTable();
filterTable.Columns.Add("Value", typeof(int));
sourceTable.Rows.Add(new object[] { 10 });
sourceTable.Rows.Add(new object[] { 20 });
sourceTable.Rows.Add(new object[] { 30 });
filterTable.Rows.Add(new object[] { 20 });
filterTable.Rows.Add(new object[] { 40 });
IEnumerable<DataRow> source = sourceTable.AsEnumerable();
IEnumerable<DataRow> exclusion = filterTable.AsEnumerable();
IEnumerable<DataRow> difference = source.Except(exclusion, DataRowComparer.Default);
foreach (var r in difference)
Console.WriteLine(r["Value"]);
// Output:
// 10
// 30
Intersect — Retrieving rows common to both sequences:
var leftTable = new DataTable();
leftTable.Columns.Add("Item", typeof(string));
var rightTable = new DataTable();
rightTable.Columns.Add("Item", typeof(string));
leftTable.Rows.Add(new object[] { "Alpha" });
leftTable.Rows.Add(new object[] { "Beta" });
rightTable.Rows.Add(new object[] { "Beta" });
rightTable.Rows.Add(new object[] { "Gamma" });
IEnumerable<DataRow> left = leftTable.AsEnumerable();
IEnumerable<DataRow> right = rightTable.AsEnumerable();
IEnumerable<DataRow> common = left.Intersect(right, DataRowComparer.Default);
foreach (var r in common)
Console.WriteLine(r["Item"]);
// Output:
// Beta
Union — Combining sequences while removing duplicates:
var setA = new DataTable();
setA.Columns.Add("Code", typeof(int));
var setB = new DataTable();
setB.Columns.Add("Code", typeof(int));
setA.Rows.Add(new object[] { 1 });
setA.Rows.Add(new object[] { 2 });
setB.Rows.Add(new object[] { 2 });
setB.Rows.Add(new object[] { 3 });
IEnumerable<DataRow> groupA = setA.AsEnumerable();
IEnumerable<DataRow> groupB = setB.AsEnumerable();
IEnumerable<DataRow> merged = groupA.Union(groupB, DataRowComparer.Default);
foreach (var r in merged)
Console.WriteLine(r["Code"]);
// Output:
// 1
// 2
// 3
SequenceEqual — Comparing two sequences for identical content:
var firstTable = new DataTable();
firstTable.Columns.Add("Data", typeof(int));
var secondTable = new DataTable();
secondTable.Columns.Add("Data", typeof(int));
firstTable.Rows.Add(new object[] { 5 });
firstTable.Rows.Add(new object[] { 10 });
secondTable.Rows.Add(new object[] { 5 });
secondTable.Rows.Add(new object[] { 10 });
IEnumerable<DataRow> seq1 = firstTable.AsEnumerable();
IEnumerable<DataRow> seq2 = secondTable.AsEnumerable();
bool areIdentical = seq1.SequenceEqual(seq2, DataRowComparer.Default);
Console.WriteLine(areIdentical);
// Output:
// True
Working with DataColumn Values
Field<T> — A strongly-typed extension method for retrieving column values:
var sampleTable = new DataTable();
sampleTable.Columns.Add("Price", typeof(decimal));
sampleTable.Rows.Add(new object[] { 29.99m });
sampleTable.Rows.Add(new object[] { 49.99m });
IEnumerable<DataRow> rows = sampleTable.AsEnumerable();
IEnumerable<decimal> prices = rows.Select(r => r.Field<decimal>("Price"));
foreach (decimal p in prices)
Console.WriteLine(p);
// Output:
// 29.99
// 49.99
The method supports three parameter overloads: DataColumn, string, and int. Using the string overload improves code readability.
SetField<T> — Updating column values in place:
var numbers = new DataTable();
numbers.Columns.Add("Amount", typeof(int));
numbers.Rows.Add(new object[] { 5 });
numbers.Rows.Add(new object[] { 15 });
IEnumerable<DataRow> dataRows = numbers.AsEnumerable();
foreach (DataRow row in dataRows)
row.SetField<int>("Amount", row.Field<int>("Amount") * 2);
foreach (int val in dataRows.Select(r => r.Field<int>("Amount")))
Console.WriteLine(val);
// Output:
// 10
// 30
Creating a New DataTable from Query Results
The CopyToDataTable() method reconstructs a DataTable from a sequence of DataRow objects:
var original = new DataTable();
original.Columns.Add("Name", typeof(string));
original.Rows.Add(new object[] { "Alice" });
original.Rows.Add(new object[] { "Bob" });
IEnumerable<DataRow> filtered = original.AsEnumerable()
.Where(r => r.Field<string>("Name").StartsWith("A"));
DataTable filteredTable = filtered.CopyToDataTable();
LINQ to XML
The System.Xml.Linq namespace provides a fluent API for constructing, querying, and manipulating XML documents. These types integrate seamlessly with standard LINQ operators.
Required Namespaces
using System.Linq;
using System.Xml.Linq;
Core Types
| Type | Purpoce |
|---|---|
| XDocument | Represents an entire XML document |
| XElement | Represents a single XML element |
| XAttribute | Represents an XML attribute |
| XNamespace | Manages XML namespace prefixes |
| XCData | Encapsulates CDATA sections |
| XDeclaration | Specifies XML version and encoding |
Building XML Documents Programmatically
XDocument document = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("catalog",
new XElement("book",
new XAttribute("id", "B001"),
"Professional C#"),
new XElement("book",
new XAttribute("id", "B002"),
new XCData("<script>alert('test')</script>"))));
// Serialized output:
// <?xml version="1.0" encoding="utf-8" standalone="yes"?>
// <catalog>
// <book id="B001">Professional C#</book>
// <book id="B002"><![CDATA[<script>alert('test')</script>]]></book>
// </catalog>
Persisting and Loading Documents
Saving to disk:
document.Save("catalog.xml");
Loading from file:
XDocument loaded = XDocument.Load("catalog.xml");
The Load method supports multiple overloads accepting Stream, String, TextReader, or XmlReader.
XNode Hierarchy
XNode serves as the abstract base for all XML content nodes, including comments, text, and processing instructions. Its derived class XContainer encompasses XDocument and XElement, which can hold child nodes.
Traversal Methods
XDocument doc = XDocument.Load("catalog.xml");
// All descendant nodes (elements, text, comments, etc.)
IEnumerable<XNode> allNodes = doc.DescendantNodes();
// Direct child elements only
IEnumerable<XElement> childElements = doc.Elements();
Once you obtain node or element sequences, standard LINQ operators become available:
XDocument doc = XDocument.Load("catalog.xml");
IEnumerable<string> bookIds = doc
.Descendants("book")
.Where(e => e.Value.Length > 10)
.Select(e => e.Attribute("id")?.Value);
foreach (string id in bookIds)
Console.WriteLine(id);
For comprehensive API documentation, consult the System.Xml.Linq namespace reference in the Microsoft documentation.