異なるデータソース(例えば SQL Server と CSV ファイル)のレコードを内部結合または左外部結合して GridView などに一覧表示する例を書きます。
上の画像はこの記事で紹介するサンプルの実行結果で、SQL Server と CSV ファイルをデータソースに使い、左側が内部結合、右側が左外部結合した結果一覧を ASP.NET Web Forms アプリの GridView に表示したものです。
この記事で使用したデータソースは、Microsoft が提供しているサンプルデータベース Northwind の Orders テーブルと、Customers テーブルから一部のフィールド / レコードを抜き出して作った以下の画像の CSV ファイルです。
データソースが両方とも SQL Server のサンプルデータベース Northwind にあれば、SELECT クエリで JOIN 句を使って結合し、その結果を DataTable などに取得するのが簡単ですが、一方が CSV ファイルではそうはいきません。
ではどうするかと言うと、SQL Server のテーブルと CSV ファイルそれぞれから List<T> 型のオブジェクトを作り、それを Linq で結合した結果を GridView のデータソースとしてバインドしてやるのがよさそうです。
Linq を使って結合する例は Microsoft の文書「join 句 (C# リファレンス)」やそれからリンクが張ってある記事が参考になりました。
上の画像を表示したサンプルコードは以下の通りです。説明はコメントとして書きましたので、それを見てください。(手抜きでスミマセン)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;
// SQL Server の Northwind サンプルデータベース Orders
// テーブルのレコードを格納するクラス定義
public class Order
{
public int OrderID { get; set; }
public string CustomerID { get; set; }
public int? EmployeeID { get; set; }
public DateTime? OrderDate { get; set; }
public DateTime? RequiredDate { get; set; }
public DateTime? ShippedDate { get; set; }
public int? ShipVia { get; set; }
public decimal? Freight { get; set; }
public string ShipName { get; set; }
public string ShipAddress { get; set; }
public string ShipCity { get; set; }
public string ShipRegion { get; set; }
public string ShipPostalCode { get; set; }
public string ShipCountry { get; set; }
}
// CSV ファイルのレコードを格納するためのクラス定義
public class Customer
{
public string CustomerID { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
public string ContactTitle { get; set; }
}
// 結合後の結果を格納するためのクラス定義
public class Result
{
public int OrderID { get; set; }
public string CompanyName { get; set; }
public DateTime? OrderDate { get; set; }
public decimal? Freight { get; set; }
}
public partial class _0019_GridViewJoinedList :
System.Web.UI.Page
{
// SQL Server のサンプルデータベース Northwind の
// Orders テーブルからデータを取得して List<Order>
// オブジェクトを生成。Entity Framework を使う方が簡単
// だが、ここではプリミティブに ADO.NET の SqlDataReader
// を使用した。
protected List<Order> CreateOrderList()
{
List<Order> orders = new List<Order>();
string connString = WebConfigurationManager.
ConnectionStrings["NORTHWINDConnectionString"].
ConnectionString;
string query = "SELECT [OrderID], [CustomerID]," +
"[EmployeeID], [OrderDate], [RequiredDate]," +
"[ShippedDate], [ShipVia], [Freight]," +
"[ShipName], [ShipAddress], [ShipCity]," +
"[ShipRegion], [ShipPostalCode], [shipCountry]" +
"FROM [Orders]";
using (SqlConnection conn = new SqlConnection(connString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(query, conn))
{
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader != null)
{
while (reader.Read())
{
Order record = new Order();
record.OrderID = reader.GetInt32(0);
record.CustomerID = reader.IsDBNull(1) ?
null : reader.GetString(1);
record.EmployeeID = reader.IsDBNull(2) ?
null : (int?)reader.GetInt32(2);
record.OrderDate = reader.IsDBNull(3) ?
null : (DateTime?)reader.GetDateTime(3);
record.RequiredDate = reader.IsDBNull(4) ?
null : (DateTime?)reader.GetDateTime(4);
record.ShippedDate = reader.IsDBNull(5) ?
null : (DateTime?)reader.GetDateTime(5);
record.ShipVia = reader.IsDBNull(6) ?
null : (int?)reader.GetInt32(6);
record.Freight = reader.IsDBNull(7) ?
null : (decimal?)reader.GetDecimal(7);
record.ShipName = reader.IsDBNull(8) ?
null : reader.GetString(8);
record.ShipAddress = reader.IsDBNull(9) ?
null : reader.GetString(9);
record.ShipCity = reader.IsDBNull(10) ?
null : reader.GetString(10);
record.ShipRegion = reader.IsDBNull(11) ?
null : reader.GetString(11);
record.ShipPostalCode = reader.IsDBNull(12) ?
null : reader.GetString(12);
record.ShipCountry = reader.IsDBNull(13) ?
null : reader.GetString(13);
orders.Add(record);
}
}
}
}
}
return orders;
}
// CSV ファイルからデータを取得して List<Customer> オブ
// ジェクトを生成。
protected List<Customer> CreateCustomerList()
{
List<Customer> customers = new List<Customer>();
string csvFile = Server.MapPath("~/App_Data/TextFile.csv");
using (Microsoft.VisualBasic.FileIO.TextFieldParser tfp =
new Microsoft.VisualBasic.FileIO.TextFieldParser(
csvFile,
System.Text.Encoding.GetEncoding("Shift_JIS")))
{
//フィールドがデリミタで区切られている
tfp.TextFieldType =
Microsoft.VisualBasic.FileIO.FieldType.Delimited;
// デリミタを , とする
tfp.Delimiters = new string[] { "," };
// フィールドを " で囲み、改行文字、デリミタを
// 含めることができるか
tfp.HasFieldsEnclosedInQuotes = true;
// フィールドの前後からスペースを削除
tfp.TrimWhiteSpace = true;
while (!tfp.EndOfData)
{
string[] fields = tfp.ReadFields();
Customer customer = new Customer()
{
CustomerID = fields[0],
CompanyName = fields[1],
ContactName = fields[2],
ContactTitle = fields[3]
};
customers.Add(customer);
}
}
return customers;
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
List<Order> orders = CreateOrderList();
List<Customer> customers = CreateCustomerList();
// 内部結合
var innerJoin = from o in orders
join c in customers
on o.CustomerID equals c.CustomerID
select new Result
{
OrderID = o.OrderID,
CompanyName = c.CompanyName,
OrderDate = o.OrderDate,
Freight = o.Freight
};
// シーケンスが空の場合に返すデフォルト値
// 下の DefaultIfEmpty メソッドの引数に設定する
Customer defaultValue = new Customer() {
CustomerID = string.Empty,
CompanyName = string.Empty,
ContactName = string.Empty,
ContactTitle = string.Empty };
// 左外部結合
var leftOuterJoin =
from o in orders
join c in customers
on o.CustomerID equals c.CustomerID into cGroup
from item in cGroup.DefaultIfEmpty(defaultValue)
select new Result
{
OrderID = o.OrderID,
CompanyName = item.CompanyName,
OrderDate = o.OrderDate,
Freight = o.Freight
};
// 上の画像の左側の GridView(内部結合)
GridView1.DataSource = innerJoin;
GridView1.DataBind();
// 上の画像の右側の GridView(左外部結合)
GridView2.DataSource = leftOuterJoin;
GridView2.DataBind();
}
}
}