C# の LINQ で複数条件を組み合わせる
実際のアプリケーションでは、単一のLINQメソッドだけでなく、複数の条件や操作を組み合わせて使うことが多い。ここでは複合的なクエリの書き方を解説する。
Where での複数条件
論理演算子で条件を組み合わせる。
var products = new List<Product>
{
new Product { Name = "Apple", Category = "Food", Price = 100, InStock = true },
new Product { Name = "Book", Category = "Books", Price = 500, InStock = true },
new Product { Name = "Banana", Category = "Food", Price = 80, InStock = false },
new Product { Name = "Laptop", Category = "Electronics", Price = 50000, InStock = true }
};
// AND 条件:食品かつ在庫あり
var foodInStock = products.Where(p => p.Category == "Food" && p.InStock);
// OR 条件:食品または価格500円以下
var cheapOrFood = products.Where(p => p.Category == "Food" || p.Price <= 500);複雑な条件は読みやすさのために変数に分割することもできる。
var result = products.Where(p =>
{
bool isFood = p.Category == "Food";
bool isAffordable = p.Price <= 1000;
bool isAvailable = p.InStock;
return (isFood || isAffordable) && isAvailable;
});動的な条件構築
検索条件が動的に変わる場合、条件付きでWhere を追加する。
string? categoryFilter = "Food";
int? maxPrice = 200;
bool? inStockOnly = true;
IEnumerable<Product> query = products;
if (!string.IsNullOrEmpty(categoryFilter))
{
query = query.Where(p => p.Category == categoryFilter);
}
if (maxPrice.HasValue)
{
query = query.Where(p => p.Price <= maxPrice.Value);
}
if (inStockOnly == true)
{
query = query.Where(p => p.InStock);
}
var results = query.ToList();遅延評価のおかげで、Where を連鎖させても効率が落ちない。最終的に ToList() を呼んだときにまとめて評価される。
メソッドチェーンの組み合わせ
複数の操作を連鎖させる。
var employees = new List<Employee>
{
new Employee { Name = "Alice", Department = "Engineering", Salary = 60000 },
new Employee { Name = "Bob", Department = "Sales", Salary = 50000 },
new Employee { Name = "Charlie", Department = "Engineering", Salary = 70000 },
new Employee { Name = "Diana", Department = "Sales", Salary = 55000 },
new Employee { Name = "Eve", Department = "Engineering", Salary = 65000 }
};
// エンジニアリング部門の給与上位2名
var topEngineers = employees
.Where(e => e.Department == "Engineering")
.OrderByDescending(e => e.Salary)
.Take(2)
.Select(e => new { e.Name, e.Salary });
foreach (var e in topEngineers)
{
Console.WriteLine($"{e.Name}: {e.Salary}");
}
// Charlie: 70000
// Eve: 65000フィルタリング → 並べ替え → 件数制限 → 射影の流れは非常によくあるパターンだ。
GroupBy と集計の組み合わせ
グループ化してから各グループを集計する。
var orders = new List<Order>
{
new Order { CustomerId = 1, Product = "Apple", Amount = 100 },
new Order { CustomerId = 2, Product = "Banana", Amount = 200 },
new Order { CustomerId = 1, Product = "Cherry", Amount = 150 },
new Order { CustomerId = 2, Product = "Date", Amount = 50 },
new Order { CustomerId = 1, Product = "Elderberry", Amount = 300 }
};
var summary = orders
.GroupBy(o => o.CustomerId)
.Select(g => new
{
CustomerId = g.Key,
OrderCount = g.Count(),
TotalAmount = g.Sum(o => o.Amount),
AverageAmount = g.Average(o => o.Amount),
MaxOrder = g.Max(o => o.Amount)
})
.OrderByDescending(x => x.TotalAmount);
foreach (var item in summary)
{
Console.WriteLine($"顧客{item.CustomerId}: {item.OrderCount}件, 合計{item.TotalAmount}円");
}サブクエリ
クエリの中で別のクエリを使う。
var products = new List<Product>
{
new Product { Name = "Apple", Price = 100 },
new Product { Name = "Banana", Price = 80 },
new Product { Name = "Cherry", Price = 200 },
new Product { Name = "Date", Price = 150 }
};
// 平均価格以上の商品
double avgPrice = products.Average(p => p.Price);
var aboveAverage = products.Where(p => p.Price >= avgPrice);
// または一度に書く
var aboveAverage2 = products.Where(p => p.Price >= products.Average(x => x.Price));注意点として、2番目の書き方では products.Average() がループの各反復で呼ばれる可能性がある。パフォーマンスを考えるなら、平均を先に計算しておく方が良い。
複数コレクションの操作
複数のコレクションを組み合わせる。
var customers = new List<Customer>
{
new Customer { Id = 1, Name = "Alice", City = "Tokyo" },
new Customer { Id = 2, Name = "Bob", City = "Osaka" }
};
var orders = new List<Order>
{
new Order { CustomerId = 1, Product = "Apple", Amount = 100 },
new Order { CustomerId = 1, Product = "Banana", Amount = 200 },
new Order { CustomerId = 2, Product = "Cherry", Amount = 150 }
};
// 顧客ごとの注文合計を東京の顧客に限定
var result = customers
.Where(c => c.City == "Tokyo")
.GroupJoin(
orders,
c => c.Id,
o => o.CustomerId,
(customer, customerOrders) => new
{
customer.Name,
TotalAmount = customerOrders.Sum(o => o.Amount)
}
);
foreach (var item in result)
{
Console.WriteLine($"{item.Name}: {item.TotalAmount}円");
}
// Alice: 300円クエリ構文との使い分け
複雑な結合やグループ化はクエリ構文の方が読みやすいこともある。
// クエリ構文
var query1 = from c in customers
join o in orders on c.Id equals o.CustomerId
where c.City == "Tokyo"
group o by c.Name into g
select new { Name = g.Key, Total = g.Sum(o => o.Amount) };
// メソッド構文(同じ結果)
var query2 = customers
.Where(c => c.City == "Tokyo")
.Join(orders, c => c.Id, o => o.CustomerId, (c, o) => new { c.Name, o.Amount })
.GroupBy(x => x.Name)
.Select(g => new { Name = g.Key, Total = g.Sum(x => x.Amount) });両方の書き方を理解しておき、状況に応じて使い分けるのが理想的だ。