LINQ to SQL: リレーショナル データ用 .NET 統合言語クエリ

 

Dinesh Kulkarni、Lj Bolognese、Matt Warren、Anders Hejlsberg、Kit George

2007 年 3 月

適用対象:
   Visual Studio Code 名 "Orcas"
   .NET Framework 3.5

概要: LINQ to SQLは、クエリ機能を失うことなく、リレーショナル データをオブジェクトとして管理するためのランタイム インフラストラクチャを提供します。 LINQ to SQLがバックグラウンドで変更を自動的に追跡している間、アプリケーションはオブジェクトを自由に操作できます。 (119ページ印刷)

内容

はじめに
クイック ツアー
   エンティティ クラスの作成
   The DataContext
   リレーションシップの定義
   リレーションシップを介したクエリの実行
   エンティティの変更と保存
クエリ In-Depth
   クエリの実行
   オブジェクト ID
   リレーションシップ
   結合
   プロジェクション
   コンパイル済みクエリ
   SQL 変換
エンティティのライフサイクル
   変更の追跡
   変更の送信
   同時変更
   トランザクション
   ストアド プロシージャ
エンティティ クラス In-Depth
   属性の使用
   グラフの整合性
   変更通知
   継承
高度なトピック
   データベースの作成
   ADO.NET との相互運用
   競合の解決を変更する
   ストアド プロシージャの呼び出し
   エンティティ クラス ジェネレーター ツール
   ジェネレーター ツール DBML リファレンス
   多層エンティティ
   外部マップ
   NET Framework 関数のサポートと注意事項
   デバッグのサポート

はじめに

今日書かれているほとんどのプログラムは、何かの方法でデータを操作し、多くの場合、このデータはリレーショナル データベースに格納されます。 しかし、最新のプログラミング言語とデータベースの間には、情報の表現と操作方法に大きな違いがあります。 このインピーダンスの不一致は、複数の方法で表示されます。 最も注目すべき点は、プログラミング言語は、クエリをテキスト文字列として指定する必要がある API を介してデータベース内の情報にアクセスすることです。 これらのクエリは、プログラム ロジックの重要な部分です。 ただし、これらは言語に対して不透明であり、コンパイル時の検証や IntelliSense などのデザイン時機能の恩恵を受けることができません。

もちろん、違いはそれよりもずっと深くなります。 情報の表現方法 (データ モデル) は、2 つの間で大きく異なります。 最新のプログラミング言語では、オブジェクトの形式で情報を定義します。 リレーショナル データベースでは行が使用されます。 各インスタンスは物理的に別のインスタンスとは異なるので、オブジェクトは一意の ID を持ちます。 行は主キー値によって識別されます。 オブジェクトには、インスタンスを識別してリンクする参照があります。 行は意図的に個別に残され、外部キーを使用して関連する行を緩やかに結び付ける必要があります。 オブジェクトは、別のオブジェクトによって引き続き参照されている限り、独立して存在します。 行はテーブルの要素として存在し、削除されるとすぐに消えます。

このギャップを埋めるために期待されるアプリケーションの構築と保守が困難なのは不思議ではありません。 それは確かにどちらか一方の側を取り除くために方程式を簡素化します。 しかし、リレーショナル データベースは、長期的なストレージとクエリ処理のための重要なインフラストラクチャを提供します。最新のプログラミング言語は、アジャイル開発と豊富な計算に不可欠です。

これまでは、アプリケーション開発者が各アプリケーションでこの不一致を個別に解決する作業を行ってきました。 これまでの最良の解決策は、アプリケーションのドメイン固有のオブジェクト モデルとデータベースの表形式表現の間で情報を渡し、各方法でデータの再整形と再フォーマットを行う、詳細なデータベース抽象化レイヤーでした。 しかし、真のデータ ソースを隠すことで、これらのソリューションはリレーショナル データベースの最も魅力的な機能を捨ててしまうのです。クエリを実行するデータの機能。

Visual Studio Code Name "Orcas" のコンポーネントであるLINQ to SQLは、クエリ機能を失うことなくリレーショナル データをオブジェクトとして管理するためのランタイム インフラストラクチャを提供します。 これを行うには、言語統合クエリを SQL に変換してデータベースで実行し、表形式の結果を定義したオブジェクトに変換します。 その後、LINQ to SQLがバックグラウンドで変更を自動的に追跡している間、アプリケーションはオブジェクトを自由に操作できます。

  • LINQ to SQLは、アプリケーションに影響を受けずに設計されています。
    • LINQ to SQLは単に ADO.NET ファミリの別のコンポーネントであるため、現在の ADO.NET ソリューションを段階的に (同じ接続とトランザクションを共有する) 段階的にLINQ to SQLに移行できます。 LINQ to SQLにはストアド プロシージャの広範なサポートもあり、既存のエンタープライズ資産を再利用できます。
  • LINQ to SQLアプリケーションは簡単に開始できます。
    • リレーショナル データにリンクされたオブジェクトは、通常のオブジェクトと同様に定義でき、プロパティが列にどのように対応するかを識別するために属性でのみ装飾されます。 もちろん、手で行う必要もありません。 既存のリレーショナル データベース スキーマをオブジェクト定義に自動的に変換するためのデザイン時ツールが用意されています。

LINQ to SQLランタイム インフラストラクチャとデザイン時ツールを組み合わせることで、データベース アプリケーション開発者のワークロードが大幅に削減されます。 次の章では、LINQ to SQLを使用してデータベース関連の一般的なタスクを実行する方法の概要について説明します。 リーダーは、Language-Integrated クエリと標準クエリ演算子に精通していることを前提としています。

LINQ to SQLは言語に依存しません。 Language-Integratedクエリを提供するために構築された言語は、リレーショナル データベースに格納されている情報へのアクセスを有効にするために使用できます。 このドキュメントのサンプルは、C# と Visual Basic の両方で示されています。LINQ to SQLは、LINQ 対応バージョンの Visual Basic コンパイラでも使用できます。

クイック ツアー

LINQ to SQL アプリケーションをビルドする最初の手順は、アプリケーション データを表すために使用するオブジェクト クラスを宣言することです。 例を見ていきましょう。

エンティティ クラスの作成

まず、単純なクラス Customer から始めて、Northwind サンプル データベースの customers テーブルに関連付けます。 これを行うには、カスタム属性をクラス宣言の先頭にのみ適用する必要があります。 LINQ to SQLでは、この目的のために Table 属性を定義します。

C#

[Table(Name="Customers")]
public class Customer
{
   public string CustomerID;
   public string City;
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer
   Public CustomerID As String
   Public City As String
End Class

Table 属性には Name プロパティがあり、データベース テーブルの正確な名前を指定するために使用できます。 Name プロパティが指定されていない場合、LINQ to SQLはデータベース テーブルの名前が クラスと同じであると見なします。 テーブルとして宣言されたクラスのインスタンスのみがデータベースに格納されます。 これらの種類のクラスのインスタンスはエンティティと呼 ばれます。 クラス自体は エンティティ クラスと呼ばれます。

クラスをテーブルに関連付けるだけでなく、データベース列に関連付ける各フィールドまたはプロパティを示す必要があります。 このために、LINQ to SQLは Column 属性を定義します。

C#

[Table(Name="Customers")]
public class Customer
{
   [Column(IsPrimaryKey=true)]
   public string CustomerID;
   [Column]
   public string City;
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer
   <Column(IsPrimaryKey:=true)> _
   Public CustomerID As String

   <Column> _
   Public City As String

End Class

Column 属性には、フィールドとデータベース列の間の正確なマッピングをカスタマイズするために使用できるさまざまなプロパティがあります。 メモの 1 つのプロパティは 、Id プロパティです。 データベース列がテーブルの主キーの一部であることをLINQ to SQLに通知します。

Table 属性と同様に、Column 属性に情報を指定する必要があるのは、フィールドまたはプロパティ宣言から推測できる情報と異なる場合のみです。 この例では、CustomerID フィールドがテーブルの主キーの一部であることをLINQ to SQLに伝える必要があります。ただし、正確な名前や型を指定する必要はありません。

列として宣言されたフィールドとプロパティのみが、データベースに永続化されるか、データベースから取得されます。 その他は、アプリケーション ロジックの一時的な部分と見なされます。

The DataContext

DataContext は、データベースからオブジェクトを取得して変更を再送信するメインコンジットです。 ADO.NET 接続を使用するのと同じ方法で使用します。 実際、 DataContext は指定した接続または接続文字列で初期化されます。 DataContext の目的は、オブジェクトの要求をデータベースに対して行われた SQL クエリに変換し、結果からオブジェクトをアセンブルすることです。 DataContext は、WhereSelect などの標準クエリ演算子と同じ演算子パターンを実装することで、言語統合クエリを有効にします。

たとえば、 DataContext を使用して、都市が London である顧客オブジェクトを次のように取得できます。

C#

// DataContext takes a connection string 
DataContext db = new   DataContext("c:\\northwind\\northwnd.mdf");
// Get a typed table to run queries
Table<Customer> Customers = db.GetTable<Customer>();
// Query for customers from London
var q =
   from c in Customers
   where c.City == "London"
   select c;
foreach (var cust in q)
   Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);

Visual Basic

' DataContext takes a connection string 
Dim db As DataContext  = New DataContext("c:\northwind\northwnd.mdf")
' Get a typed table to run queries
Dim Customers As Customers(Of Customer) = db.GetTable(Of Customer)()
' Query for customers from London
Dim londonCustomers = From customer in Customers _
                      Where customer.City = "London" _
                      Select customer
For Each cust in londonCustomers
   Console.WriteLine("id = " & cust.CustomerID & ", City = " & cust.City)
Next

各データベース テーブルは Table コレクションとして表され、エンティティ クラスを使用して GetTable() メソッドを使用してアクセスして識別できます。 基本的な DataContext クラスと GetTable() メソッドに依存するのではなく、厳密に型指定された DataContext を宣言することをお勧めします。 厳密に型指定された DataContext は 、すべての Table コレクションをコンテキストのメンバーとして宣言します。

C#

public partial class Northwind : DataContext
{
   public Table<Customer> Customers;
   public Table<Order> Orders;
   public Northwind(string connection): base(connection) {}
}

Visual Basic

Partial Public Class Northwind 
              Inherits DataContext

   Public Customers As Table(Of Customers)
   Public Orders As Table(Of Orders)
         Public Sub New(ByVal connection As String)
            MyBase.New(connection)
   End Sub
End Class

その後、London の顧客に対するクエリは、次のように簡単に表現できます。

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (var cust in q)
   Console.WriteLine("id = {0}, City = {1}",cust.CustomerID, cust.City);

Visual Basic

Dim db = New Northwind("c:\northwind\northwnd.mdf")
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust
For Each cust in londonCustomers
   Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City) 

Next

概要ドキュメントの残りの部分では、厳密に型指定された Northwind クラスを引き続き使用します。

リレーションシップの定義

リレーショナル データベースのリレーションシップは、通常、他のテーブルの主キーを参照する外部キー値としてモデル化されます。 それらの間を移動するには、リレーショナル結合操作を使用して 2 つのテーブルを明示的にまとめる必要があります。 一方、オブジェクトは、プロパティ参照または "dot" 表記を使用して移動した参照のコレクションを使用して相互に参照します。 明らかに、移動するたびに明示的な結合条件を思い出す必要がないため、点線は結合よりも簡単です。

このようなデータ リレーションシップが常に同じである場合、エンティティ クラスでプロパティ参照としてエンコードすると非常に便利になります。 LINQ to SQLでは、リレーションシップを表すために使用されるメンバーに適用できる Association 属性を定義します。 関連付けリレーションシップは、テーブル間で列の値を照合することによって行われる主キーリレーションシップへの外部キーと似ています。

C#

[Table(Name="Customers")]
public class Customer
{
   [Column(Id=true)]
   public string CustomerID;
   ...
   private EntitySet<Order> _Orders;
   [Association(Storage="_Orders", OtherKey="CustomerID")]
   public EntitySet<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer

   <Column(Id:=true)> _
   Public CustomerID As String
   ...
   Private _Orders As EntitySet(Of Order)
                  <Association(Storage:="_Orders", OtherKey:="CustomerID")> _
         Public Property Orders() As EntitySet(Of Order)
            Get
               Return Me._Orders
            End Get
            Set(ByVal value As EntitySet(Of Order))
            End Set
   End Property

End Class

Customer クラスに、顧客とその注文間の関係を宣言するプロパティが含まれるようになりました。 Orders プロパティの種類は EntitySet です。リレーションシップは 1 対多であるためです。 Association 属性の OtherKey プロパティを使用して、この関連付けの実行方法を説明します。 このクラスと比較する関連クラスのプロパティの名前を指定します。 指定しなかった ThisKey プロパティも存在します。 通常は、リレーションシップのこの側のメンバーを一覧表示するために使用します。 ただし、これを省略することで、LINQ to SQLは主キーを構成するメンバーからそれらを推論できます。

Order クラスの定義でこれが逆になっていることに注意してください。

C#

[Table(Name="Orders")]
public class Order
{
   [Column(Id=true)]
   public int OrderID;
   [Column]
   public string CustomerID;
   private EntityRef<Customer> _Customer;    
   [Association(Storage="_Customer", ThisKey="CustomerID")]
   public Customer Customer {
      get { return this._Customer.Entity; }
      set { this._Customer.Entity = value; }
   }
}

Visual Basic

<Table(Name:="Orders")> _
Public Class Order

   <Column(Id:=true)> _
   Public OrderID As String
   <Column> _
   Public CustomerID As String
   Private _Customer As EntityRef(Of Customer)
         <Association(Storage:="_Customer", ThisKey:="CustomerID")> _
         Public Property Customer() As Customer
            Get
               Return Me._Customer.Entity
            End Get
            Set(ByVal value As Customer)
               Me._Customers.Entity = value
            End Set
   End Property
End Class

Order クラスは EntityRef 型を使用して、顧客との関係を記述します。 遅延読み込みをサポートするには、EntityRef クラスの使用が必要です (後で説明します)。 Customer プロパティの Association 属性は、推論不可能なメンバーがリレーションシップのこの側に存在するため、ThisKey プロパティを指定します。

Storage プロパティも 参照してください。 プロパティの値を保持するために使用されるプライベート メンバーをLINQ to SQLに指示します。 これにより、LINQ to SQLは、その値を格納して取得するときに、パブリック プロパティ アクセサーをバイパスできます。 これは、アクセサーに書き込まれるカスタム ビジネス ロジックを回避するためにLINQ to SQLする場合に不可欠です。 ストレージ プロパティが指定されていない場合は、代わりにパブリック アクセサーが使用されます。 Column 属性で Storage プロパティを使用することもできます。

エンティティ クラスにリレーションシップを導入すると、通知とグラフの一貫性のサポートが導入されるにつれて、記述する必要があるコードの量が増えます。 幸いなことに、必要なすべての定義を部分クラスとして生成するために使用できるツール (後述) があり、生成されたコードとカスタム ビジネス ロジックを組み合わせて使用できます。

このドキュメントの残りの部分では、完全な Northwind データ コンテキストとすべてのエンティティ クラスを生成するためにツールが使用されていることを前提としています。

リレーションシップを介したクエリの実行

リレーションシップが作成されたので、クラスで定義されているリレーションシップ プロパティを参照するだけで、クエリを記述するときに使用できます。

C#

var q =
   from c in db.Customers
   from o in c.Orders
   where c.City == "London"
   select new { c, o };

Visual Basic

Dim londonCustOrders = From cust In db.Customers, ord In cust.Orders _
                       Where cust.City = "London" _
                       Select Customer = cust, Order = ord

上記のクエリでは 、Orders プロパティを使用して顧客と注文の間でクロス積を形成し、 Customer と Order のペアの新しいシーケンス 生成します。

逆の操作も可能です。

C#

var q =
   from o in db.Orders
   where o.Customer.City == "London"
   select new { c = o.Customer, o };

Visual Basic

Dim londonCustOrders = From ord In db.Orders _
                       Where ord.Customer.City = "London" _
                       Select Customer = ord.Customer, Order = ord

この例では、注文に対してクエリを実行し、 Customer リレーションシップを使用して、関連付けられている Customer オブジェクトの情報にアクセスします。

エンティティの変更と保存

クエリのみを念頭に置いて構築されているアプリケーションはほとんどありません。 データも作成および変更する必要があります。 LINQ to SQLは、オブジェクトに加えられた変更を操作および永続化する上で最大限の柔軟性を提供するように設計されています。 エンティティ オブジェクトを使用できるようになったら(クエリを使用して取得するか、新たに作成するか)、アプリケーションで通常のオブジェクトとして操作したり、値を変更したり、コレクションに追加したりコレクションから削除したりできます。 LINQ to SQLはすべての変更を追跡し、完了したらすぐにデータベースに送信する準備が整います。

次の例では、Northwind サンプル データベース全体のメタデータからツールによって生成された Customer クラスと Order クラスを使用します。 簡潔にするために、クラス定義は表示されていません。

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// Query for a specific customer
string id = "ALFKI";
var cust = db.Customers.Single(c => c.CustomerID == id);
// Change the name of the contact
cust.ContactName = "New Contact";
// Create and add a new Order to Orders collection
Order ord = new Order { OrderDate = DateTime.Now };
cust.Orders.Add(ord);
// Ask the DataContext to save all the changes
db.SubmitChanges();

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' Query for a specific customer
Dim id As String = "ALFKI"
Dim targetCustomer = (From cust In db.Customers _ 
                     Where cust.CustomerID = id).First
' Change the name of the contact
targetCustomer.ContactName = "New Contact"
' Create and add a new Order to Orders collection
Dim id = New Order With { .OrderDate = DateTime.Now }
targetCustomer.Orders.Add(ord)
' Ask the DataContext to save all the changes
db.SubmitChanges()

SubmitChanges() が呼び出されると、LINQ to SQLは変更をデータベースに送信するために SQL コマンドを自動的に生成して実行します。 この動作をカスタム ロジックでオーバーライドすることもできます。 カスタム ロジックは、データベース ストアド プロシージャを呼び出す場合があります。

クエリ In-Depth

LINQ to SQLでは、リレーショナル データベース内のテーブルに関連付けられているオブジェクトに対する標準クエリ演算子の実装が提供されます。 この章では、クエリのLINQ to SQL固有の側面について説明します。

クエリの実行

クエリを高レベルの クエリ式 として記述する場合でも、個々の演算子からクエリを作成する場合でも、記述するクエリは、すぐに実行される命令型ステートメントではありません。 説明です。 たとえば、ローカル変数 q の下の宣言では、クエリを実行した結果ではなく、クエリの説明を参照します。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
For Each cust  In londonCustomers
   Console.WriteLine(cust.CompanyName) 
Next

このインスタンスの q の実際の型は 、IQueryable<Customer です>。 アプリケーションが実際に実行するクエリの内容の列挙を試みるまでではありません。 この例では、 foreach ステートメントによって実行が実行されます。

IQueryable オブジェクトは、ADO.NET コマンド オブジェクトに似ています。 一方を持つことは、クエリが実行されたことを意味するものではありません。 コマンド オブジェクトは、クエリを記述する文字列を保持します。 同様に、 IQueryable オブジェクトは 、Expression と呼ばれるデータ構造としてエンコードされたクエリの説明を保持します。 コマンド オブジェクトには実行を実行する ExecuteReader() メソッドがあり、結果は DataReader として返されます。 IQueryable オブジェクトには、実行を引き起こす GetEnumerator() メソッドがあり、結果は IEnumerator<Customer として返されます>

したがって、クエリが 2 回列挙された場合、クエリは 2 回実行されます。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
// Execute first time
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);
// Execute second time
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
' Execute first time
For Each cust In londonCustomers
   Console.WriteLine(cust.CompanyName) 
Next
' Execute second time
For Each cust In londonCustomers
   Console.WriteLine(cust.CustomerID) 
Next

この動作は、 遅延実行と呼ばれます。 ADO.NET コマンド オブジェクトと同様に、クエリを保持して再実行することもできます。

もちろん、多くの場合、アプリケーション ライターは、クエリが実行される場所とタイミングについて非常に明示的である必要があります。 アプリケーションでクエリを複数回実行する場合、結果を複数回調べる必要があったという理由だけで予期しない可能性があります。 たとえば、クエリの結果を DataGrid のようなものにバインドできます。 コントロールは、画面に描画するたびに結果を列挙できます。

複数回実行されないようにするには、結果を任意の数の標準コレクション クラスに変換します。 標準クエリ演算子 ToList() または ToArray() を使用すると、結果をリストまたは配列に簡単に変換できます。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
// Execute once using ToList() or ToArray()
var list = q.ToList();
foreach (Customer c in list)
   Console.WriteLine(c.CompanyName);
foreach (Customer c in list)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
' Execute once using ToList() or ToArray()
Dim londonCustList = londonCustomers.ToList()
' Neither of these iterations re-executes the query
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next 

遅延実行の利点の 1 つは、構築が完了したときにのみ実行でクエリを段階的に構築できる点です。 クエリの一部を作成し、ローカル変数に割り当ててから、後でさらに多くの演算子を適用し続けることができます。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
if (orderByLocation) {
   q =
      from c in q
      orderby c.Country, c.City
      select c;
}
else if (orderByName) {
   q =
      from c in q
      orderby c.ContactName
      select c;
}
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
if orderByLocation Then
   londonCustomers = From cust in londonCustomers _
                     Order By cust.Country, cust.City

Else If orderByName Then
   londonCustomers = From cust in londonCustomers _
                     Order By cust.ContactName
End If
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next 

この例では、 q はロンドンのすべての顧客のクエリとして開始されます。 その後、アプリケーションの状態に応じて順序付けされたクエリに変わります。 実行を遅延させることで、危険な文字列操作を必要とせずに、アプリケーションの正確なニーズに合わせてクエリを構築できます。

オブジェクト ID

ランタイム内のオブジェクトには一意の ID があります。 2 つの変数が同じオブジェクトを参照している場合、実際には同じオブジェクト インスタンスを参照しています。 このため、1 つの変数のパスを介して行われた変更は、もう一方の変数を通じてすぐに表示されます。 リレーショナル データベース テーブル内の行には一意の ID がありません。 ただし、主キーがあり、主キーが一意である可能性があります。つまり、2 つの行が同じキーを共有することはできません。 ただし、これはデータベース テーブルの内容のみを制約します。 したがって、リモート コマンドを介してのみデータを操作する限り、同じ程度になります。

ただし、このケースはほとんどありません。 ほとんどの場合、データはデータベースから取り出され、アプリケーションによって操作される別の層に取り込まれます。 明らかに、これはLINQ to SQLサポートするように設計されたモデルです。 データがデータベースから行として取り出される場合、同じデータを表す 2 つの行が実際には同じ行インスタンスに対応するとは思われません。 特定の顧客に対して 2 回クエリを実行すると、それぞれ同じ情報を含む 2 行のデータが取得されます。

しかし、オブジェクトでは、まったく異なるものが必要です。 DataContext に同じ情報をもう一度要求すると、実際には同じオブジェクト インスタンスが返されます。 これは、オブジェクトがアプリケーションにとって特別な意味を持ち、通常のオブジェクトのように動作することを期待しているためです。 あなたはそれらを階層またはグラフとして設計し、同じものを 2 回要求したからといって、レプリケートされたインスタンスの大群なしで、それらを取得することを確かに期待しています。

このため、 DataContext は オブジェクト ID を管理します。 新しい行がデータベースから取得されるたびに、その主キーによって ID テーブルに記録され、新しいオブジェクトが作成されます。 同じ行が再度取得されるたびに、元のオブジェクト インスタンスがアプリケーションに返されます。 このようにして、 DataContext は ID (キー) のデータベースの概念を言語の概念 (インスタンス) に変換します。 アプリケーションでは、最初に取得された状態のオブジェクトのみが表示されます。 新しいデータが異なる場合は、破棄されます。

アプリケーションがデータを捨てるのはなぜですか? これが、ローカル オブジェクトの整合性LINQ to SQL管理し、オプティミスティック更新をサポートする方法です。 オブジェクトが最初に作成された後に発生する変更は、アプリケーションによって行われた変更だけなので、アプリケーションの意図は明確です。 外部パーティによる変更が中間に発生した場合、 SubmitChanges() が呼び出されるときに識別されます。 詳細については、「同時変更」セクションを参照してください。

データベースに主キーのないテーブルが含まれている場合、LINQ to SQLはテーブルに対してクエリを送信できますが、更新は許可されません。 これは、一意のキーがないため、更新する行をフレームワークで識別できないためです。

もちろん、クエリによって要求されたオブジェクトが、既に取得されているために主キーによって簡単に識別できる場合、クエリはまったく実行されません。 ID テーブルは、以前に取得したすべてのオブジェクトを格納するキャッシュとして機能します。

リレーションシップ

クイック ツアーで説明したように、クラス定義内の他のオブジェクトまたは他のオブジェクトのコレクションへの参照は、データベース内の外部キーリレーションシップに直接対応しています。 これらのリレーションシップは、ドット表記を使用してリレーションシップ プロパティにアクセスし、オブジェクト間を移動するだけでクエリを実行するときに使用できます。 これらのアクセス操作は、同等の SQL のより複雑な結合または相関サブクエリに変換され、クエリ中にオブジェクト グラフを確認できます。 たとえば、次のクエリでは、ロンドン在住の顧客からの注文のみが結果として得られるように注文から顧客へと移動します。

C#

var q =
   from o in db.Orders
   where o.Customer.City == "London"
   select o;

Visual Basic

Dim londonOrders = From ord In db.Orders _
                       where ord.Customer.City = "London"

リレーションシップ プロパティが存在しない場合は、SQL クエリの場合と同様に、手動で結合として記述する必要があります。

C#

var q =
   from c in db.Customers
   join o in db.Orders on c.CustomerID equals o.CustomerID
   where c.City == "London"
   select o;

Visual Basic

Dim londonOrders = From cust In db.Customers _
                            Join ord In db.Orders _
                            On cust.CustomerID Equals ord.CustomerID _
                   Where ord.Customer.City = "London" _
                   Select ord

リレーションシップ プロパティを使用すると、一度この特定のリレーションシップを定義して、より便利なドット構文を使用できます。 ただし、これはリレーションシップ プロパティが存在する理由ではありません。 ドメイン固有のオブジェクト モデルを階層またはグラフとして定義する傾向があるため、存在します。 プログラムに対して選択するオブジェクトには、他のオブジェクトへの参照があります。 オブジェクトとオブジェクトのリレーションシップは、プロパティ アクセスが結合を書き込む便利な方法につながるデータベースの外部キー スタイルのリレーションシップに対応しているため、これは幸いな偶然にすぎません。

したがって、リレーションシップ プロパティの存在は、クエリ自体の一部としてよりも、クエリの結果側で重要です。 特定の顧客を手に入れると、そのクラス定義によって、顧客に注文があることを示します。 そのため、特定の顧客の Orders プロパティを調べると、コレクションにすべての顧客の注文が設定されます。これは実際には、この方法でクラスを定義して宣言したコントラクトであるためです。 特に注文を前もって要求しなかった場合でも、そこに注文が表示されます。 オブジェクト モデルは、データベースのメモリ内拡張機能であり、関連するオブジェクトがすぐに使用可能であるという錯覚を維持することが期待されます。

LINQ to SQLは、この錯覚を維持するために、遅延読み込みと呼ばれる手法を実装します。 オブジェクトに対してクエリを実行する場合は、実際には要求したオブジェクトのみを取得します。 関連オブジェクトが自動で同時にフェッチされることはありません。 ただし、関連オブジェクトにアクセスしようとするとすぐに要求が出て取得されるため、関連オブジェクトがまだ読み込まれていないという事実は観察できません。

C#

var q =
   from o in db.Orders
   where o.ShipVia == 3
   select o;
foreach (Order o in q) {
   if (o.Freight > 200)
      SendCustomerNotification(o.Customer);
   ProcessOrder(o);
}

Visual Basic

Dim shippedOrders = From ord In db.Orders _
                    where ord.ShipVia = 3
For Each ord In shippedOrders
   If ord.Freight > 200 Then
      SendCustomerNotification(ord.Customer) 
      ProcessOrder(ord)
   End If
Next

たとえば、特定の注文セットに対してクエリを実行した後、特定の顧客に電子メール通知を送信する場合があります。 すべての注文ですべての顧客データを前もって取得する必要はありません。 遅延読み込みでは、絶対に必要になるまで追加情報を取得するコストを延期できます。

もちろん、逆のことも当てはまるかもしれません。 顧客データと注文データを同時に調べなければならないアプリケーションがある場合があります。 両方のデータが必要であることがわかっています。 各顧客の注文を取得するとすぐに、アプリケーションがドリルダウンされることがわかります。 すべての顧客の注文に対して個々のクエリを実行するのは残念です。 実際に行いたいのは、注文データを顧客と一緒に取得することです。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (Customer c in q) {
   foreach (Order o in c.Orders) {
      ProcessCustomerOrder(o);
   }
}

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                   Where cust.City = "London"
For Each cust In londonCustomers
   For Each ord In cust.Orders
      ProcessCustomerOrder(ord) 
   End If
Next

確かに、クロス積を形成し、データのすべての相対ビットを 1 つの大きなプロジェクションとして取得することで、クエリで顧客と注文を結合する方法を常に見つけることができます。 ただし、結果はエンティティではありません。 エンティティは、変更できる ID を持つオブジェクトであり、結果は変更および永続化できないプロジェクションになります。 さらに悪いことに、フラット化された結合出力の各注文に対して各顧客が繰り返されるため、膨大な量の冗長データを取得することになります。

実際に必要なのは、一連の関連オブジェクトを同時に取得する方法です。これは、グラフの線を引いた部分であるため、目的の使用に必要な以上または少ない値を取得することはありません。

LINQ to SQLを使用すると、この理由だけで、オブジェクト モデルの領域の即時読み込みを要求できます。 これは、DataContextDataShape の指定を許可することによって行われます。 DataShape クラスは、特定の型が取得されたときに取得するオブジェクトについてフレームワークに指示するために使用されます。 これは、次のように LoadWith メソッドを使用して実現されます。

C#

DataShape ds = new DataShape();
ds.LoadWith<Customer>(c => c.Orders);
db.Shape = ds;
var q = 
   from c in db.Customers
   where c.City == "London"
   select c;

Visual Basic

Dim ds As DataShape = New DataShape()
ds.LoadWith(Of Customer)(Function(c As Customer) c.Orders)
db.Shape = ds
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust

前のクエリでは、クエリの実行時に London に住むすべての顧客のすべての Orders が取得されるため、Customer オブジェクトの Orders プロパティに連続してアクセスしてもデータベース クエリはトリガーされません。

DataShape クラスを使用して、リレーションシップ ナビゲーションに適用されるサブクエリを指定することもできます。 たとえば、今日出荷された注文だけを取得する場合は、次のように DataShapeAssociateWith メソッドを使用できます。

C#

DataShape ds = new DataShape();
ds.AssociateWith<Customer>(
   c => c.Orders.Where(p => p.ShippedDate != DateTime.Today));
db.Shape = ds;
var q = 
   from c in db.Customers
   where c.City == "London"
   select c;
foreach(Customer c in q) {
   foreach(Order o in c.Orders) {}
}

Visual Basic

Dim ds As DataShape = New DataShape()
ds.AssociateWith(Of Customer)( _
         Function(cust As Customer) From cust In db.Customers _
                                 Where order.ShippedDate <> Today _
                                 Select cust)
db.Shape = ds
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust
For Each cust in londonCustomers
   For Each ord In cust.Orders …
   Next
   Next

前のコードでは、内部 foreach ステートメントは、このような注文だけがデータベースから取得されているため、今日出荷された Orders に対して反復処理を行います。

DataShape クラスに関する 2 つの事実に注目することが重要です。

  1. DataShapeDataContext に割り当てた後は、DataShape を変更できません。 このような DataShapeLoadWith メソッドまたは AssociateWith メソッドを呼び出すと、実行時にエラーが返されます。

  2. LoadWith または AssociateWith を使用してサイクルを作成することはできません。 たとえば、次の例では実行時にエラーが生成されます。

    C#

    DataShape ds = new DataShape();
    ds.AssociateWith<Customer>(
             c=>c.Orders.Where(o=> o.Customer.Orders.Count() < 35);
    

    Visual Basic

    Dim ds As DataShape = New DataShape()
    ds.AssociateWith(Of Customer)( _
             Function(cust As Customer) From ord In cust.Orders _
                          Where ord.Customer.Orders.Count() < 35)
    

結合

オブジェクト モデルに対するほとんどのクエリは、オブジェクト モデル内のオブジェクト参照の移動に大きく依存します。 ただし、オブジェクト モデルで参照としてキャプチャされない可能性があるエンティティ間には興味深い "リレーションシップ" があります。 たとえば、 Customer.Orders は、Northwind データベースの外部キーリレーションシップに基づく便利なリレーションシップです。 ただし、同じ市区町村または国のサプライヤーと顧客は、外部キーリレーションシップに基づかない アドホック リレーションシップであり、オブジェクト モデルでキャプチャされない可能性があります。 結合は、このようなリレーションシップを処理するための追加のメカニズムを提供します。 LINQ to SQLでは、LINQ で導入された新しい結合演算子がサポートされます。

次の問題を考えてみましょう。同じ都市に拠点を置くサプライヤーと顧客を見つけます。 次のクエリでは、仕入先と顧客の会社名と共通の市区町村がフラット化された結果として返されます。 これは、リレーショナル データベースの内部等価結合に相当します。

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City
   select new {
      Supplier = s.CompanyName,
      Customer = c.CompanyName,
      City = c.City
   };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Join cust In db.Customers _
                              On sup.City Equals cust.City _
                        Select Supplier = sup.CompanyName, _
                        CustomerName = cust.CompanyName, _
                        City = cust.City

上記のクエリでは、特定の顧客と同じ都市にいないサプライヤーが削除されます。 ただし、 アドホック リレーションシップ内のエンティティの 1 つを排除したくない場合があります。 次のクエリでは、各仕入先の顧客グループを持つすべての仕入先を一覧表示します。 特定のサプライヤーが同じ都市に顧客を持っていない場合、結果はそのサプライヤーに対応する顧客の空のコレクションになります。 結果はフラットではないことに注意してください。各サプライヤーには関連する コレクションがあります。 実質的に、これはグループ結合を提供し、2 つのシーケンスを結合し、2 番目のシーケンスの要素を最初のシーケンスの要素でグループ化します。

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into scusts
   select new { s, scusts };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Select Supplier = sup, _
                        Customers = supCusts

グループ結合は、複数のコレクションに拡張することもできます。 次のクエリでは、仕入先と同じ都市にある従業員を一覧表示することで、上記のクエリを拡張します。 この結果は、顧客と従業員の (空の可能性がある) コレクションを持つサプライヤーを示しています。

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into scusts
   join e in db.Employees on s.City equals e.City into semps
   select new { s, scusts, semps };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Group Join emp In db.Employees _
                              On sup.City Equals emp.City _
                              Into supEmps _
                        Select Supplier = sup, _
                        Customers = supCusts, Employees = supEmps

グループ結合の結果はフラット化することもできます。 サプライヤーと顧客の間でグループ結合をフラット化した結果は、顧客ごとに 1 つずつ、市内に複数の顧客を持つサプライヤーの複数のエントリです。 空のコレクションは null に置き換えられます。 これは、リレーショナル データベースの左外部等価結合と同じです。

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into sc
   from x in sc.DefaultIfEmpty()
   select new {
      Supplier = s.CompanyName, 
      Customer = x.CompanyName, 
      City = x.City 
   };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Select Supplier = sup, _
                        CustomerName = supCusts.CompanyName, sup.City

基になる結合演算子のシグネチャは、標準のクエリ演算子ドキュメントで定義されています。 等結合のみがサポートされており、 等号 の 2 つのオペランドは同じ型である必要があります。

プロジェクション

ここまでは、 エンティティ (データベース テーブルに直接関連付けられたオブジェクト) を取得するためのクエリのみを確認してきました。 私たちは、このだけに自分自身を制約する必要はありません。 クエリ言語の美しさは、必要な任意の形式で情報を取得できることです。 自動変更追跡や ID 管理を利用することはできません。 ただし、必要なデータだけを取得できます。

たとえば、ロンドンのすべての顧客の会社名を知る必要がある場合があります。 この場合、名前を選択するだけで顧客オブジェクト全体を取得する特別な理由はありません。 クエリの一部として名前を投影できます。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c.CompanyName;

Visual Basic

Dim londonCustomerNames = From cust In db.Customer _
                          Where cust.City = "London" _
                          Select cust.CompanyName

この場合、 q は文字列のシーケンスを取得するクエリになります。

単一の名前以上のものを取得したいが、顧客オブジェクト全体のフェッチを正当化するのに十分ではない場合は、クエリの一部として結果を作成することで、必要なサブセットを指定できます。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.CompanyName, c.Phone };

Visual Basic

Dim londonCustomerInfo = From cust In db.Customer _
                         Where cust.City = "London" _
                         Select cust.CompanyName, cust.Phone

この例では、 匿名オブジェクト初期化子 を使用して、会社名と電話番号の両方を保持する構造体を作成します。 型を呼び出す方法がわからない場合がありますが、必ずしも必要ない言語で 暗黙的に型指定されたローカル変数宣言 を使用します。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.CompanyName, c.Phone };
foreach(var c in q)
   Console.WriteLine("{0}, {1}", c.CompanyName, c.Phone);

Visual Basic

Dim londonCustomerInfo = From cust In db.Customer _
                         Where cust.City = "London" _
                         Select cust.CompanyName, cust.Phone
For Each cust In londonCustomerInfo 
   Console.WriteLine(cust.CompanyName & ", " & cust.Phone) 
Next

データをすぐに使用する場合、 匿名型 は、クエリ結果を保持するクラスを明示的に定義する代わりに適しています。

オブジェクト全体のクロス積を形成することもできますが、そうする理由はほとんどありません。

C#

var q =
   from c in db.Customers
   from o in c.Orders
   where c.City == "London"
   select new { c, o };

Visual Basic

Dim londonOrders = From cust In db.Customer, _
                   ord In db.Orders _
                   Where cust.City = "London" _
                   Select Customer = cust, Order = ord

このクエリは、customer オブジェクトと order オブジェクトのペアのシーケンスを構築します。

また、クエリの任意の段階でプロジェクションを行うこともできます。 新しく構築されたオブジェクトにデータを投影し、後続のクエリ操作でそれらのオブジェクトのメンバーを参照できます。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new {Name = c.ContactName, c.Phone} into x
   orderby x.Name
   select x;

Visual Basic

Dim londonItems = From cust In db.Customer _
                  Where cust.City = "London" _
                  Select Name = cust.ContactName, cust.Phone _
                  Order By Name

ただし、この段階でパラメーター化されたコンストラクターを使用することは注意してください。 これを行うのは技術的に有効ですが、コンストラクター内の実際のコードを理解せずに、コンストラクターの使用がメンバー状態に与える影響を追跡LINQ to SQLできません。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new MyType(c.ContactName, c.Phone) into x
   orderby x.Name
   select x;

Visual Basic

Dim londonItems = From cust In db.Customer _
                  Where cust.City = "London" _
                  Select MyType = New MyType(cust.ContactName, cust.Phone) _
                  Order By MyType.Name

LINQ to SQLはクエリを純粋なリレーショナル SQL ローカルで定義されたオブジェクト型に変換しようとするので、実際に構築するサーバーでは使用できません。 すべてのオブジェクトの構築は、データがデータベースから取り戻されるまで、実際には延期されます。 生成された SQL では、実際のコンストラクターの代わりに、通常の SQL 列プロジェクションが使用されます。 クエリトランスレーターはコンストラクター呼び出し中に何が起こっているのかを理解できないため、MyTypeName フィールドの意味を確立できません。

代わりに、オブジェクト 初期化子 を常に使用してプロジェクションをエンコードすることをお勧めします。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new MyType { Name = c.ContactName, HomePhone = c.Phone } into x
   orderby x.Name
   select x;

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London" _
                      Select Contact = New With {.Name = cust.ContactName, _
                      .Phone = cust.Phone} _
                      Order By Contact.Name

パラメーター化されたコンストラクターを使用する唯一の安全な場所は、クエリの最終的なプロジェクションにあります。

C#

var e =
   new XElement("results",
      from c in db.Customers
      where c.City == "London"
      select new XElement("customer",
         new XElement("name", c.ContactName),
         new XElement("phone", c.Phone)
      )
   );

Visual Basic

      Dim x = <results>
                  <%= From cust In db.Customers _
                      Where cust.City = "London" _
                      Select <customer>
                         <name><%= cust.ContactName %></name>
                         <phone><%= cust.Phone %></phone>
                      </customer> 
                 %>
        </results>

クエリの結果から直接 XML を構築するこの例のように、必要に応じてオブジェクト コンストラクターの入れ子を複雑にすることもできます。 これは、クエリの最後のプロジェクションである限り機能します。

それでも、コンストラクター呼び出しが理解されていても、ローカル メソッドの呼び出しができない可能性があります。 最終的なプロジェクションでローカル メソッドの呼び出しが必要な場合、LINQ to SQLが義務付ける可能性は低くなります。 SQL への既知の変換がないメソッド呼び出しは、クエリの一部として使用できません。 このルールの 1 つの例外は、クエリ変数に依存する引数を持たないメソッド呼び出しです。 これらは変換されたクエリの一部とは見なされず、代わりにパラメーターとして扱われます。

まだ詳細なプロジェクション (変換) では、実装するためにローカルの手続き型ロジックが必要になる場合があります。 最終的なプロジェクションで独自のローカル メソッドを使用するには、2 回投影する必要があります。 最初のプロジェクションでは、参照する必要があるすべてのデータ値が抽出され、2 番目のプロジェクションによって変換が実行されます。 これら 2 つのプロジェクションの間では、AsEnumerable() 演算子を呼び出して、その時点での処理をLINQ to SQLクエリからローカルで実行されるクエリにシフトします。

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.ContactName, c.Phone };
var q2 =
   from c in q.AsEnumerable()
   select new MyType {
      Name = DoNameProcessing(c.ContactName),
      Phone = DoPhoneProcessing(c.Phone)
   };

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London" _
                      Select cust.ContactName, cust.Phone

Dim processedCustomers = From cust In londonCustomers.AsEnumerable() _
                         Select Contact = New With { _
                         .Name = DoNameProcessing(cust.ContactName), _
                         .Phone = DoPhoneProcessing(cust.Phone)}

メモAsEnumerable() 演算子は、ToList() および ToArray()とは異なり、クエリを実行しません。 これは引き続き延期されます。 AsEnumerable() 演算子は、クエリの静的な型指定を変更するだけで、IQueryable<T> (Visual Basic では IQueryable (ofT) を Visual Basic の IEnumerable<T> (IEnumerable (ofT) に変換し、コンパイラはクエリの残りの部分をローカルで実行されるものとして処理するように誘導します。

コンパイル済みクエリ

多くのアプリケーションでは、構造的に似たクエリを何度も実行するのが一般的です。 このような場合は、クエリを 1 回コンパイルし、異なるパラメーターを使用してアプリケーションで複数回実行することで、パフォーマンスを向上させることができます。 この結果は、CompiledQuery クラスを使用してLINQ to SQLで取得されます。 次のコードは、コンパイル済みクエリを定義する方法を示しています。

C#

static class Queries
{
   public static Func<Northwind, string, IQueryable<Customer>>
      CustomersByCity = CompiledQuery.Compile((Northwind db, string city) =>
         from c in db.Customers where c.City == city select c);
}

Visual Basic

Class Queries
   public Shared Function(Of Northwind, String, IQueryable(Of Customer)) _      CustomersByCity = CompiledQuery.Compile( _
                Function(db As Northwind, city As String) _
                From cust In db.Customers Where cust.City = city)
End Class

Compile メソッドは、入力パラメーターを変更するだけで、キャッシュして後で複数回実行できるデリゲートを返します。 この例を次のコードに示します。

C#

public IEnumerable<Customer> GetCustomersByCity(string city) {
         Northwind db = new Northwind();
         return Queries.CustomersByCity(myDb, city);
}

Visual Basic

Public Function GetCustomersByCity(city As String) _ 
               As IEnumerable(Of Customer)
         Dim db As Northwind = New Northwind()
         Return Queries.CustomersByCity(myDb, city)
End Function

SQL 変換

LINQ to SQLは実際にはクエリを実行しません。リレーショナル データベースはクエリを実行します。 LINQ to SQLは、記述したクエリを同等の SQL クエリに変換し、処理のためにサーバーに送信します。 実行は遅延されるため、LINQ to SQLは複数の部分から組み立てられた場合でも、クエリ全体を調べることができます。

リレーショナル データベース サーバーは(SQL Server 2005 の CLR 統合を除いて) IL を実際に実行していないため、クエリは IL としてサーバーに送信されません。 実際には、パラメーター化された SQL クエリとしてテキスト形式で送信されます。

もちろん、SQL (CLR 統合を使用した T-SQL であっても) は、プログラムでローカルに使用できるさまざまなメソッドを実行することはできません。 したがって、記述するクエリは、SQL 環境内で使用できる同等の操作と関数に変換する必要があります。

.Net Framework 組み込み型のほとんどのメソッドと演算子は、SQL に直接変換されます。 一部は、使用可能な関数から生成できます。 変換できない例外は許可されません。使用しようとすると実行時例外が生成されます。 このドキュメントの後半には、SQL に変換するために実装されるフレームワーク メソッドについて詳しく説明するセクションがあります。

エンティティのライフサイクル

LINQ to SQLは、リレーショナル データベースの標準クエリ演算子の実装だけではありません。 クエリの翻訳に加えて、オブジェクトを有効期間中管理するサービスであり、データの整合性を維持し、変更をストアに翻訳するプロセスを自動化するのに役立ちます。

一般的なシナリオでは、アプリケーションが変更をサーバーに送り返す準備ができるまで、オブジェクトは 1 つ以上のクエリを使用して取得され、何らかの方法で操作されます。 このプロセスは、アプリケーションがこの情報に使用されなくなるまで、何度も繰り返される場合があります。 その時点で、オブジェクトは通常のオブジェクトと同様にランタイムによって再利用されます。 ただし、データはデータベースに残ります。 実行時の存在から消去された後でも、同じデータを表すオブジェクトを取得できます。 この意味では、オブジェクトの真の有効期間は、単一の実行時の症状を超えて存在します。

この章の焦点は、特定のランタイム コンテキスト内でエンティティ オブジェクトの単一の症状の期間をサイクルが参照するエンティティ の ライフサイクル です。 このサイクルは、 DataContext が新しいインスタンスを認識し、オブジェクトまたは DataContext が不要になったときに終了すると開始されます。

変更の追跡

エンティティがデータベースから取得されたら、自由に操作できます。 これらはあなたのオブジェクトです。は、ユーザーが行うとおりに使用します。 このようにすると、LINQ to SQLは変更を追跡して、SubmitChanges() が呼び出されたときにデータベースに保持できるようにします。

LINQ to SQLは、データベースからエンティティが取得された時点で、エンティティを手に取る前に追跡を開始します。 実際、前述の ID 管理サービス も既に開始されています。 変更追跡のコストは、実際に変更を開始するまで、追加のオーバーヘッドがほとんど発生しません。

C#

Customer cust = db.Customers.Single(c => c.CustomerID == "ALFKI");
cust.CompanyName = "Dr. Frogg's Croakers";

Visual Basic

' Query for a specific customer
Dim id As String = "ALFKI"
Dim targetCustomer = (From cust In db.Customers _ 
                      Where cust.CustomerID = id).First
targetCustomer.CompanyName = "Dr. Frogg's Croakers"

上記の例で CompanyName が割り当てられるとすぐに、LINQ to SQL変更が認識され、記録できるようになります。 すべてのデータ メンバーの元の値は、 変更追跡サービスによって保持されます。

変更追跡サービスでは、リレーションシップ プロパティのすべての操作も記録されます。 リレーションシップ プロパティは、データベース内のキー値によってリンクされている場合でも、エンティティ間のリンクを確立するために使用します。 キー列に関連付けられているメンバーを直接変更する必要はありません。 LINQ to SQL変更が送信される前に、自動的に同期されます。

C#

Customer cust1 = db.Customers.Single(c => c.CustomerID == custId1);
foreach (Order o in db.Orders.Where(o => o.CustomerID == custId2)) {
   o.Customer = cust1;
}

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                      Where cust.CustomerID = custId1).First

For Each ord In (From o In db.Orders _ 
                 Where o.CustomerID = custId2) 
   o.Customer = targetCustomer
Next

顧客のプロパティに割り当てを行うだけで、ある顧客から別の 顧客 に注文を移動できます。 顧客と注文の間にリレーションシップが存在するため、どちらの側も変更してリレーションシップを変更できます。 次に示すように、cust2 Orders コレクションからそれらを簡単に削除し、cust1 の orders コレクションに追加することもできます。

C#

Customer cust1 = db.Customers.Single(c => c.CustomerID == custId1);
Customer cust2 = db.Customers.Single(c => c.CustomerID == custId2); 
// Pick some order
Order o = cust2.Orders[0]; 
// Remove from one, add to the other
cust2.Orders.Remove(o);
cust1.Orders.Add(o);
// Displays 'true'
Console.WriteLine(o.Customer == cust1);

Visual Basic

Dim targetCustomer1 = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
Dim targetCustomer2 = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer2.Orders(0) 
' Remove from one, add to the other
targetCustomer2.Orders.Remove(o)
targetCustomer1.Orders.Add(o)
' Displays 'True'
MsgBox(o.Customer = targetCustomer1)

もちろん、 リレーションシップに null の値を割り当てると、実際にはリレーションシップが完全に削除されます。 注文の Customer プロパティを null に割り当てると、実際には顧客の一覧から注文が削除されます。

C#

Customer cust = db.Customers.Single(c => c.CustomerID == custId1); 
// Pick some order
Order o = cust.Orders[0];
// Assign null value
o.Customer = null;
// Displays 'false'
Console.WriteLine(cust.Orders.Contains(o));

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer.Orders(0)
' Assign null value
o.Customer = Nothing
' Displays 'False'
Msgbox(targetCustomer.Orders.Contains(o))

リレーションシップの両側の自動更新は、オブジェクト グラフの一貫性を維持するために不可欠です。 通常のオブジェクトとは異なり、データ間のリレーションシップは多くの場合双方向です。 LINQ to SQLでは、プロパティを使用してリレーションシップを表すことができます。 ただし、これらの双方向プロパティを同期して自動的に保持するサービスは提供されません。これは、クラス定義に直接ベイクする必要があるサービス レベルです。 コード生成ツールを使用して生成されたエンティティ クラスには、この機能があります。 次の章では、自分の手書きのクラスにこれを行う方法について説明します。

ただし、リレーションシップを削除しても、オブジェクトがデータベースから削除されたことは意味しないことに注意してください。 基になるデータの有効期間は、テーブルから行が削除されるまでデータベースに保持されます。 オブジェクトを実際に削除する唯一の方法は、 その Table コレクションからオブジェクトを削除することです。

C#

Customer cust = db.Customers.Single(c => c.CustomerID == custId1); 
// Pick some order
Order o = cust.Orders[0];
// Remove it directly from the table (I want it gone!)
db.Orders.Remove(o);
// Displays 'false'.. gone from customer's Orders
Console.WriteLine(cust.Orders.Contains(o));
// Displays 'true'.. order is detached from its customer
Console.WriteLine(o.Customer == null);

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                          Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer.Orders(0)
' Remove it directly from the table (I want it gone!)
db.Orders.Remove(o)
' Displays 'False'.. gone from customer’s Orders
Msgbox(targetCustomer.Orders.Contains(o))
' Displays 'True'.. order is detached from its customer
Msgbox(o.Customer = Nothing)

他のすべての変更と同様に、注文は実際には削除されていません。 残りのオブジェクトから削除されてデタッチされたので、そのように見えます。 Orders テーブルから order オブジェクトが削除されると、変更追跡サービスによって削除対象としてマークされました。 実際にデータベースから削除されるのは、 SubmitChanges() の呼び出しで変更が送信されたときに発生します。 オブジェクト自体は削除されません。 ランタイムはオブジェクト インスタンスの有効期間を管理するため、参照を保持している限り、そのインスタンスは維持されます。 ただし、オブジェクトが Table から削除され、変更が送信された後は、変更追跡サービスによって追跡されなくなります。

エンティティが追跡されない残りの唯一の時間は、 DataContext が認識される前にエンティティが存在する場合です。 これは、コードで新しいオブジェクトを作成するたびに発生します。 データベースからエンティティ クラスを取得することなく、アプリケーションでエンティティ クラスのインスタンスを自由に使用できます。 変更のタッキングと ID 管理は、 DataContext が認識しているオブジェクトにのみ適用されます。 そのため、 DataContext に追加するまで、新しく作成されたインスタンスに対してどちらのサービスも有効になりません。

これは、2 つの方法のいずれかで発生する可能性があります。 関連する Table コレクションで Add() メソッドを手動で呼び出すことができます。

C#

Customer cust =
   new Customer {
      CustomerID = "ABCDE",
      ContactName = "Frond Smooty",
      CompanyTitle = "Eggbert's Eduware",
      Phone = "888-925-6000"
   };
// Add new customer to Customers table
db.Customers.Add(cust);

Visual Basic

Dim targetCustomer = New Customer With { _
         .CustomerID = “ABCDE”, _
         .ContactName = “Frond Smooty”, _
         .CompanyTitle = “Eggbert’s Eduware”, _
         .Phone = “888-925-6000”}
' Add new customer to Customers table
db.Customers.Add(cust)

または、 DataContext が既に認識しているオブジェクトに新しいインスタンスをアタッチすることもできます。

C#

// Add an order to a customer's Orders
cust.Orders.Add(
   new Order { OrderDate = DateTime.Now }
); 

Visual Basic

' Add an order to a customer's Orders
targetCustomer.Orders.Add( _
   New Order With { .OrderDate = DateTime.Now } )

DataContext は、他の新しいインスタンスにアタッチされている場合でも、新しいオブジェクト インスタンスを検出します。

C#

// Add an order and details to a customer's Orders
Cust.Orders.Add(
   new Order {
      OrderDate = DateTime.Now,
      OrderDetails = {
         new OrderDetail {
            Quantity = 1,
            UnitPrice = 1.25M,
            Product = someProduct
         }
      }
   }
); 

Visual Basic

' Add an order and details to a customer's Orders
targetCustomer.Orders.Add( _
   New Order With { _
      .OrderDate = DateTime.Now, _
      .OrderDetails = New OrderDetail With { _
               .Quantity = 1,
               .UnitPrice = 1.25M,
               .Product = someProduct 
      }
   } )

基本的に、 DataContext は、 Add() メソッドを呼び出したかどうかにかかわらず、現在新しいインスタンスとして追跡されていないオブジェクト グラフ内のすべてのエンティティを認識します。

読み取り専用 DataContext の使用

多くのシナリオでは、データベースから取得したエンティティを更新する必要はありません。 Web ページに Customers のテーブルを表示することは、明らかな例の 1 つです。 このような場合はすべて、エンティティに対する変更を追跡しないように DataContext に指示することで、パフォーマンスを向上させることができます。 これは、次のコードのように、DataContextObjectTracking プロパティを false に指定することで実現されます。

C#

      db.ObjectTracking = false;
      
      var q = db.Customers.Where( c => c.City = "London");
      foreach(Customer c in q)
         Display(c);

Visual Basic

db.ObjectTracking = False
      
      Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London"
      For Each c in londonCustomers
         Display(c)
Next

変更の送信

オブジェクトに加えた変更の数に関係なく、これらの変更はメモリ内レプリカにのみ行われました。 データベース内の実際のデータにはまだ何も起こっていません。 この情報をサーバーに送信するには、DataContextSubmitChanges() を呼び出して明示的に要求します。

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// make changes here
db.SubmitChanges();

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' make changes here
db.SubmitChanges()

SubmitChanges()を呼び出すと、DataContext は、すべての変更を同等の SQL コマンドに変換し、対応するテーブルの行を挿入、更新、または削除しようとします。 これらのアクションは、必要に応じて独自のカスタム ロジックによってオーバーライドできますが、送信の順序は、変更プロセッサと呼ばれる DataContext のサービスによって調整されます。

SubmitChanges() を呼び出すときに最初に行われるのは、既知のオブジェクトのセットを調べて、新しいインスタンスがアタッチされているかどうかを判断することです。 これらの新しいインスタンスは、追跡対象オブジェクトのセットに追加されます。 次に、保留中の変更を含むすべてのオブジェクトは、それらの間の依存関係に基づいて一連のオブジェクトに順序付けられます。 他のオブジェクトに依存する変更を持つオブジェクトは、依存関係の後にシーケンスされます。 外部キー制約とデータベース内の一意性制約は、変更の正しい順序付けを決定する上で大きな役割を果たしています。 その後、実際の変更が送信される直前に、既にスコープ内に存在しない限り、一連の個々のコマンドをカプセル化するトランザクションが開始されます。 最後に、オブジェクトに対する変更が 1 つずつ SQL コマンドに変換され、サーバーに送信されます。

この時点で、データベースによって検出されたすべてのエラーによって送信プロセスが中止され、例外が発生します。 データベースに対するすべての変更は、提出が行われなかったかのようにロールバックされます。 DataContext は引き続きすべての変更を完全に記録するため、SubmitChanges() をもう一度呼び出して問題を修正し、再送信することができます。

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// make changes here 
try {
   db.SubmitChanges();
}
catch (Exception e) {
   // make some adjustments
   ...
   // try again
   db.SubmitChanges();
}

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' make changes here 
Try 
   db.SubmitChanges()
Catch e As Exception
   ' make some adjustments
   ...
   ' try again
   db.SubmitChanges()
End Try

送信に関するトランザクションが正常に完了すると、 DataContext は変更追跡情報を忘れるだけでオブジェクトへの変更を受け入れます。

同時変更

SubmitChanges() の呼び出しが失敗する理由はさまざまです。 無効な主キーを持つオブジェクトを作成した可能性があります。既に使用されている値、またはデータベースのチェック制約に違反する値を含むもの。 このようなチェックは、多くの場合、データベースの状態全体に関する絶対的な知識を必要とするため、ビジネス ロジックにベイクするのは困難です。 ただし、失敗の最も可能性の高い理由は、他のユーザーが前にオブジェクトに変更を加えただけであるということです。

確かに、データベース内の各オブジェクトをロックし、完全にシリアル化されたトランザクションを使用している場合、これは不可能です。 ただし、このスタイルのプログラミング (ペシミスティック コンカレンシー) は、コストが高く、真の衝突はほとんど発生しないため、ほとんど使用されません。 同時変更を管理する最も一般的な形式は、 オプティミスティック コンカレンシーの形式を採用することです。 このモデルでは、データベース行に対するロックはまったく行われません。 つまり、最初にオブジェクトを取得してから変更を送信するまでの間に、データベースに対する任意の数の変更が発生した可能性があります。

したがって、前回の更新で優先されるポリシーを使用して、その前に発生した他の何かをワイプしない限り、基になるデータが他のユーザーによって変更されたという事実に対してアラートを受け取る必要があります。

DataContext には、変更の競合を自動的に検出することでオプティミスティック コンカレンシーが組み込まれています。 個々の更新は、データベースの現在の状態が、オブジェクトを最初に取得したときにデータが認識された状態と一致する場合にのみ成功します。 これはオブジェクトごとに発生し、変更したオブジェクトに違反が発生した場合にのみ警告します。

エンティティ クラスを定義するときに 、DataContext が変更の競合を検出する度合いを制御できます。 各 Column 属性には UpdateCheck というプロパティがあり、 AlwaysNeverWhenChanged の 3 つの値のいずれかを割り当てることができます。 Column 属性の既定値を設定しない場合は Always です。つまり、バージョン スタンプのような明確なタイ ブレーカーがない限り、そのメンバーによって表されるデータ値は常に競合がないかチェックされます。 Column 属性には IsVersion プロパティがあり、データ値がデータベースによって管理されるバージョン スタンプを構成するかどうかを指定できます。 バージョンが存在する場合、バージョンは競合が発生したかどうかを判断するために単独で使用されます。

変更の競合が発生すると、他のエラーと同様に例外がスローされます。 送信を取り巻くトランザクションは中止しますが、 DataContext は変わらず、問題を修正してもう一度試すことができます。

C#

while (retries < maxRetries) {
   Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");

   // fetch objects and make changes here

   try {
      db.SubmitChanges();
      break;
   }
   catch (ChangeConflictException e) {
      retries++;
   }
}

Visual Basic

Do While retries < maxRetries
   Dim db As New Northwind("c:\northwind\northwnd.mdf")

   ' fetch objects and make changes here

   Try
      db.SubmitChanges()
      Exit Do
   
   catch cce As ChangeConflictException
      retries += 1
   End Try
Loop

中間層またはサーバーで変更を行う場合、変更の競合を修正するために実行できる最も簡単な方法は、最初からやり直して、コンテキストを再作成し、変更を再適用することです。 その他のオプションについては、次のセクションで説明します。

トランザクション

トランザクションは、データベースまたはその他のリソース マネージャーによって提供されるサービスであり、一連の個々のアクションが自動的に発生することを保証するために使用できます。すべて成功するか、すべて成功しないかを意味します。 そうでない場合は、他の操作が許可される前に、すべて自動的に元に戻されます。 スコープ内にトランザクションが既に存在しない場合、SubmitChanges() を呼び出すと、DataContext によって更新を保護するデータベース トランザクションが自動的に開始されます。

使用されるトランザクションの種類、分離レベル、または実際に含まれるものを自分で開始することで制御できます。 DataContext で使用されるトランザクションの分離は、ReadCommitted と呼ばれます。

C#

Product prod = db.Products.Single(p => p.ProductID == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

using(TransactionScope ts = new TransactionScope()) {
   db.SubmitChanges();
   ts.Complete();
}

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

Using ts As TransactionScope = New TransactionScope())
   db.SubmitChanges()
   ts.Complete()
End Using

上記の例では、新しいトランザクション スコープ オブジェクトを作成することで、完全にシリアル化されたトランザクションを開始します。 トランザクションのスコープ内で実行されるすべてのデータベース コマンドは、トランザクションによって保護されます。

C#

Product prod = db.Products.Single(p => p.ProductId == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

using(TransactionScope ts = new TransactionScope()) {
   db.ExecuteCommand("exec sp_BeforeSubmit");
   db.SubmitChanges();
   ts.Complete();
}

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

Using ts As TransactionScope = New TransactionScope())
   db.ExecuteCommand(“exec sp_BeforeSubmit”)
   db.SubmitChanges()
   ts.Complete()
End Using

同じ例のこの変更されたバージョンでは、DataContextExecuteCommand() メソッドを使用して、変更が送信される直前にデータベース内のストアド プロシージャを実行します。 ストアド プロシージャがデータベースに対して何を行うかに関係なく、そのアクションが同じトランザクションの一部であることを確認できます。

トランザクションが正常に完了すると、 DataContext は蓄積されたすべての追跡情報をスローし、エンティティの新しい状態を変更せずに処理します。 ただし、トランザクションが失敗した場合、オブジェクトへの変更はロールバックされません。 これにより、変更の送信中に問題に対処する際の最大限の柔軟性が得られます。

新しい TransactionScope の代わりにローカル SQL トランザクションを使用することもできます。 LINQ to SQLには、既存の ADO.NET アプリケーションにLINQ to SQL機能を統合するのに役立つこの機能が用意されています。 ただし、このルートに移動する場合は、より多くの責任を負う必要があります。

C#

Product prod = q.Single(p => p.ProductId == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

db.Transaction = db.Connection.BeginTransaction();
try {
   db.SubmitChanges();
   db.Transaction.Commit();
}
catch {
   db.Transaction.Rollback();
   throw;
}
finally {
   db.Transaction = null;
}

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

db.Transaction = db.Connection.BeginTransaction()
Try
   db.SubmitChanges()
   db.Transaction.Commit()

catch e As Exception
   db.Transaction.Rollback()
   Throw e
Finally
   db.Transaction = Nothing
End Try

ご覧のように、手動で制御されたデータベース トランザクションの使用にはもう少し関係があります。 自分で開始する必要があるだけでなく、 DataContextTransaction プロパティに割り当てて使用するように明示的に指示する必要があります。 次に、try-catch ブロックを使用して送信ロジックを適用し、コミットするようにトランザクションに明示的に指示し、変更を受け入れるように DataContext に明示的に指示するか、または任意の時点でエラーが発生した場合にトランザクションを中止することを忘れないでください。 また、完了したら、 Transaction プロパティを null に設定することを忘れないでください。

ストアド プロシージャ

SubmitChanges() が呼び出されると、LINQ to SQLは SQL コマンドを生成して実行し、データベース内の行を挿入、更新、および削除します。 これらのアクションはアプリケーション開発者によってオーバーライドでき、その代わりにカスタム コードを使用して目的のアクションを実行できます。 このようにして、データベース ストアド プロシージャなどの代替機能を 変更プロセッサによって自動的に呼び出すことができます。

Northwind サンプル データベースの Products テーブルの在庫単位を更新するためのストアド プロシージャを検討してください。 プロシージャの SQL 宣言は次のとおりです。

SQL

create proc UpdateProductStock
   @id               int,
   @originalUnits    int,
   @decrement         int
as

厳密に型指定された DataContext にメソッドを定義することで、通常の自動生成された更新コマンドの代わりにストアド プロシージャを使用できます。 DataContext クラスがLINQ to SQLコード生成ツールによって自動生成されている場合でも、これらのメソッドは独自の部分クラスで指定できます。

C#

public partial class Northwind : DataContext
{
   ...

   public void UpdateProduct(Product original, Product current) {
      // Execute the stored procedure for UnitsInStock update
      if (original.UnitsInStock != current.UnitsInStock) {
         int rowCount = this.ExecuteCommand(
            "exec UpdateProductStock " +
            "@id={0}, @originalUnits={1}, @decrement={2}",
            original.ProductID,
            original.UnitsInStock,
            (original.UnitsInStock - current.UnitsInStock)
         );
         if (rowCount < 1)
            throw new Exception("Error updating");
      }
      ...
   }
}

Visual Basic

Partial Public Class Northwind
         Inherits DataContext

   ...

   Public Sub UpdateProduct(original As Product, current As Product)
      ‘ Execute the stored procedure for UnitsInStock update
      If original.UnitsInStock <> current.UnitsInStock Then
         Dim rowCount As Integer = ExecuteCommand( _
            "exec UpdateProductStock " & _
            "@id={0}, @originalUnits={1}, @decrement={2}", _
            original.ProductID, _
            original.UnitsInStock, _
            (original.UnitsInStock - current.UnitsInStock) )
         If rowCount < 1 Then
            Throw New Exception(“Error updating”)
         End If
      End If
      ...
   End Sub
End Class

メソッドとジェネリック パラメーターのシグネチャは、生成された update ステートメントの代わりにこのメソッドを使用するように DataContext に指示します。 元のパラメーターと現在のパラメーターは、指定した型のオブジェクトの元のコピーと現在のコピーを渡す場合に、LINQ to SQLによって使用されます。 オプティミスティック コンカレンシーの競合検出には、2 つのパラメーターを使用できます。

メモ 既定の更新ロジックをオーバーライドする場合、競合の検出はユーザーの責任です。

ストアド プロシージャ UpdateProductStock は、DataContextExecuteCommand() メソッドを使用して呼び出されます。 影響を受ける行の数を返し、次のシグネチャを持っています。

C#

public int ExecuteCommand(string command, params object[] parameters);

Visual Basic

Public Function ExecuteCommand(command As String, _
         ParamArray parameters() As Object) As Integer

オブジェクト配列は、 コマンドの実行に必要なパラメーターを渡す場合に使用 されます

update メソッドと同様に、insert メソッドと delete メソッドを指定できます。 Insert メソッドと delete メソッドは、更新するエンティティ型のパラメーターを 1 つだけ受け取ります。 たとえば、Product インスタンスを挿入および削除するメソッドは、次のように指定できます。

C#

public void InsertProduct(Product prod) { ... }
public void DeleteProudct(Product prod) { ... }

Visual Basic

Public Sub InsertProduct(prod As Product)  ... 
Public Sub DeleteProudct(prod As Product)  ... 

エンティティ クラス In-Depth

属性の使用

エンティティ クラスは、アプリケーションの一部として定義できる通常のオブジェクト クラスと同じですが、特定のデータベース テーブルに関連付ける特別な情報で注釈が付けられている点が異なります。 これらの注釈は、クラス宣言でカスタム属性として作成されます。 属性は、 クラスを LINQ to SQL と組み合わせて使用する場合にのみ意味があります。 これらは、.NET Frameworkの XML シリアル化属性に似ています。 これらの "データ" 属性は、LINQ to SQLに、オブジェクトのクエリをデータベースに対する SQL クエリに変換し、オブジェクトに対する変更を SQL 挿入、更新、および削除コマンドに変換するのに十分な情報を提供します。

属性の代わりに XML マッピング ファイルを使用してマッピング情報を表すこともできます。 このシナリオの詳細については、「外部マッピング」セクションを参照してください。

Database 属性

Database 属性は、接続によって指定されていない場合に、データベースの既定の名前を指定するために使用されます。 データベース 属性は、厳密に型指定された DataContext 宣言に適用できます。 この属性は省略可能です。

Database 属性

プロパティ 種類 説明
名前 String データベースの名前を指定します。 この情報は、接続自体でデータベース名が指定されていない場合にのみ使用されます。 この Database 属性がコンテキスト宣言に存在せず、接続で指定されていない場合、データベースはコンテキスト クラスと同じ名前を持つと見なされます。

C#

[Database(Name="Database#5")]
public class Database5 : DataContext {
   ...
}

Visual Basic

<Database(Name:="Database#5")> _
Public Class Database5 
               Inherits DataContext
   ...
End Class

Table 属性

Table 属性は、データベース テーブルに関連付けられたエンティティ クラスとしてクラスを指定するために使用されます。 Table 属性を持つクラスは、LINQ to SQLによって特別に扱われます。

Table 属性

プロパティ 種類 説明
名前 String テーブルの名前を指定します。 この情報が指定されていない場合は、テーブルの名前がエンティティ クラスと同じであると見なされます。

C#

[Table(Name="Customers")]
public class Customer {
   ...
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer 
   ...
End Class

Column の属性

Column 属性は、データベース テーブル内の列を表すエンティティ クラスのメンバーを指定するために使用されます。 これは、任意のフィールドまたはプロパティ、パブリック、プライベート、または内部に適用できます。 データベースに変更を保存LINQ to SQL場合は、列として識別されたメンバーのみが保持されます。

列属性

プロパティ 種類 説明
名前 String テーブルまたはビュー内の列の名前。 指定しない場合、列はクラス メンバーと同じ名前であると見なされます。
ストレージ String 基になるストレージの名前。 指定した場合、データ メンバーのパブリック プロパティ アクセサーをバイパスし、生の値自体を操作する方法をLINQ to SQLに指示します。 指定しない場合LINQ to SQLパブリック アクセサーを使用して値を取得および設定します。
Dbtype String データベース型と修飾子を使用して指定されたデータベース列の型。 これは、T-SQL テーブル宣言コマンドで列を定義するために使用される正確なテキストになります。 指定しない場合、データベース列の型はメンバー型から推論されます。 特定のデータベースの種類は、 CreateDatabase() メソッドを使用してデータベースのインスタンスを作成する必要がある場合にのみ必要です。
IsPrimaryKey Bool true に設定すると、クラス メンバーはテーブルの主キーの一部である列を表します。 クラスの複数のメンバーが ID として指定されている場合、主キーは関連付けられた列の複合と言われます。
IsDbGenerated Boolean メンバーの列値がデータベースによって自動生成されることを識別します。 IsDbGenerated=true を指定した主キーには、IDENTITY 修飾子を持つ DBType も含まれている必要があります。 IsDbGenerated メンバーは、データ行が挿入された直後に同期され、 SubmitChanges() の完了後に使用できます。
IsVersion Boolean メンバーの列の種類をデータベースのタイムスタンプまたはバージョン番号として識別します。 バージョン番号がインクリメントされ、関連付けられている行が更新されるたびに、タイムスタンプ列がデータベースによって更新されます。 IsVersion=true のメンバーは、データ行が更新された直後に同期されます。 新しい値は、 SubmitChanges() の完了後に表示されます。
UpdateCheck UpdateCheck オプティミスティック コンカレンシー競合検出LINQ to SQL実装する方法を決定します。 メンバーが IsVersion=true として指定されていない場合は、元のメンバー値と現在のデータベース状態を比較することで検出が行われます。 競合の検出時に使用LINQ to SQLメンバーを制御できます。各メンバーに UpdateCheck 列挙値を指定します。
  • Always: 競合の検出には常にこの列を使用します
  • Never: 競合の検出にこの列を使用しないでください
  • WhenChanged: アプリケーションによってメンバーが変更された場合にのみ、この列を使用します
IsDiscriminator Boolean クラス メンバーが継承階層の識別子の値を保持するかどうかを決定します。
String LINQ to SQLの操作には影響しませんが、 の実行中に使用されます。計算列式を表す生の SQL 式として CreateDatabase() を作成します。
CanBeNull Boolean 値に null 値を含めることができることを示します。 通常、これはエンティティ メンバーの CLR 型から推論されます。 この属性を使用して、文字列値がデータベース内の null 許容でない列として表されることを示します。
AutoSync AutoSync 挿入コマンドまたは更新コマンドでデータベースによって生成された値から列を自動的に同期するかどうかを指定します。 このタグの有効な値は 、OnInsertAlwaysNever です

一般的なエンティティ クラスでは、パブリック プロパティに Column 属性を使用し、実際の値をプライベート フィールドに格納します。

C#

private string _city;

[Column(Storage="_city", DBType="NVarChar(15)")]
public string City {
   get { ... }
   set { ... }
}

Visual Basic

Private _city As String

<Column(Storage:="_city", DBType:="NVarChar(15)")> _
public Property City As String
   Get
   set
End Property

DBType は、CreateDatabase() メソッドが最も正確な型を持つテーブルを構築できるようにのみ指定されます。 それ以外の場合、基になる列が 15 文字に制限されているという知識は使用されません。

データベース型の主キーを表すメンバーは、多くの場合、自動生成された値に関連付けられます。

C#

private string _orderId;

[Column(Storage="_orderId", IsPrimaryKey=true, IsDbGenerated = true,
   DBType="int NOT NULL IDENTITY")]
public string OrderId {
   get { ... }
   set { ... }
}

Visual Basic

Private _orderId As String

<Column(Storage:="_orderId", IsPrimaryKey:=true, _
           IsDbGenerated:= true, DBType:="int NOT NULL IDENTITY")> _
public Property OrderId As String
   Get
   Set
End Property

DBType を指定する場合は、必ず IDENTITY 修飾子を含めます。 LINQ to SQLは、カスタムで指定された DBType を拡張しません。 ただし、DBType が指定されていない場合、LINQ to SQL CreateDatabase() メソッドを使用してデータベースを作成するときに IDENTITY 修飾子が必要であると推測されます。

同様に、 IsVersion プロパティが true の場合、 DBType は正しい修飾子を指定してバージョン番号またはタイムスタンプ列を指定する必要があります。 DBType が指定されていない場合、LINQ to SQLは正しい修飾子を推論します。

自動生成された列、バージョン スタンプ、または非表示にする列に関連付けられているメンバーへのアクセスを制御するには、メンバーのアクセス レベルを指定するか、アクセサー自体を制限します。

C#

private string _customerId;

[Column(Storage="_customerId", DBType="NCHAR(5) ")]
public string CustomerID {
   get { ... }
}

Visual Basic

Private _customerId As String

<Column(Storage:="_customerId", DBType:="NCHAR(5)")> _
Public Property CustomerID As String
   Get
End Property

Order の CustomerID プロパティは、set アクセサーを定義しないことで読み取り専用にすることができます。 LINQ to SQLは引き続き、ストレージ メンバーを介して基になる値を取得および設定できます。

プライベート メンバーに Column 属性を配置することで、アプリケーションの残りの部分からメンバーに完全にアクセスできないようにすることもできます。 これにより、エンティティ クラスは、一般的に公開することなく、クラスのビジネス ロジックに関連する情報を含めることができます。 プライベート メンバーは翻訳されたデータの一部ですが、プライベートであるため、言語統合クエリで参照することはできません。

既定では、オプティミスティック コンカレンシーの競合検出を実行するために、すべてのメンバーが使用されます。 特定のメンバーを使用するかどうかを制御するには、 その UpdateCheck 値を指定します。

C#

[Column(Storage="_city", UpdateCheck=UpdateCheck.WhenChanged)]
public string City {
   get { ... }
   set { ... }
}

Visual Basic

<Column(Storage:="_city", UpdateCheck:=UpdateCheck.WhenChanged)> _
Public Property City As String
   Get
   Set
End Property

次の表は、データベース型と対応する CLR 型の間の許容されるマッピングを示しています。 特定のデータベース列を表すために使用する CLR 型を決定する場合は、このテーブルをガイドとして使用します。

データベース型と対応する CLR 型の許容されるマッピング

データベースの種類 .NET CLR 型 説明
bit、tinyint、smallint、int、bigint Bye、Int16、Uint16、Int32、Uint32、Int64、Uint64 損失の可能性のある変換。 値はラウンドトリップしない可能性があります。
bit Boolean  
decimal、numeric、smallmoney、money Decimal スケールの違いにより、損失の変換が発生する可能性があります。 ラウンドトリップしない場合があります。
real、float Single、Double 精度の違い。
char、varchar、text、nchar、nvarchar、ntext String 可能なロケールの違い。
datetime、smalldatetime DateTime 精度が異なると、損失の変換やラウンドトリップの問題が発生する可能性があります。
UNIQUEIDENTIFIER Guid 異なる照合順序規則。 並べ替えが期待どおりに機能しない場合があります。
timestamp Byte[] (Visual Basic の Byte())、Binary バイト配列はスカラー型として扱われます。 ユーザーは、コンストラクターが呼び出されたときに適切なストレージを割り当てる必要があります。 変更不可と見なされ、変更は追跡されません。
binary、varbinary Byte[] (Visual Basic の Byte())、Binary  

Association 属性

Association 属性は、外部キーと主キーのリレーションシップのようなデータベースの関連付けを表すプロパティを指定するために使用されます。

Association 属性

プロパティ 種類 説明
名前 String アソシエーションの名前。 これは多くの場合、データベースの外部キー制約名と同じです。 関連する制約を生成するために、 CreateDatabase() を使用してデータベースのインスタンスを作成するときに使用されます。 また、同じターゲット エンティティ クラスを参照する 1 つのエンティティ クラス内の複数のリレーションシップを区別するためにも使用されます。 この場合、リレーションシップの両側のリレーションシップ プロパティ (両方が定義されている場合) は、同じ名前を持つ必要があります。
ストレージ String 基になるストレージ メンバーの名前。 指定した場合、データ メンバーのパブリック プロパティ アクセサーをバイパスし、生の値自体と対話する方法をLINQ to SQLに指示します。 指定しない場合LINQ to SQLは、パブリック アクセサーを使用して値を取得および設定します。 すべての関連付けメンバーは、個別のストレージ メンバーが識別されたプロパティにすることをお勧めします。
Thiskey String 関連付けのこの側のキー値を表す、このエンティティ クラスの 1 つ以上のメンバーの名前のコンマ区切りのリスト。 指定しない場合、メンバーは主キーを構成するメンバーであると見なされます。
Otherkey String 関連付けの反対側のキー値を表すターゲット エンティティ クラスの 1 つ以上のメンバーの名前のコンマ区切りのリスト。 指定しない場合、メンバーは他のエンティティ クラスの主キーを構成するメンバーであると見なされます。
IsUnique Boolean True の 場合、外部キーに一意性制約があり、真の 1:1 リレーションシップを示します。 このプロパティは、データベース内で 1 対 1 のリレーションシップを管理することはほとんど不可能であるため、ほとんど使用できません。 ほとんどのエンティティ モデルは、アプリケーション開発者によって 1:1 として扱われる場合でも、1:n リレーションシップを使用して定義されます。
IsForeignKey Boolean True を指定 すると、関連付けのターゲットの "other" 型がソース型の親になります。 外部キーと主キーのリレーションシップでは、外部キーを保持する側が子で、主キーを保持している側が親になります。
DeleteRule String この関連付けに削除動作を追加するために使用されます。 たとえば、"CASCADE" は FK リレーションシップに "ON DELETE CASCADE" を追加します。 null に設定すると、削除動作は追加されません。

関連付けプロパティは、別のエンティティ クラス インスタンスへの単一の参照を表すか、参照のコレクションを表します。 単一参照は、EntityRef T> (Visual Basic の EntityRef<(OfT) 値型を使用してエンティティ クラスでエンコードして、実際の参照を格納する必要があります。 EntityRef 型は、LINQ to SQL参照の遅延読み込みを有効にする方法です。

C#

class Order
{
   ...
   private EntityRef<Customer> _Customer;

   [Association(Name="FK_Orders_Customers", Storage="_Customer",
      ThisKey="CustomerID")]
   public Customer Customer {
      get { return this._Customer.Entity; }
      set { this._Customer.Entity = value;
            // Additional code to manage changes }
   }
}

Visual Basic

Class Order

   ...
   Private _customer As EntityRef(Of Customer)

   <Association(Name:="FK_Orders_Customers", _
            Storage:="_Customer", ThisKey:="CustomerID")> _
   public Property Customer() As Customer
      Get  
         Return _customer.Entity
      End Get   
   Set (value As Customer)
      _customer.Entity = value
      ‘ Additional code to manage changes
   End Set
End Class

パブリック プロパティは、EntityRef<Customer> ではなく Customer として入力されます。 クエリ内のこの型への参照は SQL に変換されないため、 EntityRef 型をパブリック API の一部として公開しないことが重要です。

同様に、コレクションを表す関連付けプロパティでは、リレーションシップを格納するために EntitySet<T> (Visual Basic の EntitySet(OfT) コレクション型を使用する必要があります。

C#

class Customer
{
   ...
   private EntitySet<Order> _Orders;

   [Association(Name="FK_Orders_Customers", Storage="_Orders",
      OtherKey="CustomerID")]
   public EntitySet<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
} 

Visual Basic

Class Customer

   ...
   Private _Orders As EntitySet(Of Order)

   <Association(Name:="FK_Orders_Customers", _
         Storage:="_Orders", OtherKey:="CustomerID")> _
   public Property Orders() As EntitySet(Of Order)
      Get
           Return _Orders
      End Get
   Set (value As EntitySet(Of Order))
      _Orders.Assign(value)
   End Property
End Class

ただし、 EntitySet<T> (Visual Basic では EntitySet(OfT) はコレクションであるため、戻り値の型として EntitySet を 使用すると有効です。 また、代わりに Visual Basic の ICollection<T> (OfT) インターフェイスを使用して、コレクションの true 型を偽装することもできます。

C#

class Customer
{
   ...

   private EntitySet<Order> _Orders;

   [Association(Name="FK_Orders_Customers", Storage="_Orders",
      OtherKey="CustomerID")]
   public ICollection<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
}

Visual Basic

Class Customer

   ...
   Private _orders As EntitySet(Of Order)

   <Association(Name:="FK_Orders_Customers", _
         Storage:="_Orders", OtherKey:="CustomerID")> _
   public Property Orders() As ICollection (Of Order)
      Get
           Return _orders
      End Get
Set (value As ICollection (Of Order))
         _orders.Assign(value)
      End Property
End Class

プロパティのパブリック セッターを公開する場合は、EntitySetAssign() メソッドを使用することを確認します。 これにより、エンティティ クラスは 変更追跡サービスに既に関連付けられている可能性があるため、同じコレクション インスタンスを使用し続けることができます。

ResultType 属性

この属性は、 IMultipleResults インターフェイスを返すように宣言されている関数から返すことができる列挙可能なシーケンスの要素型を指定します。 この属性は複数回指定できます。

ResultType 属性

プロパティ 種類 説明
種類 種類 返される結果の型。

StoredProcedure 属性

StoredProcedure 属性は、DataContext 型またはスキーマ型で定義されたメソッドの呼び出しがデータベース ストアド プロシージャの呼び出しとして変換されることを宣言するために使用されます。

StoredProcedure 属性

プロパティ 種類 説明
名前 String データベース内のストアド プロシージャの名前。 指定しない場合、ストアド プロシージャは メソッドと同じ名前を持つものと見なされます

関数属性

Function 属性は、DataContext または Schema で定義されたメソッドの呼び出しが、データベース ユーザー定義のスカラー関数またはテーブル値関数の呼び出しとして変換されることを宣言するために使用されます。

関数属性

プロパティ 種類 説明
名前 String データベース内の関数の名前。 指定しない場合、関数は メソッドと同じ名前を持つものと見なされます

パラメーター属性

Parameter 属性は、データベース ストアド プロシージャまたはユーザー定義関数のメソッドとパラメーターの間のマッピングを宣言するために使用されます。

パラメーター属性

プロパティ 種類 説明
名前 String データベース内のパラメーターの名前。 指定しない場合、パラメーターはメソッド パラメーター名から推論されます。
Dbtype String データベースの型と修飾子を使用して指定されたパラメーターの型。

InheritanceMapping 属性

InheritanceMapping 属性は、特定の識別子コードと継承サブタイプの対応関係を記述するために使用されます。 継承階層に使用されるすべての InheritanceMapping 属性は、階層のルート型で宣言する必要があります。

InheritanceMapping 属性

Propety 種類 説明
コード Object 識別子コード値。
種類 種類 継承サブタイプ。 これは、ルート型を含む継承階層内の任意の非抽象型である場合があります。
IsDefault Boolean 指定した継承サブタイプが、InheritanceMapping 属性によって定義されていない識別子コードLINQ to SQL検出されたときに構築される既定の型であるかどうかを判断します。 継承マップ属性の 1 つを IsDefault で true として宣言する必要があります。

グラフの整合性

グラフは、参照によって相互に参照されるオブジェクトのデータ構造の一般的な用語です。 階層 (またはツリー) は、グラフの縮退形式です。 ドメイン固有のオブジェクト モデルは、多くの場合、オブジェクトのグラフとして最もよく記述される参照のネットワークを記述します。 オブジェクト グラフの正常性は、アプリケーションの安定性にとって非常に重要です。 そのため、グラフ内の参照が、データベースで定義されているビジネス ルールや制約との整合性を維持することが重要です。

LINQ to SQLでは、リレーションシップ参照の整合性は自動的に管理されません。 リレーションシップが双方向の場合、リレーションシップの一方の側への変更によって、もう一方の側が自動的に更新されます。 通常のオブジェクトがこのように動作することは珍しいため、それ以外の場合はオブジェクトをこのように設計した可能性は低い点に注意してください。

LINQ to SQLでは、この作業を簡単にするためのいくつかのメカニズムと、参照を正しく管理していることを確認するためのパターンが用意されています。 コード生成ツールによって生成されたエンティティ クラスは、正しいパターンを自動的に実装します。

C#

public class Customer() {
   this._Orders =
      new EntitySet<Order>(
         new Action<Order>(this.attach_Orders),
         new Action<Order>(this.detach_Orders));
);}

Visual Basic

Public Class Customer()
         _Orders = New EntitySet(Of Order)( _
              New Action(Of Order)(attach_Orders), _
                 New Action(Of Order)(detach_Orders))
      End Class
);}

EntitySet<T> (Visual Basic の EntitySet(OfT) 型には、コールバックとして使用する 2 つのデリゲートを指定できるコンストラクターがあります。1 つ目はアイテムがコレクションに追加されたとき、2 つ目は削除されたときです。 この例からわかるように、これらのデリゲートに指定するコードは、逆引きリレーションシップ プロパティを更新するために 記述できます。 これは、注文が 顧客Orders コレクションに追加されたときに、Order インスタンスの Customer プロパティが自動的に変更される方法 です

もう一方の側でのリレーションシップの実装は簡単ではありません。 EntityRef<T> (Visual Basic の EntityRef(OfT) は、実際のオブジェクト参照からのオーバーヘッドをできるだけ少なく抑えるために定義された値型です。 代理人のペアの余地はありません。 代わりに、シングルトン参照のグラフの整合性を管理するコードを、プロパティ アクセサー自体に埋め込む必要があります。

C#

[Association(Name="FK_Orders_Customers", Storage="_Customer",
   ThisKey="CustomerID")]
public Customer Customer {
   get {
      return this._Customer.Entity;
   }
   set {
      Customer v = this._Customer.Entity;
      if (v != value) {
         if (v != null) {
            this._Customer.Entity = null;
            v.Orders.Remove(this);
         }
         this._Customer.Entity = value;
         if (value != null) {
            value.Orders.Add(this);
         }
      }
   }
}

Visual Basic

<Association(Name:="FK_Orders_Customers", _
         Storage:="_Customer", ThisKey:="CustomerID")> _
Public Property Customer As Customer 
   Get
      Return _Customer.Entity
   End Get
   Set (value As Customer)
      Dim cust As Customer v = _customer.Entity
      if cust IsNot value Then
         If cust IsNot Nothing Then
            _Customer.Entity = Nothing
            cust.Orders.Remove(Me)
         End If

         _customer.Entity = value
         if value IsNot Nothing Then
            value.Orders.Add(Me)
         End If
      End If
   End Set
End Property

セッターを見てください。 Customer プロパティが変更されると、注文インスタンスは最初に現在の顧客の Orders コレクションから削除され、後で新しい顧客のコレクションに追加されます。 Remove() の呼び出しが行われる前に、実際のエンティティ参照が null に設定されていることに注意してください。 これは、 Remove() メソッドが呼び出されたときに再帰を回避するために行われます。 EntitySet ではコールバック デリゲートを使用して、このオブジェクトの Customer プロパティを null に割り当てることに注意してくださいAdd()の呼び出しの直前にも同じことが起こります。 実際のエンティティ参照が新しい値に更新されます。 これは再び潜在的な再帰を抑制し、もちろん最初にセッターのタスクを達成します。

一対一リレーションシップの定義は、シングルトン参照の側からの一対多リレーションシップの定義と非常によく似ています。 Add() Remove() が呼び出される代わりに、新しいオブジェクトが割り当てられるか、リレーションシップを切断するために null が割り当てられます。

ここでも、リレーションシップ プロパティがオブジェクト グラフの一貫性を維持することが重要です。 メモリ内オブジェクト グラフがデータベース データと矛盾している場合は、 SubmitChanges メソッドが呼び出されたときに実行時例外が生成されます。 一貫性のある作業を維持するために、コード生成ツールを使用することを検討してください。

変更通知

オブジェクトが変更追跡プロセスに参加している可能性があります。 そうする必要はありませんが、潜在的なオブジェクトの変更を追跡するために必要なオーバーヘッドを大幅に削減できます。 アプリケーションは、最終的に変更されるよりも多くのオブジェクトをクエリから取得する可能性があります。 オブジェクトからのプロアクティブなヘルプがないと、変更追跡サービスは、実際に変更を追跡する方法に制限されます。

ランタイムには真のインターセプト サービスがないため、正式な追跡は実際には行われません。 代わりに、オブジェクトの複製コピーは、最初に取得されるときに格納されます。 後で SubmitChanges() を呼び出すと、これらのコピーは指定されたものと比較するために使用されます。 値が異なる場合、オブジェクトは変更されています。 つまり、変更していなくても、すべてのオブジェクトにメモリ内に 2 つのコピーが必要になります。

より良い解決策は、オブジェクト自体が実際に変更されたときに変更追跡サービスに読み上げさせる方法です。 これは、コールバック イベントを公開するインターフェイスを オブジェクトに実装することで実現できます。 変更追跡サービスでは、各オブジェクトを接続し、変更時に通知を受け取ることができます。

C#

[Table(Name="Customers")]
public partial class Customer: INotifyPropertyChanging {

   public event PropertyChangingEventHandler PropertyChanging;

   private void OnPropertyChanging() {
      if (this.PropertyChanging != null) {
         this.PropertyChanging(this, emptyEventArgs);
      }
   }

   private string _CustomerID;

   [Column(Storage="_CustomerID", IsPrimaryKey=true)]
   public string CustomerID {
      get {
         return this._CustomerID;
      }
      set {
         if ((this._CustomerID != value)) {
            this.OnPropertyChanging("CustomerID");
            this._CustomerID = value;
         }
      }
   }
}

Visual Basic

<Table(Name:="Customers")> _
Partial Public Class Customer 
         Inherits INotifyPropertyChanging
Public Event PropertyChanging As PropertyChangingEventHandler _
        Implements INotifyPropertyChanging.PropertyChanging

   Private Sub OnPropertyChanging()
         RaiseEvent PropertyChanging(Me, emptyEventArgs)
   End Sub

   private _customerID As String 

   <Column(Storage:="_CustomerID", IsPrimaryKey:=True)>
   public Property CustomerID() As String
      Get
         Return_customerID
      End Get
      Set (value As Customer)
         If _customerID IsNot value Then
            OnPropertyChanging(“CustomerID”)
            _CustomerID = value
         End IF
      End Set
   End Function
End Class

変更追跡の改善を支援するには、エンティティ クラスで INotifyPropertyChanging インターフェイスを実装する 必要があります。 PropertyChanging というイベントを定義するだけで済みます。変更追跡サービスは、オブジェクトが所有に入ったときにイベントに登録されます。 必要なのは、プロパティの値を変更する直前にこのイベントを発生させる必要があります。

リレーションシップ プロパティセッターにも同じイベント発生ロジックを配置することを忘れないでください。 EntitySets の場合は、指定したデリゲートでイベントを発生させます。

C#

public Customer() {
   this._Orders =
      new EntitySet<Order>(
         delegate(Order entity) {
            this.OnPropertyChanging("Orders");
            entity.Customer = this;
         },
         delegate(Order entity) {
            this.onPropertyChanging("Orders");
            entity.Customer = null;
         }
      );
}

Visual Basic

Dim _orders As EntitySet(Of Order)
Public Sub New()
   _orders = New EntitySet(Of Order)( _
      AddressOf OrderAdding, AddressOf OrderRemoving)
End Sub

Sub OrderAdding(ByVal o As Order)
   OnPropertyChanging()
   o.Customer = Me
End Sub

Sub OrderRemoving(ByVal o As Order)
   OnPropertyChanging()
   o.Customer = Nothing
End Sub

継承

LINQ to SQLでは、単一テーブル マッピングがサポートされ、継承階層全体が 1 つのデータベース テーブルに格納されます。 テーブルには、階層全体で使用可能なすべてのデータ列のフラット化された和集合が含まれており、各行の列に null が含まれており、行によって表されるインスタンスの型には適用できません。 シングル テーブル マッピング形式は、継承を表す最も単純な形式であり、多くのクエリのカテゴリで良好なパフォーマンス特性を示します。

マッピング

LINQ to SQLを使用してこのマッピングを実装するには、継承階層のルート クラスで次の属性と属性プロパティを指定する必要があります。

  • [Table] (<Visual Basic の Table>) 属性。
  • 階層構造内の各クラスの [InheritanceMapping] (<Visual Basic では InheritanceMapping> ) 属性。 非抽象クラスの場合、この属性は Code プロパティ (このデータ行が属するクラスまたはサブクラスを示す継承識別子列のデータベース テーブルに表示される値) と Type プロパティ (キー値を示すクラスまたはサブクラスを指定する) を定義する必要があります。
  • 1 つの [InheritanceMapping] (<Visual Basic の場合は InheritanceMapping>) 属性の IsDefault プロパティ。 このプロパティは、データベース テーブルの識別子値が継承マッピングのどの Code 値とも一致しない場合に備えて、"フォールバック" マッピングを指定する役割を果たします。
  • 継承マッピングの Code 値を保持する列であることを示す [Column] (<Visual Basic の Column>) 属性の IsDiscriminator プロパティ。

サブクラスには特別な属性やプロパティは必要ありません。 特に、サブクラスには [Table] (<Visual Basic の Table> ) 属性は含まれていないことに注意してください。

次の例では、 Car サブクラスと Truck サブクラスに含まれるデータが単一データベース テーブル Vehicle にマップされています。 (この例を簡略化するために、サンプル コードでは列マッピングのプロパティではなくフィールドを使用しています)。

C#

[Table]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle),
   IsDefault = true)]
public class Vehicle
{
   [Column(IsDiscriminator = true)]
   public string Key;
   [Column(IsPrimaryKey = true)]
   public string VIN;
   [Column]
   public string MfgPlant;
}
public class Car : Vehicle
{
   [Column]
   public int TrimCode;
   [Column]
   public string ModelName;
}

public class Truck : Vehicle
{
   [Column]
   public int Tonnage;
   [Column]
   public int Axles;
}

Visual Basic

<Table> _
<InheritanceMapping(Code:="C", Type:=Typeof(Car))> _
<InheritanceMapping(Code:="T", Type:=Typeof(Truck))> _
<InheritanceMapping(Code:="V", Type:=Typeof(Vehicle), _
              IsDefault:=true)> _
Public Class Vehicle

   <Column(IsDiscriminator:=True)> _
   Public Key As String
   <Column(IsPrimaryKey:=True)> _
   Public VIN As String
   <Column> _
   Public MfgPlant As String
End Class
Public Class Car
       Inherits Vehicle
   <Column> _
   Public TrimCode As Integer
   <Column> _
   Public ModelName As String
End Class

Public class Truck
       Inherits Vehicle 
   <Column> _
   public Tonnage As Integer
   <Column> _
   public Axles As Integer
End Class

クラス図は次のように表示されます。

図 1. 車両クラス図

サーバー エクスプローラーで結果のデータベース ダイアグラムを表示すると、次に示すように、列がすべて 1 つのテーブルにマップされていることがわかります。

図 2. 1 つのテーブルにマップされた列

サブタイプ内のフィールドを表す列の型は null 許容であるか、既定値を指定する必要があることに注意してください。 これは、挿入コマンドを正常に実行するために必要です。

クエリ実行

次のコードは、クエリで派生型を使用する方法のフレーバーを提供します。

C#

var q = db.Vehicle.Where(p => p is Truck);
//or
var q = db.Vehicle.OfType<Truck>();
//or
var q = db.Vehicle.Select(p => p as Truck).Where(p => p != null);
foreach (Truck p in q)
   Console.WriteLine(p.Axles);

Visual Basic

Dim trucks = From veh In db.Vehicle _ 
             Where TypeOf(veh) Is Truck

For Each truck In trucks
   Console.WriteLine(p.Axles) 
Next

上級

既に提供されている単純なサンプルをはるかに超えて階層を拡張できます。

例 1

より深い階層とより複雑なクエリを次に示します。

C#

[Table]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle), IsDefault = true)]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "S", Type = typeof(Semi))]
[InheritanceMapping(Code = "D", Type = typeof(DumpTruck))]
public class Truck: Vehicle { ... }

public class Semi: Truck { ... }

public class DumpTruck: Truck { ... }

...
// Get all trucks along with a flag indicating industrial application.

db.Vehicles.OfType<Truck>.Select(t => 
   new {Truck=t, IsIndustrial=t is Semi || t is DumpTruck }
);

Visual Basic

<Table> _
<InheritanceMapping(Code:="V", Type:=Typeof(Vehicle), IsDefault:=True)> _
<InheritanceMapping(Code:="C", Type:=Typeof(Car))> _
<InheritanceMapping(Code:="T", Type:=Typeof(Truck))> _
<InheritanceMapping(Code:="S", Type:=Typeof(Semi))> _
<InheritanceMapping(Code:="D", Type:=Typeof(DumpTruck))> _
Public Class Truck
       InheritsVehicle
Public Class Semi
       Inherits Truck

Public Class DumpTruck
       InheritsTruck 
...
' Get all trucks along with a flag indicating industrial application.
Dim trucks = From veh In db.Vehicle _ 
             Where Typeof(veh) Is Truck And _ 
             IsIndustrial = (Typeof(veh) Is Semi _ 
             Or Typeof(veh) Is DumpTruck)

例 2

次の階層には、インターフェイスが含まれます。

C#

[Table]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle),
   IsDefault = true)]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "S", Type = typeof(Semi))]
[InheritanceMapping(Code = "H", Type = typeof(Helicopter))]

public class Truck: Vehicle
public class Semi: Truck, IRentableVehicle
public class Helicopter: Vehicle, IRentableVehicle

Visual Basic

<Table> _
<InheritanceMapping(Code:="V", Type:=TypeOf(Vehicle),
   IsDefault:=True) > _
<InheritanceMapping(Code:="C", Type:=TypeOf(Car)) > _
<InheritanceMapping(Code:="T", Type:=TypeOf(Truck)) > _
<InheritanceMapping(Code:="S", Type:=TypeOf(Semi)) > _
<InheritanceMapping(Code:="H", Type:=TypeOf(Helicopter)) > _
Public Class Truck
       Inherits Vehicle
Public Class Semi
       InheritsTruck, IRentableVehicle
Public Class Helicopter
       InheritsVehicle, IRentableVehicle

可能なクエリは次のとおりです。

C#

// Get commercial vehicles ordered by cost to rent.
db.Vehicles.OfType<IRentableVehicle>.OrderBy(cv => cv.RentalRate);

// Get all non-rentable vehicles
db.Vehicles.Where(v => !(v is IRentableVehicle));

Visual Basic

' Get commercial vehicles ordered by cost to rent.
Dim rentableVehicles = From veh In _ 
                       db.Vehicles.OfType(Of IRentableVehicle).OrderBy( _ 
                       Function(cv) cv.RentalRate)

' Get all non-rentable vehicles
Dim unrentableVehicles = From veh In _ 
                         db.Vehicles.OfType(Of Vehicle).Where( _ 
                         Function(uv) Not (TypeOf(uv) Is IRentableVehicle))

高度なトピック

データベースの作成

エンティティ クラスにはリレーショナル データベース テーブルと列の構造を記述する属性があるため、この情報を使用してデータベースの新しいインスタンスを作成できます。 DataContextCreateDatabase() メソッドを呼び出して、LINQ to SQLオブジェクトによって定義された構造体を使用して新しいデータベース インスタンスを構築できます。 これには、顧客システムに自動的にインストールされるアプリケーションや、オフライン状態を保存するためにローカル データベースが必要なクライアント アプリケーションを構築する場合があります。 これらのシナリオでは、CreateDatabase() が理想的です。特に、SQL Server Express 2005 のような既知のデータ プロバイダーが使用可能な場合です。

ただし、データ属性は、既存のデータベース構造に関するすべてのものをエンコードするわけではありません。 ユーザー定義関数、ストアド プロシージャ、トリガー、およびチェック制約の内容は、属性によって表されません。 CreateDatabase() 関数は、データベースの構造と各テーブル内の列の型である、それが認識する情報を使用してデータベースのレプリカのみを作成します。 ただし、さまざまなデータベースではこれで十分です。

MyDVDs.mdf という名前の新しいデータベースを作成する方法の例を次に示します。

C#

[Table(Name="DVDTable")]
public class DVD
{
   [Column(Id = true)]
   public string Title;
   [Column]
   public string Rating;
}

public class MyDVDs : DataContext
{
   public Table<DVD> DVDs;

   public MyDVDs(string connection) : base(connection) {}
}

Visual Basic

<Table(Name:="DVDTable")> _
Public Class DVD

   <Column(Id:=True)> _
   public Title As String
   <Column> _
   Public Rating As String
End Class

Public Class MyDVDs  
         Inherits DataContext

   Public DVDs As Table(Of DVD)

   Public Sub New(connection As String) 
End Class

オブジェクト モデルは、次のように、SQL Server Express 2005 を使用してデータベースを作成するために使用できます。

C#

MyDVDs db = new MyDVDs("c:\\mydvds.mdf");
db.CreateDatabase();

Visual Basic

Dim db As MyDVDs = new MyDVDs("c:\mydvds.mdf")
db.CreateDatabase()

LINQ to SQLには、新しいデータベースを作成する前に既存のデータベースを削除する API も用意されています。 上記のデータベース作成コードは、DatabaseExists() を使用してデータベースの既存のバージョンに対して最初にチェックし、DeleteDatabase() を使用して削除するように変更できます。

C#

MyDVDs db = new MyDVDs("c:\\mydvds.mdf");

if (db.DatabaseExists()) {
   Console.WriteLine("Deleting old database...");
   db.DeleteDatabase();
}

db.CreateDatabase();

Visual Basic

Dim db As MyDVDs = New MyDVDs("c:\mydvds.mdf")

If (db.DatabaseExists()) Then
   Console.WriteLine("Deleting old database...")
   db.DeleteDatabase()
End If

db.CreateDatabase()

CreateDatabase()の呼び出し後、新しいデータベースは、SubmitChanges() などのクエリとコマンドを受け入れて、MDF ファイルにオブジェクトを追加できます。

MDF ファイルまたはカタログ名のみを使用して、SQL Server Express以外の SKU で CreateDatabase() を使用することもできます。 これはすべて、接続文字列に使用する内容によって異なります。 接続文字列内の情報は、必ずしも既に存在するデータベースではなく、存在するデータベースを定義するために使用されます。 LINQ to SQLは、関連する情報を取得し、それを使用して、作成するデータベースと、それを作成するサーバーを決定します。 もちろん、これを行うには、サーバー上のデータベース管理者権限またはそれと同等のものが必要です。

ADO.NET との相互運用

LINQ to SQL は、ADO.NET テクノロジ ファミリの一部です。 これは、ADO.NET プロバイダー モデルによって提供されるサービスに基づいているため、既存の ADO.NET アプリケーションとLINQ to SQLコードを組み合わせることができます。

LINQ to SQL DataContext を作成するときに、既存の ADO.NET 接続を指定できます。 DataContext に対するすべての操作 (クエリを含む) では、指定した接続が使用されます。 接続が既に開かれている場合LINQ to SQLは、接続に対する権限を尊重し、接続が終了したときにそのままにします。 通常、LINQ to SQLは、トランザクションがスコープ内にある場合を除き、操作が完了するとすぐに接続を閉じます。

C#

SqlConnection con = new SqlConnection( ... );
con.Open(); 
...

// DataContext takes a connection
Northwind db = new Northwind(con);
...

var q =
   from c in db.Customers
   where c.City == "London"
   select c;

Visual Basic

Dim con As SqlConnection = New SqlConnection( ... )
con.Open()
...

' DataContext takes a connection
Dim db As Northwind = new Northwind(con)
...

Dim q = From c In db.Customers _
        Where c.City = "London" _
        Select c

DataContext で使用される接続には、Connection プロパティを使用していつでもアクセスして、自分で閉じることができます。

C#

db.Connection.Close(); 

Visual Basic

db.Connection.Close()

また、アプリケーションが既に開始していて、 DataContext と一緒に再生したい場合は、 DataContext に独自のデータベース トランザクションを指定することもできます。

C#

IDbTransaction = con.BeginTransaction();
...

db.Transaction = myTransaction;
db.SubmitChanges();
db.Transaction = null;

Visual Basic

Dim db As IDbTransaction = con.BeginTransaction()
...

db.Transaction = myTransaction
db.SubmitChanges()
db.Transaction = Nothing

Transaction が設定されるたびに、 クエリ を発行したりコマンドを実行したりするたびに、 DataContext によって使用されます。 完了したら、必ずプロパティを null に割り当て直してください。

ただし、.NET Frameworkでトランザクションを実行する場合は、TransactionScope オブジェクトを使用することをお勧めします。 これにより、データベースやその他のメモリ常駐リソース マネージャー間で動作する分散トランザクションを作成できます。 この考え方は、トランザクション スコープが安く開始され、トランザクションのスコープ内で複数のデータベースまたは複数の接続を実際に参照する場合にのみ、分散トランザクションを完全に昇格させるという考え方です。

C#

using(TransactionScope ts = new TransactionScope()) {
   db.SubmitChanges();
   ts.Complete();
}

Visual Basic

Using ts As TransactionScope= New TransactionScope()
   db.SubmitChanges()
   ts.Complete()
End Using

SQL ステートメントを直接実行する

接続とトランザクションは、ADO.NET と相互運用できる唯一の方法ではありません。 場合によっては、 DataContext のクエリまたは送信の変更機能が、実行する特殊なタスクに対して不十分である場合があります。 このような状況では、 DataContext を使用して生の SQL コマンドをデータベースに直接発行できます。

ExecuteQuery() メソッドを使用すると、生の SQL クエリを実行し、クエリの結果をオブジェクトに直接変換できます。 たとえば、Customer クラスのデータが customer1customer2 の 2 つのテーブルに分散されていると仮定すると、次のクエリは Customer オブジェクトのシーケンスを返します。

C#

IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
   @"select c1.custid as CustomerID, c2.custName as ContactName
      from customer1 as c1, customer2 as c2
      where c1.custid = c2.custid"
);

Visual Basic

Dim results As IEnumerable(Of Customer) = _
          db.ExecuteQuery(Of Customer)( _
          "select c1.custid as CustomerID, " & _
          "c2.custName as ContactName " & _
          "from customer1 as c1, customer2 as c2 "& _
          "where c1.custid = c2.custid" )

表形式の結果の列名がエンティティ クラスの列プロパティと一致する限り、LINQ to SQLは SQL クエリからオブジェクトを具体化します。

ExecuteQuery() メソッドでは、パラメーターも使用できます。 次のコードでは、パラメーター化されたクエリが実行されます。

C#

IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
   "select contactname from customers where city = {0}",
   "London"
);

Visual Basic

Dim results As IEnumerable(Of Customer) = _
          db.ExecuteQuery(Of Customer)( _
   "select contactname from customers where city = {0}", _
   "London" )

パラメーターは、Console.WriteLine() と String.Format() で使用されるのと同じカーリー表記を使用して、クエリ テキストで表されます。 実際、 String.Format() は、指定したクエリ文字列で実際に呼び出され、中かっこで固定されたパラメーターを p0@p1 ...p(n)などの生成されたパラメーター名に置き換えます。

競合の解決の変更

説明

変更の競合は、クライアントがオブジェクトに変更を送信しようとしたときに発生し、更新チェックで使用される 1 つ以上の値が、クライアントが最後に読み取ってからデータベースで更新されています。

メモUpdateCheck.Always または UpdateCheck.WhenChanged としてマップされたメンバーのみがオプティミスティック コンカレンシー チェックに参加します。 UpdateCheck.Never とマークされたメンバーに対してチェックは実行されません。

この競合の解決には、競合しているオブジェクトのメンバーを検出し、その処理を決定することが含まれます。 オプティミスティック コンカレンシーは、特定の状況では最適な戦略ではない可能性があることに注意してください。 場合によっては、"最後の更新プログラムを勝ち取る" のが完全に妥当な場合があります。

LINQ to SQLでの競合の検出、レポート、解決

競合の解決は、データベースに再度クエリを実行し、相違点を調整することで、競合するアイテムを更新するプロセスです。 オブジェクトが更新されると、変更トラッカーには古い元の値と新しいデータベース値があります。 LINQ to SQLオブジェクトが競合しているかどうかを判断します。 その場合、LINQ to SQLは関係するメンバーを決定します。 メンバーの新しいデータベース値が古い元の (失敗した更新チェックに使用された) と異なる場合、これは競合です。 メンバーの競合は、競合リストに追加されます。

たとえば、次のシナリオでは、User1 はデータベースに対して行のクエリを実行して更新プログラムの準備を開始します。 User1 が変更を送信する前に、User2 によってデータベースが変更されました。 Col B と Col C に必要な値が変更されたため、User1 の送信は失敗します。

データベース更新の競合

User Col A Col B Col C
元の状態 Alfreds Maria Sales
ユーザー 1 Alfred   Marketing
ユーザー 2   Mary サービス

LINQ to SQLでは、オプティミスティック コンカレンシーの競合のために更新に失敗したオブジェクトによって、例外 (ChangeConflictException) がスローされます。 例外を最初のエラーでスローするか、または例外で累積および報告されるエラーを含むすべての更新を試行するかどうかを指定できます。

// [C#]
db.SubmitChanges(ConflictMode.FailOnFirstConflict);
db.SubmitChanges(ConflictMode.ContinueOnConflict);
' [Visual Basic]
db.SubmitChanges(ConflictMode.FailOnFirstConflict)
db.SubmitChanges(ConflictMode.ContinueOnConflict)

スローされると、例外は ObjectChangeConflict コレクションへのアクセスを提供します。 詳細は、 MemberConflicts リストへのアクセスを含め、競合ごとに使用できます (1 回の失敗した更新試行にマップされます)。 各メンバー競合は、コンカレンシー チェックでエラーになった更新の 1 つのメンバーに対応します。

競合処理

前のシナリオでは、User1 には、再送信を試みる前に相違点を調整するための RefreshMode オプションが以下に示されています。 いずれの場合も、更新されたデータをデータベースからプルダウンすることで、クライアント上のレコードが最初に "更新" されます。 このアクションにより、同じコンカレンシー チェックで次の更新の試行が失敗しないようにします。

ここでは、User1 は、現在の変更セットもその値を変更した場合にのみデータベース値が上書きされるように、データベース値を現在のクライアント値とマージすることを選択します。 (このセクションの後半の例 1 を参照してください)。

上記のシナリオでは、競合の解決後、データベースの結果は次のようになります。

KeepChanges

  Col A Col B Col C
KeepChanges アルフレッド (ユーザー 1) Mary (ユーザー 2) マーケティング (ユーザー 1)
  • Col A: User1 の変更 (アルフレッド) が表示されます。
  • Col B: User2 の変更 (Mary) が表示されます。 User1 で変更されていないため、この値はマージされました。
  • Col C: User1 の変更 (マーケティング) が表示されます。 User1 もそのアイテムを変更したため、User2 の変更 (サービス) はマージされません。

以下では、User1 は、データベース値を現在の値で上書きすることを選択します。 (このセクションの後の例 2 を参照してください)。

更新後、User1 の変更が送信されます。 データベースの結果は次のようになります。

KeepCurrentValues

  Col A Col B Col C
KeepCurrentValues アルフレッド (ユーザー 1) Maria (オリジナル) マーケティング (ユーザー 1)
  • Col A: User1 の変更 (アルフレッド) が表示されます。
  • コルB:元のマリアは残っています。User2 の変更は破棄されます。
  • Col C: User1 の変更 (マーケティング) が表示されます。 User2 の変更 (サービス) は破棄されます。

次のシナリオでは、User1 は、データベース値がクライアントの現在の値を上書きすることを許可することを選択します。 (このセクションの後の例 3 を参照してください)。

上記のシナリオでは、競合の解決後、データベースの結果は次のようになります。

OverwriteCurrentValues

  Col A Col B Col C
OverwriteCurrentValues アルフレッド (オリジナル) Mary (ユーザー 2) サービス (ユーザー 2)
  • コルA:元の値(アルフレッド)は残っています。User1 の値 (アルフレッド) は破棄されます。
  • Col B: User2 の変更 (Mary) が表示されます。
  • Col C: User2 の変更 (サービス) が表示されます。 User1 の変更 (マーケティング) は破棄されます。

競合が解決したら、再送信を試みることができます。 この 2 回目の更新も失敗する可能性があるため、更新の試行にループを使用することを検討してください。

次のコードの抜粋は、メンバーの競合を検出して解決するためのさまざまな情報メンバーと手法を示しています。

例 1

この例では、競合は "自動的に" 解決されます。つまり、クライアントがその値 (KeepChanges) も変更していない限り、データベース値は現在のクライアント値とマージされます。 個々のメンバーの競合の検査またはカスタム処理は行われません。

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   //automerge database values into current for members
   //that client has not modified
   context.ChangeConflicts.Resolve(RefreshMode.KeepChanges);
}
//submit succeeds on second try
context.SubmitChanges(ConflictMode.FailOnFirstConflict);

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   ' automerge database values into current for members
   ' that client has not modified   context.ChangeConflicts.Resolve(RefreshMode.KeepChanges)
End Try
' submit succeeds on second try
context.SubmitChanges(ConflictMode.FailOnFirstConflict)

例 2

この例では、競合はカスタム処理なしで再度解決されます。 ただし、今回は、データベース値は現在のクライアント値にマージされません。

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   foreach (ObjectChangeConflict cc in context.ChangeConflicts) {
      //No database values are automerged into current
      cc.Resolve(RefreshMode.KeepCurrentValues);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      ‘No database values are automerged into current
      cc.Resolve(RefreshMode.KeepCurrentValues)
   Next
End Try

例 3

ここでも、カスタム処理は行われません。 ただし、この場合、すべてのクライアント値は現在のデータベース値で更新されます。

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict); 
}
catch (ChangeConflictException e) {
   foreach (ObjectChangeConflict cc in context.ChangeConflicts) {
      //No database values are automerged into current
      cc.Resolve(RefreshMode.OverwriteCurrentValues);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      ' No database values are automerged into current
      cc.Resolve(RefreshMode. OverwriteCurrentValues)
   Next
End Try

例 4

この例では、競合しているエンティティの情報にアクセスする方法を示します。

C#

try {
   user1.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   Console.WriteLine("Optimistic concurrency error");
   Console.ReadLine();
   foreach (ObjectChangeConflict cc in user1.ChangeConflicts) {
      ITable table = cc.Table;
      Customers entityInConflict = (Customers)cc.Object;
      Console.WriteLine("Table name: {0}", table.Name);
      Console.Write("Customer ID: ");
      Console.WriteLine(entityInConflict.CustomerID);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   Console.WriteLine("Optimistic concurrency error")
   Console.ReadLine()
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      Dim table As ITable = cc.Table
      Dim entityInConflict As Customers = CType(cc.Object, Customers)
      Console.WriteLine("Table name: {0}", table.Name)
      Console.Write("Customer ID: ")
      Console.WriteLine(entityInConflict.CustomerID)
   Next
End Try

例 5

次の使用例は、個々のメンバーにループを追加します。 ここでは、任意のメンバーのカスタム処理を提供できます。

メモSystem.Reflection を使用して を追加します。MemberInfo を指定します。

C#

try {
   user1.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   Console.WriteLine("Optimistic concurrency error");
   Console.ReadLine();
   foreach (ObjectChangeConflict cc in user1.ChangeConflicts) {
      ITable table = cc.Table;
      Customers entityInConflict = (Customers)cc.Object;
      Console.WriteLine("Table name: {0}", table.Name);
      Console.Write("Customer ID: ");
      Console.WriteLine(entityInConflict.CustomerID);
      foreach (MemberChangeConflict mc in         cc.MemberConflicts) {
         object currVal = mc.CurrentValue;
         object origVal = mc.OriginalValue;
         object databaseVal = mc.DatabaseValue;
         MemberInfo mi = mc. Member;
         Console.WriteLine("Member: {0}", mi.Name);
         Console.WriteLine("current value: {0}", currVal);
         Console.WriteLine("original value: {0}", origVal);
         Console.WriteLine("database value: {0}", databaseVal);
         Console.ReadLine();
      }
   }
}

Visual Basic

Try 
   user1.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   Console.WriteLine("Optimistic concurrency error")
   Console.ReadLine()
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      Dim table As ITable = cc.Table
      Dim entityInConflict As Customers = CType(cc.Object, Customers)
      Console.WriteLine("Table name: {0}", table.Name)
      Console.Write("Customer ID: ")
      Console.WriteLine(entityInConflict.CustomerID)
   For Each mc As MemberChangeConflict In   cc.MemberConflicts
         Dim currVal As Object = mc.CurrentValue
         Dim origVal As Object = mc.OriginalValue
         Dim databaseVal As Object = mc.DatabaseValue
         Dim mi As MemberInfo = mc.Member
         Console.WriteLine("Member: {0}", mi.Name)
         Console.WriteLine("current value: {0}", currVal)
         Console.WriteLine("original value: {0}", origVal)
         Console.WriteLine("database value: {0}", databaseVal)
         Console.ReadLine()
      Next

   Next
End Try

ストアド プロシージャの呼び出し

LINQ to SQL は、ストアド プロシージャとユーザー定義関数をサポートしています。 LINQ to SQLは、これらのデータベース定義の抽象化をコードで生成されたクライアント オブジェクトにマップし、クライアント コードから厳密に型指定された方法でアクセスできるようにします。 IntelliSense を使用してこれらのメソッドを簡単に検出できます。また、メソッドシグネチャは、データベースで定義されているプロシージャと関数のシグネチャと可能な限りよく似ています。 マップされたプロシージャの呼び出しによって返される結果セットは、厳密に型指定されたコレクションです。 LINQ to SQLは、マップされたメソッドを自動的に生成できますが、コード生成を使用しないことを選択した場合でも手動マッピングをサポートします。

LINQ to SQLは、属性を使用してストアド プロシージャと関数をメソッドにマップします。 StoredProcedureParameter、および Function 属性はすべて Name プロパティをサポートし、Parameter 属性は DBType プロパティもサポートします。 次に 2 つの例を示します。

C#

   [StoredProcedure()]
   public IEnumerable<CustOrderHistResult> CustOrderHist(
      [Parameter(Name="CustomerID", DBType="NChar(5)")] string customerID) {

      IExecuteResult result = this.ExecuteMethodCall(this, 
         ((MethodInfo)(MethodInfo.GetCurrentMethod())), customerID);

      return ((IEnumerable<CustOrderHistResult>)(result.ReturnValue));
   }

[Function(Name="[dbo].[ConvertTemp]")]
public string ConvertTemp(string string) { ... }

Visual Basic

<StoredProcedure()> _
   Public Function CustOrderHist( _
         <Parameter(Name:="CustomerID", DBType:="NChar(5)")> _
         customerID As String) As IEnumerable(Of CustOrderHistResult)

         Dim result As IExecuteResult = ExecuteMethodCall(Me, _
                 CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID)

         Return CType(result.ReturnValue, IEnumerable(Of CustOrderHistResult))
   End Function

<Function(Name:="[dbo].[ConvertTemp]")> _
Public Function ConvertTemp(str As String) As String

次の例は、さまざまな種類のストアド プロシージャのマッピングを示しています。

例 1

次のストアド プロシージャは、1 つの入力パラメーターを受け取り、整数を返します。

CREATE PROCEDURE GetCustomerOrderCount(@CustomerID nchar(5))
AS
Declare @count int
SELECT @count = COUNT(*) FROM ORDERS WHERE CustomerID = @CustomerID
RETURN @count

マップされたメソッドは次のようになります。

C#

[StoredProcedure(Name = "GetCustomerOrderCount")]
public int GetCustomerOrderCount(
      [Parameter(Name = "CustomerID")] string customerID) {
         IExecuteResult result = this.ExecuteMethodCall(this, 
            ((MethodInfo)(MethodInfo.GetCurrentMethod())), customerID); 
          return (int) result.ReturnValue;
}

Visual Basic

<StoredProcedure (Name:="GetCustomerOrderCount")> _
public Function GetCustomerOrderCount( _
      <Parameter(Name:= "CustomerID")> customerID As String) As Integer
         Dim result As IExecuteResult = ExecuteMethodCall(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID)
          return CInt(result.ReturnValue)
End Function

例 2

複数の結果形状を返すことができるストアド プロシージャの場合、戻り値の型を単一の射影形状として厳密に型指定することはできません。 次の例では、結果の図形は入力に依存します。

CREATE PROCEDURE VariableResultShapes(@shape int)
AS
if(@shape = 1)
   select CustomerID, ContactTitle, CompanyName from customers
else if(@shape = 2)
   select OrderID, ShipName from orders

マップされたメソッドは次のとおりです。

C#

      [StoredProcedure(Name = "VariableResultShapes")]
      [ResultType(typeof(Customer))]
      [ResultType(typeof(Order))]
      public IMultipleResults VariableResultShapes(System.Nullable<int> shape) {
         IExecuteResult result = this.ExecuteMethodCallWithMultipleResults(this,
            ((MethodInfo)(MethodInfo.GetCurrentMethod())), shape);
         return (IMultipleResults) result.ReturnValue;
      }

Visual Basic

<StoredProcedure(Name:= "VariableResultShapes")> _
      <ResultType(typeof(Customer))> _
      <ResultType(typeof(Order))> _
   public VariableResultShapes(shape As Integer?) As IMultipleResults
      Dim result As IExecuteResult =
                ExecuteMethodCallWithMultipleResults(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), shape)
         return CType(result.ReturnValue, IMultipleResults)
      End Function

このストアド プロシージャは、次のように使用できます。

C#

      IMultipleResults result = db.VariableResultShapes(1);
      foreach (Customer c in result.GetResult<Customer>()) {
         Console.WriteLine(c.CompanyName);
      }

      result = db.VariableResultShapes(2);
      foreach (Order o in result.GetResult<Order>()) {
         Console.WriteLine(o.OrderID);
      }           

Visual Basic

Dim result As IMultipleResults = db.VariableResultShapes(1)
      For Each c As Customer In result.GetResult(Of Customer)()
         Console.WriteLine(c.CompanyName)
      Next

      result = db.VariableResultShapes(2);
      For Each o As Order In result.GetResult(Of Order)()
         Console.WriteLine(o.OrderID)
      Next 
         
      }           

ここでは、 GetResult パターンを使用して、ストアド プロシージャに関する知識に基づいて、正しい型の列挙子を取得する必要があります。 LINQ to SQLは、可能なすべてのプロジェクション型を生成できますが、返される順序を知る方法はありません。 マップされたメソッドに対応する生成されたプロジェクション型を知ることができる唯一の方法は、 メソッドに対して生成されたコード コメントを使用することです。

例 3

複数の結果図形を順番に返すストアド プロシージャの T-SQL を次 示します。

CREATE PROCEDURE MultipleResultTypesSequentially
AS
select * from products
select * from customers

LINQ to SQLは、上記の例 2 と同様に、この手順をマップします。 ただし、この場合は、2 つの 連続した 結果セットがあります。

C#

[StoredProcedure(Name="MultipleResultTypesSequentially")]      
[ResultType(typeof(Product))]
[ResultType(typeof(Customer))]
public IMultipleResults MultipleResultTypesSequentially() {
   return ((IMultipleResults)(
      this.ExecuteMethodCallWithMultipleResults (this, 
         ((MethodInfo)(MethodInfo.GetCurrentMethod()))).ReturnValue
      )
   );
}

Visual Basic

<StoredProcedure(Name:="MultipleResultTypesSequentially")> _
<ResultType(typeof(Customer))> _
<ResultType(typeof(Order))> _
public Function MultipleResultTypesSequentially() As IMultipleResults
   Return CType( ExecuteMethodCallWithMultipleResults (Me, _
         CType(MethodInfo.GetCurrentMethod(), MethodInfo)), _
         IMultipleResults).ReturnValue
      
End Function

このストアド プロシージャは、次のように使用できます。

C#

      IMultipleResults sprocResults = db.MultipleResultTypesSequentially();

      //first read products
      foreach (Product p in sprocResults.GetResult<Product>()) {
         Console.WriteLine(p.ProductID);
      }

      //next read customers
      foreach (Customer c in sprocResults.GetResult<Customer>()){
         Console.WriteLine(c.CustomerID);
      }

Visual Basic

Dim sprocResults As IMultipleResults = db.MultipleResultTypesSequentially()

   ' first read products
   For Each P As Product In sprocResults.GetResult(Of Product)()
      Console.WriteLine(p.ProductID)
   Next

   ' next read customers
   For Each c As Customer c In sprocResults.GetResult(Of Customer)()
      Console.WriteLine(c.CustomerID) 
   Next

例 4

LINQ to SQLはパラメーターを参照パラメーター (ref キーワード (keyword)) にマップoutし、値型の場合、パラメーターを null 許容として宣言します (例: int?)。 次の例のプロシージャは、1 つの入力パラメーターを受け取り、 パラメーターを out 返します。

CREATE PROCEDURE GetCustomerCompanyName(
   @customerID nchar(5),
   @companyName nvarchar(40) output
   )
AS
SELECT @companyName = CompanyName FROM Customers
WHERE CustomerID=@CustomerID

マップされたメソッドは次のとおりです。

C#

      [StoredProcedure(Name = "GetCustomerCompanyName")]
      public int GetCustomerCompanyName(
         string customerID, ref string companyName) {

         IExecuteResult result =
            this.ExecuteMethodCall(this,
               ((MethodInfo)(MethodInfo.GetCurrentMethod())),
               customerID, companyName);

         companyName = (string)result.GetParameterValue(1);
         return (int)result.ReturnValue;
      }

Visual Basic

   <StoredProcedure(Name:="GetCustomerCompanyName")> _
      Public Function GetCustomerCompanyName( _
               customerID As String, ByRef companyName As String) As Integer

      Dim result As IExecuteResult = ExecuteMethodCall(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID, _
               companyName)

         companyName = CStr(result.GetParameterValue(1))
         return CInt(result.ReturnValue)
      End Function

この場合、メソッドには明示的な戻り値はありませんが、既定の戻り値はマップされます。 出力パラメーターには、対応する出力パラメーターが想定どおりに使用されます。

上記のストアド プロシージャを次のように呼び出します。

C#

string CompanyName = "";
string customerID = "ALFKI";
db.GetCustomerCompanyName(customerID, ref CompanyName);
Console.WriteLine(CompanyName);

Visual Basic

Dim CompanyName As String = ""
Dim customerID As String = "ALFKI"
db.GetCustomerCompanyName(customerID, CompanyName)
Console.WriteLine(CompanyName)

ユーザー定義関数

LINQ to SQLでは、スカラー値関数とテーブル値関数の両方がサポートされ、両方に対応するインライン関数がサポートされます。

LINQ to SQLは、システム定義関数の呼び出し方法と同様にインライン スカラー呼び出しを処理します。 次のクエリを考えてみます。

C#

var q =
   from p in db.Products
   select
      new {
         pid = p.ProductID,
         unitp = Math.Floor(p.UnitPrice.Value)
      };

Visual Basic

Dim productInfos = From prod In db.Products _
                   Select p.ProductID, price = Math.Floor(p.UnitPrice.Value)

ここでは、メソッド呼び出し Math.Floor は、システム関数 'FLOOR' の呼び出しに変換されます。 同様に、UDF にマップされている関数の呼び出しは、SQL の UDF の呼び出しに変換されます。

例 1

スカラー ユーザー定義関数 (UDF) ReverseCustName() を次に示します。 SQL Serverでは、関数は次のように定義できます。

CREATE FUNCTION ReverseCustName(@string varchar(100))
RETURNS varchar(100)
AS
BEGIN
   DECLARE @custName varchar(100)
   -- Impl. left as exercise for the reader
   RETURN @custName
END

次のコードを使用して、スキーマ クラスで定義されているクライアント メソッドをこの UDF にマップできます。 メソッドの本体は、メソッド呼び出しの意図をキャプチャし、変換と実行のためにその式を DataContext に渡す式を構築します。 (この直接実行は、関数が呼び出された場合にのみ実行されます)。

C#

[Function(Name = "[dbo].[ReverseCustName]")]
public string ReverseCustName(string string1) {
   IExecuteResult result = this.ExecuteMethodCall(this,
      (MethodInfo)(MethodInfo.GetCurrentMethod())), string1);
   return (string) result.ReturnValue;
}

Visual Basic

Function(Name:= "[dbo].[ReverseCustName]")> _
Public Function ReverseCustName(string1 As String) As String

    Dim result As IExecuteResult = ExecuteMethodCall(Me, _
             CType(MethodInfo.GetCurrentMethod(), MethodInfo), string1)
   return CStr(result.ReturnValue)

例 2

次のクエリでは、生成された UDF メソッド ReverseCustName のインライン呼び出しを確認できます。 この場合、関数はすぐには実行されません。 このクエリ用に構築された SQL は、データベースで定義されている UDF の呼び出しに変換されます (クエリの後の SQL コードを参照してください)。

C#

var q =
   from c in db.Customers
   select
      new {
         c.ContactName,
         Title = db.ReverseCustName(c.ContactTitle)
      };

Visual Basic

Dim customerInfos = From cust In db.Customers _
                    Select c.ContactName, _
                    Title = db.ReverseCustName(c.ContactTitle)



SELECT [t0].[ContactName],
   dbo.ReverseCustName([t0].[ContactTitle]) AS [Title]
FROM [Customers] AS [t0]

クエリの外部で同じ関数を呼び出すと、LINQ to SQLは、次の SQL 構文 (パラメーター@p0が渡された定数にバインドされている) を使用して、メソッド呼び出し式から単純なクエリを作成します。

LINQ to SQL:

C#

string str = db.ReverseCustName("LINQ to SQL");

Visual Basic

Dim str As String = db.ReverseCustName("LINQ to SQL")

変換対象:

SELECT dbo.ReverseCustName(@p0)

例 3

テーブル値関数 (TVF) は、(複数の結果図形を返すことができるストアド プロシージャとは異なり) 1 つの結果セットを返します。 TVF 戻り値の型は table であるため、テーブルを使用できる SQL 内の任意の場所で TVF を使用でき、TVF はテーブルと同じ方法で処理できます。

テーブル値関数の次のSQL Server定義について考えてみましょう。

CREATE FUNCTION ProductsCostingMoreThan(@cost money)
RETURNS TABLE
AS
RETURN
   SELECT ProductID, UnitPrice
   FROM Products
   WHERE UnitPrice > @cost

この関数では、TABLE が返されることを明示的に示しているため、返される結果セット構造は暗黙的に定義されます。 LINQ to SQL は、次のように関数をマップします。

C#

       [Function(Name = "[dbo].[ProductsCostingMoreThan]")]
      public IQueryable<Product> ProductsCostingMoreThan(
            System.Nullable<decimal> cost) {

         return this.CreateMethodCallQuery<Product>(this,
            (MethodInfo)MethodInfo.GetCurrentMethod(),
            cost);
      }

Visual Basic

   <Function(Name:="[dbo].[ProductsCostingMoreThan]")> _
      Public Function ProductsCostingMoreThan(
            cost As System.Nullable(Of Decimal)) As IQueryable(Of Product)

    Return CreateMethodCallQuery(Of Product)(Me, _
             CType(MethodInfo.GetCurrentMethod(), MethodInfo), cost)

次の SQL コードは、関数によって返されるテーブルに結合でき、それ以外の場合は他のテーブルと同様に扱うことができることを示しています。

SELECT p2.ProductName, p1.UnitPrice
FROM dbo.ProductsCostingMoreThan(80.50)
AS p1 INNER JOIN Products AS p2 ON p1.ProductID = p2.ProductID

LINQ to SQLでは、クエリは次のようにレンダリングされます (新しい 'join' 構文を使用)。

C#

var q =
   from p in db.ProductsCostingMoreThan(80.50m)
   join s in db.Products on p.ProductID equals s.ProductID
   select new {p.ProductID, s.UnitPrice};

Visual Basic

Dim productInfos = From costlyProd In db.ProductsCostingMoreThan(80.50m) _
                   Join prod In db.Products _
                   On costlyProd.ProductID Equals prod.ProductID _
                   Select costlyProd.ProductID, prod.UnitPrice

ストアド プロシージャのLINQ to SQL制限事項

LINQ to SQLでは、静的に決定された結果セットを返すストアド プロシージャのコード生成がサポートされています。 したがって、LINQ to SQL コード ジェネレーターでは、次の機能はサポートされていません。

  • 動的 SQL を使用して結果セットを返すストアド プロシージャ。 ストアド プロシージャに動的 SQL ステートメントを作成するための条件付きロジックが含まれている場合、結果セットの生成に使用されるクエリは実行時まで不明であるため、LINQ to SQLは結果セットのメタデータを取得できません。
  • 一時テーブルに基づいて結果を生成するストアド プロシージャ。

エンティティ クラス ジェネレーター ツール

既存のデータベースがある場合は、それを表すためだけに完全なオブジェクト モデルを手動で作成する必要はありません。 LINQ to SQLディストリビューションには、SQLMetal というツールが付属しています。 これは、データベース メタデータから適切なクラスを推論することによってエンティティ クラスを作成するタスクを自動化するコマンド ライン ユーティリティです。

SQLMetal を使用すると、データベースから SQL メタデータを抽出し、エンティティ クラス宣言を含むソース ファイルを生成できます。 または、プロセスを 2 つの手順に分割し、最初に SQL メタデータを表す XML ファイルを生成してから、その XML ファイルをクラス宣言を含むソース ファイルに変換することもできます。 この分割プロセスを使用すると、メタデータを編集できるようにファイルとして保持できます。 ファイルを生成する抽出プロセスでは、データベースのテーブル名と列名を指定すると、適切なクラス名とプロパティ名に関するいくつかの推論が行われます。 ジェネレーターがより適切な結果を生成したり、オブジェクトに存在したくないデータベースの側面を非表示にしたりするために、XML ファイルを編集することが必要な場合があります。

SQLMetal を使用する最も簡単なシナリオは、既存のデータベースからクラスを直接生成することです。 ツールを呼び出す方法を次に示します。

C#

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize /namespace:nwind /code:Northwind.cs

Visual Basic

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize /namespace:nwind /code:Northwind.vb /language:vb

ツールを実行すると、データベース メタデータを読み取って生成されたオブジェクト モデルを含む Northwind.cs または .vb ファイルが作成されます。 この使用法は、データベース内のテーブルの名前が、生成するオブジェクトの名前に似ている場合に適切に機能します。 そうでない場合は、2 段階のアプローチを取る必要があります。

DBML ファイルを生成するように SQLMetal に指示するには、次のようにツールを使用します。

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize
   /xml:Northwind.dbml

dbml ファイルが生成されたら、先に進み、 クラスプロパティ 属性を使用して注釈を付けて、テーブルと列がクラスとプロパティにどのようにマップされるかを説明できます。 dbml ファイルに注釈を付けたら、次のコマンドを実行してオブジェクト モデルを生成できます。

C#

SqlMetal /namespace:nwind /code:Northwind.cs Northwind.dbml

Visual Basic

SqlMetal /namespace:nwind /code:Northwind.vb Northwind.dbml /language:vb

SQLMetal の使用法の署名は次のとおりです。

SqlMetal [options] [filename]

SQLMetal で使用できるコマンド ライン オプションを示す表を次に示します。

SQLMetal のコマンド ライン オプション

オプション 説明
/server:<name> データベースにアクセスするために接続するサーバーを示します。
/database:<name> メタデータの読み取り元となるデータベースの名前を示します。
/user:<name> サーバーのログイン ユーザー ID。
/password:<name> サーバーのログイン パスワード。
/views データベース ビューを抽出します。
/functions データベース関数を抽出します。
/sprocs ストアド プロシージャを抽出します。
/code[:<filename>] ツールの出力がエンティティ クラス宣言のソース ファイルであることを示します。
/language:<language> Visual Basic または C# (既定値) を使用します。
/xml[:<filename>] ツールの出力が、データベース メタデータとクラス名とプロパティ名の最初の推測近似を記述する DBML ファイルであることを示します。
/map[:<filename>] 属性の代わりに外部マッピング ファイルを使用する必要があることを示します。
/pluralize 適切なクラス名とプロパティ名を生成するために、ツールでテーブルの名前に対して英語の複数形化/複数形化解除ヒューリスティックを実行する必要があることを示します。
/namespace:<name> エンティティ クラスが生成される名前空間を示します。
/timeout:<秒> データベース コマンドに使用するタイムアウト値 (秒単位)。

メモ MDF ファイルからメタデータを抽出するには、他のすべてのオプションの後に MDF ファイル名を指定する必要があります。 /server が指定されていない場合、localhost と見なされます。

ジェネレーター ツール DBML リファレンス

DBML (データベース マッピング言語) ファイルは、まず、特定のデータベースの SQL メタデータの説明です。 これは、データベース メタデータを調べることによって SQLMetal によって抽出されます。 SQLMetal でも同じファイルが使用され、データベースを表す既定のオブジェクト モデルが生成されます。

DBML 構文のプロトタイプの例を次に示します。

<?xml version="1.0" encoding="utf-16"?>
<Database Name="Northwind" EntityNamespace="Mappings.FunctionMapping"
   ContextNamespace="Mappings.FunctionMapping"
   Provider="System.Data.Linq.SqlClient.Sql2005Provider"
   xmlns="https://schemas.microsoft.com/dsltools/LINQ to SQLML">
   <Table Name="Categories">
      <Type Name="Category">
         <Column Name="CategoryID" Type="System.Int32"
            DbType="Int NOT NULL IDENTITY" IsReadOnly="False" 
            IsPrimaryKey="True" IsDbGenerated="True" CanBeNull="False" />
         <Column Name="CategoryName" Type="System.String"
            DbType="NVarChar(15) NOT NULL" CanBeNull="False" />
         <Column Name="Description" Type="System.String"
            DbType="NText" CanBeNull="True" UpdateCheck="Never" />
         <Column Name="Picture" Type="System.Byte[]"
            DbType="Image" CanBeNull="True" UpdateCheck="Never" />
         <Association Name="FK_Products_Categories" Member="Products"
            ThisKey="CategoryID" OtherKey="CategoryID"
            OtherTable="Products" DeleteRule="NO ACTION" />
      </Type>
   </Table>

   <Function Name="GetCustomerOrders">
      <Parameter Name="customerID" Type="System.String" DbType="NChar(5)" />
      <ElementType Name="GetCustomerOrdersResult">
         <Column Name="OrderID" Type="System.Int32"
            DbType="Int" CanBeNull="True" />
         <Column Name="ShipName" Type="System.String"
            DbType="NVarChar(40)" CanBeNull="True" />
         <Column Name="OrderDate" Type="System.DateTime"
            DbType="DateTime" CanBeNull="True" />
         <Column Name="Freight" Type="System.Decimal"
            DbType="Money" CanBeNull="True" />
      </ElementType>
   </Function>
</Database>

要素とその属性を次に示します。

データベース

これは XML 形式の最も外側の要素です。 この要素は、生成された DataContext の Database 属性に疎にマップされます。

データベース属性

属性 種類 Default 説明
名前 String なし データベースの名前。 存在する場合、および DataContext を生成する場合は、この名前で Database 属性がアタッチされます。 クラス属性が存在しない場合は、 DataContext クラスの名前としても使用されます。
EntityNamespace Strong なし Table 要素内の Type 要素から生成されるクラスの既定の名前空間。 ここで名前空間が指定されていない場合、エンティティ クラスはルート名前空間に生成されます。
ContextNamespace String なし 生成された DataContext クラスの既定の名前空間。 ここで名前空間が指定されていない場合は、 DataContext クラスがルート名前空間に生成されます。
クラス String Database.Name 生成された DataContext クラスの名前。 存在しない場合は、Database 要素の Name 属性を使用します。
AccessModifier AccessModifier パブリック 生成された DataContext クラスのアクセシビリティ レベル。 有効な値は、 PublicProtectedInternalPrivate です
BaseType String "System.Data.Linq.DataContext" DataContext クラスの基本型。
プロバイダー String "System.Data.Linq.SqlClient.Sql2005Provider" DataContext のプロバイダーは、Sql2005 プロバイダーを既定として使用します
ExternalMapping Boolean False 外部マッピング ファイルの生成に DBML を使用するかどうかを指定します。
シリアル化 SerializationMode SerializationMode.None 生成された DataContext クラスとエンティティ クラスをシリアル化できるかどうかを指定します。

データベース Sub-Element属性

Sub-Element 要素型 出現範囲 説明
<Table> テーブル 0-unbounded 1 つの型または継承階層にマップされるSQL Serverテーブルまたはビューを表します。
<Function> 機能 0-unbounded 生成された DataContext クラスのメソッドにマップされる、SQL Serverストアド プロシージャまたは db 関数を表します。
<[接続]> Connection 0-1 DataContext が使用するデータベース接続を表します。

テーブル

この要素は、単一の型または継承階層にマップされるデータベース テーブル (またはビュー) を表します。 この要素は、生成されたエンティティ クラスの Table 属性に疎にマップされます。

テーブル属性

属性 種類 Default 説明
名前 String (必須) データベース内のテーブルの名前。 必要に応じて、テーブル アダプターの既定の名前のベースとして機能します。
メンバー String Table.Name DataContext クラス内でこのテーブルに対して生成されるメンバー フィールドの名前。
AccessModifier AccessModifier パブリック DataContext 内の Table<T> 参照のアクセシビリティ レベル。 有効な値は、 PublicProtectedInternalPrivate です

テーブル Sub-Element属性

Sub-Element 要素型 出現範囲 説明
<Type> 種類 1-1 このテーブルにマップされている型または継承階層を表します。
<InsertFunction> TableFunction 0-1 挿入するメソッド。 存在する場合は、 InsertT メソッドが生成されます。
<UpdateFunction> TableFunction 0-1 更新のメソッド。 存在する場合は、 UpdateT メソッドが生成されます。
<DeleteFunction> TableFunction 0-1 削除するメソッド。 存在する場合は、 DeleteT メソッドが生成されます。

種類

この要素は、Table またはストアド プロシージャの結果図形の型定義を表します。 これにより、列と関連付けが指定された新しい CLR 型にコード生成されます。

型は、継承階層のコンポーネントを表し、複数の型が同じテーブルにマッピングされている場合もあります。 この場合、Type 要素は親子の継承関係を表すために入れ子になり、指定された InheritanceCode によってデータベース内で区別されます。

型属性

属性 種類 Default 説明
名前 String (必須) 生成する CLR 型の名前。
InheritanceCode String None この型が継承に参加している場合は、テーブルから行を読み込むときに CLR 型を区別するための継承コードを関連付けることができます。 InheritanceCodeIsDiscriminator 列の値と一致する Type は、読み込まれたオブジェクトをインスタンス化するために使用されます。 継承コードが存在しない場合、生成されたエンティティ クラスは抽象です。
IsInheritanceDefault Boolean False 継承階層内の Type に対してこれが当てはまる場合、この型は、定義された継承コードに一致しない行を読み込むときに使用されます。
AccessModifier AccessModifier パブリック 作成される CLR 型のアクセシビリティ レベル。 有効な値は、 PublicProtectedInternalPrivate です
Id String None 型には一意の ID を指定できます。型の ID は、他のテーブルまたは関数で使用できます。 ID は DBML ファイルにのみ表示され、オブジェクト モデルには表示されません。
Idref String None IdRef は、別の型の ID を参照するために使用 されますIdRef が型要素に存在する場合、type 要素には IdRef 情報のみを含む必要があります。 IdRef は DBML ファイルにのみ表示され、オブジェクト モデルには表示されません。

型Sub-Element属性

Sub-Element 要素型 出現範囲 説明
<列> 0-unbounded この型のテーブル内のフィールドにバインドされる、この型内のプロパティを表します。
<関連付け> 関連付け 0-unbounded テーブル間の外部キー リレーションシップの一方の端にバインドされる、この型内のプロパティを表します。
<Type> サブタイプ 0-unbounded 継承階層内のこの型のサブタイプを表します。

サブタイプ

この要素は、継承階層内の派生型を表します。 これは、この型で指定された列と関連付けを使用して、新しい CLR 型に生成されます。 サブタイプの継承属性は生成されません。

Type と比較すると、SubType 要素には AccessModifier がありません。すべての派生型はパブリックである必要があるためです。 SubTypes は他のテーブルや関数では再利用できないため、 IdIdRef は含められません。

SubType 属性

属性 種類 Default 説明
名前 String (必須) 生成する CLR 型の名前。
InheritanceCode String なし この型が継承に参加している場合は、テーブルから行を読み込むときに CLR 型を区別するための継承コードを関連付けることができます。 InheritanceCodeIsDiscriminator 列の値と一致する Type は、読み込まれたオブジェクトをインスタンス化するために使用されます。 継承コードが存在しない場合、生成されたエンティティ クラスは抽象です。
IsInheritanceDefault Boolean False 継承階層内の Type に対してこれが当てはまる場合、この型は、定義された継承コードに一致しない行を読み込むときに使用されます。

SubType Sub-Element属性

Sub-Element 要素型 出現範囲 説明
<列> 0-unbounded この型のテーブル内のフィールドにバインドされる、この型内のプロパティを表します。
<関連付け> 関連付け 0-unbounded テーブル間の外部キー リレーションシップの一方の端にバインドされる、この型内のプロパティを表します。
<Type> サブタイプ 0-unbounded 継承階層内のこの型のサブタイプを表します。

この要素は、クラス内のプロパティ (およびバッキング フィールド) にマップされるテーブル内の列を表します。 ただし、外部キー リレーションシップの両端には Column 要素は存在しません。これは Association 要素によって完全に (両端に) 表されるためです。

列の属性

属性 Type Default 説明
名前 String なし この列がマップされるデータベース フィールドの名前。
メンバー String 名前 格納型で生成される CLR プロパティの名前。
ストレージ String _メンバー この列の値を格納するプライベート CLR バッキング フィールドの名前。 ストレージが既定の場合でも、シリアル化時に ストレージ を削除しないでください。
AccessModifier AccessModifier パブリック 作成される CLR プロパティのアクセシビリティ レベル。 有効な値は、 PublicProtectedInternalPrivate です
種類 String ("必須") 作成される CLR プロパティとバッキング フィールドの両方の型の名前。 これは、生成されたコードがコンパイルされるときに最終的にスコープ内にある限り、完全修飾名からクラスの直接名まで、何でもかまいません。
DbType String なし この列の完全なSQL Server型 (NOT NULL などの注釈を含む)。 createDatabase() の実行時に生成されるクエリを最適化し、より具体的に指定する場合は、LINQ to SQLによって使用されます。 常に DbType をシリアル化します。
IsReadOnly Boolean False IsReadOnly が設定されている場合、プロパティ セッターは作成されません。つまり、そのオブジェクトを使用してこの列の値を変更することはできません。
IsPrimaryKey Boolean False この列がテーブルの主キーに含まれることを示します。 この情報は、LINQ to SQLが正常に動作するために必要です。
IsDbGenerated Boolean False このフィールドのデータがデータベースによって生成されることを示します。 これは、主に オートナンバー 型のフィールドと計算フィールドの場合です。 これらのフィールドに値を割り当てることは意味がないため、自動的に IsReadOnly になります
CanBeNull Boolean なし 値に null 値を含めることができることを示します。 CLR で実際に null 値を使用する場合は、引き続き ClrTypeNull 許容<T> として指定する必要があります。
UpdateCheck UpdateCheck Always (少なくとも 1 つの他のメンバーに IsVersion が 設定されている場合を除き、Never) オプティミスティック コンカレンシーの競合検出中LINQ to SQLこの列を使用する必要があるかどうかを示します。 通常、 IsVersion 列が存在しない限り、すべての列は既定で参加し、それ自体が参加します。 使用できる値は、 AlwaysNever、または WhenChanged です (これは、列が独自の値が変更された場合に参加することを意味します)。
IsDiscriminator Boolean False このフィールドに、継承階層内の型を選択するために使用される識別子コードが含まれているかどうかを示します。
String なし LINQ to SQLの操作には影響しませんが、 の間に使用されます。CreateDatabase() は、計算列式を表す生の SQL 式です。
IsVersion Boolean False このフィールドが、行が変更されるたびに自動的に更新されるSQL Serverの TIMESTAMP フィールドを表していることを示します。 このフィールドを使用すると、オプティミスティック コンカレンシーの競合検出をより効率的に行うことができます。
IsDelayLoaded Boolean False この列は、オブジェクトの具体化時にすぐに読み込むのではなく、関連するプロパティに最初にアクセスする場合にのみ読み込む必要があることを示します。 これは、大きなメモ フィールドや、常に必要とされない行のバイナリ データに役立ちます。
AutoSync AutoSync If (IsDbGenerated && IsPrimaryKey) OnInsert;

Else if (IsDbGenerated) Always

Else Never

データベースによって生成された値から列を自動的に同期するかどうかを指定します。 このタグの有効な値は 、OnInsertAlwaysNever です

関連付け

この要素は、外部キーリレーションシップの両端を表します。 一対多リレーションシップの場合、これは一方の 側の EntitySet<T> と、多側の EntityRef<T> になります。 一対一リレーションシップの場合、これは両側の EntityRef<T> になります。

アソシエーションの両側に Association エントリが必要ではないことに注意してください。 この場合、プロパティはエントリを持つ側でのみ生成されます (一方向リレーションシップを形成します)。

関連付け属性

属性 種類 Default 説明
名前 String ("必須") リレーションシップの名前 (通常は外部キー制約名)。 これは技術的には省略可能ですが、同じ 2 つのテーブル間に複数のリレーションシップがある場合にあいまいさを回避するために、常にコードによって生成する必要があります。
メンバー String 名前 関連付けのこの側で生成される CLR プロパティの名前。
ストレージ String OneToMany と Not IsForeignKey の場合:

_OtherTable

その他の場合:

_TypeName(OtherTable)

この列の値を格納するプライベート CLR バッキング フィールドの名前。
AccessModifier AccessModifier パブリック 作成される CLR プロパティのアクセシビリティ レベル。 有効な値は、 PublicProtectedInternalPrivate です
Thiskey String 包含クラス内の IsIdentity プロパティ 関連付けのこの側のキーのコンマ区切りのリスト。
OtherTable String 説明を参照してください。 リレーションシップのもう一方の端にあるテーブル。 通常、これはリレーションシップ名の一致によってLINQ to SQLランタイムによって決定されますが、これは一方向の関連付けや匿名の関連付けでは不可能です。
Otherkey String 外部クラス内の主キー 関連付けの反対側にあるキーのコンマ区切りのリスト。
IsForeignKey Boolean False これがリレーションシップの "子" 側、つまり一対多の多辺であるかどうかを示します。
RelationshipType RelationshipType OneToMany この関連付けに関連するデータが 1 対 1 のデータの条件を満たしていることをユーザーがアサートしているか、一対多の一般的なケースに適合しているかを示します。 1 対 1 の場合、ユーザーは主キー ("1") 側のすべての行に対して、外部キー ("多") 側に 1 つの行しか存在することをアサートしています。 これにより、EntitySet T> ではなく"1" 側に EntityRef<T> が生成されます。< 有効な値は 、OneToOneOneToMany です
DeleteRule String なし この関連付けに削除動作を追加するために使用します。 たとえば、"CASCADE" は FK リレーションシップに "ONDELETECASCADE" を追加します。 null に設定すると、削除動作は追加されません。

機能

この要素は、ストアド プロシージャまたはデータベース関数を表します。 すべての Function ノードについて、 DataContext クラスでメソッドが生成されます。

関数の属性

属性 種類 Default 説明
名前 String ("必須") データベース内のストアド プロシージャの名前。
認証方法 String 方法 ストアド プロシージャの呼び出しを許可する、生成する CLR メソッドの名前。 Method の既定の名前には、[dbo] などがあります。Name は削除されています。
AccessModifier AccessModifier パブリック ストアド プロシージャ メソッドのアクセシビリティ レベル。 有効な値は、 PublicProtectedInternalPrivate です
HasMultipleResults Boolean 型 > 1 の数 この Function ノードで表されるストアド プロシージャが複数の結果セットを返すかどうかを指定します。 すべての結果セットは表形式の図形であり、既存の Type または列のセットにすることができます。 後者の場合は、列セットに 対して Type ノードが作成されます。
IsComposable Boolean False 関数/ストアド プロシージャをLINQ to SQLクエリで構成できるかどうかを指定します。 void を返さない DB 関数のみを構成できます。

関数Sub-Element属性

Sub-Element 要素の型 出現範囲 説明
<パラメーター> パラメーター 0-unbounded このストアド プロシージャの in パラメーターと out パラメーターを表します。
<ElementType> 種類 0-unbounded 対応するストアド プロシージャが返すことができる表形式の図形を表します。
<Return> 戻り値 0-1 この db 関数またはストアド プロシージャの返されるスカラー型。 Return が null の場合、関数は void を返します。 関数に ReturnElementType の両方を指定することはできません。

TableFunction

この要素は、テーブルの CUD オーバーライド関数を表します。 LINQ to SQL デザイナーでは、LINQ TO SQL の InsertUpdate、Delete のオーバーライド メソッドを作成でき、エンティティ プロパティ名をストアド プロシージャのパラメーター名にマッピングできます。

CUD 関数のメソッド名は固定されているため、TableFunction 要素の DBML に Method 属性はありません。 たとえば、Customer テーブルの場合、CUD メソッドの名前は InsertCustomerUpdateCustomerDeleteCustomer です。

Table 関数は表形式の図形を返すことができないため、TableFunction 要素に ElementType 属性はありません。

TableFunction 属性

属性 種類 Default 説明
名前 String ("必須") データベース内のストアド プロシージャの名前。
AccessModifier AccessModifier プライベート ストアド プロシージャ メソッドのアクセシビリティ レベル。 有効な値は、 PublicProtectedInternalPrivate です
HasMultipleResults Boolean 型 > 1 の数 この Function ノードで表されるストアド プロシージャが複数の結果セットを返すかどうかを指定します。 すべての結果セットは表形式の図形であり、既存の Type または列のセットにすることができます。 後者の場合は、列セットに 対して Type ノードが作成されます。
IsComposable Boolean False 関数/ストアド プロシージャをLINQ to SQLクエリで構成できるかどうかを指定します。 void を返さない DB 関数のみを構成できます。

TableFunction Sub-Element属性

Sub-Elements 要素型 出現範囲 説明
<パラメーター> TableFunctionParameter 0-unbounded このテーブル関数の in パラメーターと out パラメーターを表します。
<Return> TableFunctionReturn 0-1 このテーブル関数の返されるスカラー型。 Return が null の場合、関数は void を返します。

パラメーター

この要素は、ストアド プロシージャ/関数パラメーターを表します。 パラメーターは、データの送受信を行うことができます。

パラメーター属性

属性 種類 Default 説明
名前 String ("必須") ストアド プロシージャ/関数パラメーターのデータベース名。
パラメーター String 名前 メソッド パラメーターの CLR 名。
  String ("必須") メソッド パラメーターの CLR 名。
DbType String なし ストアド プロシージャ/関数パラメーターの DB 型。
Direction ParameterDirection 含まれる パラメーターが流れる方向。 InOutInOut のいずれかを指定できます。

戻り値

この要素は、ストアド プロシージャ/関数の戻り値の型を表します。

戻り値の属性

属性 種類 Default 説明
種類 String ("必須") ストアド プロシージャ/関数の結果の CLR 型。
DbType String なし ストアド プロシージャ/関数の結果の DB 型。

TableFunctionParameter

この要素は、CUD 関数のパラメーターを表します。 パラメーターは、データの送受信を行うことができます。すべてのパラメーターは、この CUD 関数が属する Table 列にマップされます。 パラメーターがマップされる列から型情報を取得できるため、この要素には Type 属性または DbType 属性はありません。

TableFunctionParameter 属性

属性 種類 Default 説明
名前 String ("必須") CUD 関数パラメーターのデータベース名。
パラメーター String 名前 メソッド パラメーターの CLR 名。
String 名前 このパラメーターのマッピング対象の列名。
Direction ParameterDirection 含まれる パラメーターが流れる方向。 InOut、または InOut のいずれかを指定できます。
バージョン バージョン Current PropertyName が特定の列の現在のバージョンまたは元のバージョンを参照しているかどうか。 更新のオーバーライド中にのみ適用されます。 [現在] または [元] を指定できます。

TableFunctionReturn

この要素は、CUD 関数の戻り値の型を表します。 実際には、CUD 関数の結果にマップされる列名のみが含まれています。 戻り値の型情報は、 列から取得できます。

TableFunctionReturn 属性

Attrobite 種類 Default 説明
String なし 戻り値がマッピングされる列名。

Connection

この要素は、既定のデータベース 接続 パラメーターを表します。 これにより、データベースへの接続方法を既に認識している DataContext 型の既定のコンストラクターを作成できます。

可能な既定の接続には、直接 ConnectionString を使用する接続と App.Settings から読み取る接続の 2 種類があります。

接続属性

属性 種類 Default 説明
UseApplicationSettings Boolean False App.Settings ファイルを使用するか、直接 ConnectionString からアプリケーション設定を取得するかを決定します。
ConnectionString String なし SQL データ プロバイダーに送信する接続文字列。
SettingsObjectName String 設定 プロパティを取得する App.Settings オブジェクト
SettingsPropertyName String ConnectionString ConnectionString を含む App.Settings プロパティ。

多層エンティティ

2 層アプリケーションでは、1 つの DataContext が クエリと更新を処理します。 ただし、追加の層を持つアプリケーションの場合、クエリと更新に個別の DataContext インスタンスを使用する必要があることがよくあります。 たとえば、ASP.NET アプリケーションの場合、クエリと更新は Web サーバーへの個別の要求に対して実行されます。 そのため、複数の要求で同じ DataContext インスタンスを使用することは実用的ではありません。 このような場合、 DataContext インスタンスは、取得していないオブジェクトを更新できる必要があります。 LINQ to SQLでの多層エンティティのサポートは、Attach() メソッドを使用してこのような機能を提供します。

別の DataContext インスタンスを使用して Customer オブジェクトを変更する方法の例を次に示します。

C#

// Customer entity changed on another tier – for example, through a browser
// Back on the mid-tier, a new context needs to be used
Northwind db2 = new Northwind(…);

// Create a new entity for applying changes
Customer C2 = new Customer();
C2.CustomerID ="NewCustID";

// Set other properties needed for optimistic concurrency check
C2.CompanyName = "New Company Name Co.";

...

// Tell LINQ to SQL to track this object for an update; that is, not for insertion
db2.Customers.Attach(C2);

// Now apply the changes
C2.ContactName = "Mary Anders";

// DataContext now knows how to update the customer
db2.SubmitChanges();

Visual Basic

' Customer entity changed on another tier – for example, through a browser
' Back on the mid-tier, a new context needs to be used
Dim db2 As Northwind = New Northwind(…)

' Create a new entity for applying changes
Dim C2 As New Customer()
C2.CustomerID =”NewCustID”

' Set other properties needed for optimistic concurrency check
C2.CompanyName = ”New Company Name Co.”

...

' Tell LINQ to SQL to track this object for an update; that is, not for insertion
db2.Customers.Attach(C2)

' Now apply the changes
C2.ContactName = "Mary Anders"

' DataContext now knows how to update the customer
db2.SubmitChanges()

多層アプリケーションでは、エンティティ全体が、わかりやすく、相互運用性、またはプライバシーのために階層間で送信されないことがよくあります。 たとえば、サプライヤーは、中間層で使用される Order エンティティとは異なる Web サービスのデータ コントラクトを定義できます。 同様に、Web ページには Employee エンティティのメンバーのサブセットのみが表示される場合があります。 そのため、多層サポートは、このようなケースに対応するように設計されています。 Attach() を呼び出す前に、次のカテゴリの 1 つ以上に属するメンバーのみを階層間で転送し、 を設定する必要があります。

  1. エンティティの ID の一部であるメンバー。
  2. 変更されたメンバー。
  3. オプティミスティック コンカレンシー チェックに参加するメンバー。

オプティミスティック コンカレンシー チェックにタイムスタンプまたはバージョン番号列を使用する場合は、Attach() を呼び出す前に、対応するメンバーを設定する必要があります。 Attach() を呼び出す前に、他のメンバーの値を設定する必要はありません。 LINQ to SQLは、オプティミスティック コンカレンシー チェックで最小限の更新を使用します。つまり、オプティミスティック コンカレンシーに対して設定またはチェックされていないメンバーは無視されます。

オプティミスティック コンカレンシー チェックに必要な元の値は、LINQ to SQL API の範囲外のさまざまなメカニズムを使用して保持できます。 ASP.NET アプリケーションでは、ビューステート (またはビューステートを使用するコントロール) を使用できます。 Web サービスでは、更新メソッドに DataContract を使用して、元の値が更新処理に使用できることを確認できます。 相互運用性と一般化のために、LINQ to SQLは、階層間で交換されるデータの形状や、元の値のラウンドトリップに使用されるメカニズムを決定しません。

挿入と削除のエンティティには 、Attach() メソッドは必要ありません。 2 層アプリケーション (Table.Add() Table.Remove() ) に使用されるメソッドは、挿入と削除に使用できます。 2 層更新の場合と同様に、ユーザーは外部キー制約を処理する必要があります。 データベースに外部キー制約があり、注文のある顧客の削除を妨げている場合、注文を処理せずに注文を持つ顧客を削除することはできません。

LINQ to SQLは、更新のエンティティの添付ファイルも推移的に処理します。 ユーザーは基本的に必要に応じて更新前のオブジェクト グラフを作成し、 Attach() を呼び出します。 その後、次に示すように、アタッチされたグラフですべての変更を "再生" して、必要な更新を実行できます。

C#

Northwind db1 = new Northwind(…);
// Assume Customer c1 and related Orders o1, o2 are retrieved

// Back on the mid-tier, a new context needs to be used
Northwind db2 = new Northwind(…);

// Create new entities for applying changes
Customer c2 = new Customer();
c2.CustomerID = c.CustomerID;
Order o2 = new Order();
o2.OrderID = ...;

c2.Orders.Add(o2);

// Add other related objects needed for updates

// Set properties needed for optimistic concurrency check
...
// Order o1 to be deleted
Order o1 = new Order();
o1.OrderID = ...;

// Tell LINQ to SQL to track the graph transitively
db2.Customers.Attach(c2);
// Now "replay" all the changes

// Updates
c2.ContactName = ...;
o2.ShipAddress = ...;

// New object for insertion
Order o3 = new Order();
o3.OrderID = ...;
c2.Orders.Add(o3);

// Remove order o1
db2.Orders.Remove(o1);

// DataContext now knows how to do update/insert/delete
db2.SubmitChanges();

Visual Basic

Dim db1 As Northwind = New Northwind(…)
' Assume Customer c1 and related Orders o1, o2 are retrieved

' Back on the mid-tier, a new context needs to be used
Dim db2 As Northwind = New Northwind(…)

' Create new entities for applying changes
Customer c2 = new Customer()
c2.CustomerID = c.CustomerID
Dim o2 As Order = New Order()
o2.OrderID = ...

c2.Orders.Add(o2)

' Add other related objects needed for updates

' Set properties needed for optimistic concurrency check
...
' Order o1 to be deleted
Dim o1 As Order = New Order()
o1.OrderID = ...

' Tell LINQ to SQL to track the graph transitively
db2.Customers.Attach(c2)
' Now "replay" all the changes

' Updates
c2.ContactName = ...
o2.ShipAddress = ...

' New object for insertion
Dim o3 As Order = New Order()
o3.OrderID = ...
c2.Orders.Add(o3)

' Remove order o1
db2.Orders.Remove(o1)

' DataContext now knows how to do update/insert/delete
db2.SubmitChanges()

外部マップ

属性ベースのマッピングに加えて、LINQ to SQLでは外部マッピングもサポートされます。 外部マッピングの最も一般的な形式は XML ファイルです。 マッピング ファイルを使用すると、コードからマッピングを分離することが望ましい追加のシナリオが可能になります。

DataContext には、MappingSource を指定するための追加のコンストラクターが 用意されていますMappingSource の 1 つの形式は、XML マッピング ファイルから構築できる XmlMappingSource です。

マッピング ファイルを使用する方法の例を次に示します。

C#

String path = @"C:\Mapping\NorthwindMapping.xml";
XmlMappingSource prodMapping = 
   XmlMappingSource.FromXml(File.ReadAllText(path));
Northwind db = new Northwind(
   @"Server=.\SQLExpress;Database=c:\Northwind\Northwnd.mdf",
   prodMapping
   );

Visual Basic

Dim path As String = "C:\Mapping\NorthwindMapping.xml"
Dim prodMapping As XmlMappingSource = _
   XmlMappingSource.FromXml(File.ReadAllText(path))
Dim db As Northwind = New Northwind( _
   "Server=.\SQLExpress;Database=c:\Northwind\Northwnd.mdf", _
   prodMapping   )

Product クラスのマッピングを示すマッピング ファイルの対応するスニペットを次に示します。 Northwind データベースの Products テーブルにマップされた名前空間マッピングProduct クラスが表示されます。 要素と属性は、属性の名前とパラメーターと一致します。

<?xml version="1.0" encoding="utf-8"?>
<Database xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="Northwind"
   ProviderType="System.Data.Linq.SqlClient.Sql2005Provider">
   <Table Name="Products">
      <Type Name="Mappings.FunctionMapping.Product">
         <Column Name="ProductID" Member="ProductID" Storage="_ProductID"
            DbType="Int NOT NULL IDENTITY" IsPrimaryKey="True"
            IsDBGenerated="True" AutoSync="OnInsert" />
         <Column Name="ProductName" Member="ProductName" Storage="_ProductName"
            DbType="NVarChar(40) NOT NULL" CanBeNull="False" />
         <Column Name="SupplierID" Member="SupplierID" Storage="_SupplierID"
            DbType="Int" />
         <Column Name="CategoryID" Member="CategoryID" Storage="_CategoryID"
            DbType="Int" />
         <Column Name="QuantityPerUnit" Member="QuantityPerUnit"
            Storage="_QuantityPerUnit" DbType="NVarChar(20)" />
         <Column Name="UnitPrice" Member="UnitPrice" Storage="_UnitPrice"
            DbType="Money" />
         <Column Name="UnitsInStock" Member="UnitsInStock" Storage="_UnitsInStock"
            DbType="SmallInt" />
         <Column Name="UnitsOnOrder" Member="UnitsOnOrder" Storage="_UnitsOnOrder"
            DbType="SmallInt" />
         <Column Name="ReorderLevel" Member="ReorderLevel" Storage="_ReorderLevel"
            DbType="SmallInt" />
         <Column Name="Discontinued" Member="Discontinued" Storage="_Discontinued"
            DbType="Bit NOT NULL" />
         <Association Name="FK_Order_Details_Products" Member="OrderDetails"
            Storage="_OrderDetails" ThisKey="ProductID" OtherTable="Order Details"
            OtherKey="ProductID" DeleteRule="NO ACTION" />
         <Association Name="FK_Products_Categories" Member="Category"
            Storage="_Category" ThisKey="CategoryID" OtherTable="Categories"
            OtherKey="CategoryID" IsForeignKey="True" />
         <Association Name="FK_Products_Suppliers" Member="Supplier"
            Storage="_Supplier" ThisKey="SupplierID" OtherTable="Suppliers"
            OtherKey="SupplierID" IsForeignKey="True" />
      </Type>
   </Table>

</Database>

NET Framework 関数のサポートと注意事項

次の段落では、LINQ to SQL型のサポートに関する基本情報と、.NET Frameworkとの違いについて説明します。

プリミティブ型

Implemented

  • 算術演算子と比較演算子
  • シフト演算子: << および >>
  • char と numeric の間の変換は UNICODE/NCHAR によって行われます。それ以外の場合は SQL の CONVERT が使用されます。

未実装

  • <「」と入力します>。解析
  • 列挙型を使用して、テーブル内の整数と文字列にマップできます。 後者の場合、 Parse メソッドと ToString() メソッドが使用されます。

.NET との違い

  • 倍精度浮動小数点型の ToString の出力では、SQL では CONVERT(NVARCHAR(30), @x, , 2) が使用され、常に 16 桁の数字と "Scientific Notation" が使用されます。たとえば、0 の場合は "0.00000000000000e+000" であるため、 と同じ文字列は指定されません。NET の Convert.ToString()

System.String

Implemented

  • 非静的メソッド:

    • LengthSubstringContainsStartsWithEndsWithIndexOfInsertRemoveReplaceTrimToLowerToUpperLastIndexOfPadRightPadLeftEqualsCompareTo。 以下で詳しく説明するように、 StringComparison パラメーターを受け取る場合を除き、すべてのシグネチャがサポートされます。
  • 静的メソッド:

       Concat(...)               all signatures
       Compare(String, String)
       String (indexer) 
       Equals(String, String)
    
  • コンス トラクター:

        String(Char, Int32)
    
  • 演算子:

      +, ==, != (+, =, and <> in Visual Basic)
    

未実装

  • char の配列を取得または生成するメソッド。

  • CultureInfo/StringComparison/IFormatProvider を受け取るメソッド。

  • 静的 (Visual Basic では共有):

       Copy(String str)
       Compare(String, String, Boolean)
       Compare(String, String, StringComparison)
       Compare(String, String, Boolean, CultureInfo) 
       Compare(String, Int32, String, Int32, Int32)
       Compare(String, Int32, String, Int32, Int32,   Boolean)
       Compare(String, Int32, String, Int32, Int32, StringComparison)
       Compare(String, Int32, String, Int32, Int32, Boolean, CultureInfo)
       CompareOrdinal(String, String)
       CompareOrdinal(String, Int32, String, Int32, Int32)
       Join(String, ArrayOf String [,...]) All Join version with first three args
    
  • インスタンス:

       ToUpperInvariant()
       Format(String, Object)      + overloads
       IndexOf(String, Int32, StringComparison)
       IndexOfAny(ArrayOf Char)
       Normalize()
       Normalize(NormalizationForm)
       IsNormalized()
       Split(...)
       StartsWith(String, StringComparison)
       ToCharArray()
       ToUpper(CultureInfo)
       TrimEnd(ParamArray Char)
       TrimStart(ParamArray Char)
    

.NET との制限/相違点

SQL では、照合順序を使用して、文字列の等価性と順序付けを決定します。 これらは、SQL Server インスタンス、データベース、テーブル列、または式で指定できます。

これまでに実装された関数の翻訳では、照合順序が変更されたり、変換された式に異なる照合順序が指定されたりすることはありません。 そのため、既定の照合順序で大文字と小文字が区別されない場合、 CompareToIndexOf などの関数は、(大文字と小文字を区別する) .NET 関数とは異なる結果を得ることができます。

StartsWith(str)/EndsWith(str) メソッドは、引数 str が定数またはクライアントで評価される式であることを前提としています。 つまり、現在、 str に列を使用することはできません。

System.Math

実装された静的メソッド

  • すべての署名:
    • AbsAcosAsinAtanAtan2BigMulCeilingCosCoshExpFloorLogLog、Log10MaxMinPowSignSinhSqrtTanTanhTruncate

未実装

  • IEEERemainder
  • DivRem には out パラメーターがあるため、式で使用することはできません。 定数 Math.PIMath.E はクライアントで評価されるため、翻訳は必要ありません。

.NET との違い

.NET 関数 Math.Round の変換は、SQL 関数 ROUND です。 変換は、 MidpointRounding 列挙値を示すオーバーロードが指定されている場合にのみサポートされます。 MidpointRounding.AwayFromZero は SQL 動作であり、MidpointRounding.ToEven は CLR の動作を示します。

System.Convert

Implemented

  • Type1>(<Type2> x) のフォームの<メソッド (Type1、Type2 は次のいずれか) です。
    • boolbytecharDateTimedecimaldoublefloatInt16Int32Int64、または string
  • 動作はキャストと同じです。
    • ToString(Double) には、完全な精度を取得するための特別なコードがあります。
    • 変換 Int32/Char の場合、LINQ to SQLは SQL の UNICODE/NCHAR 関数を使用します。
    • それ以外の場合、変換は CONVERT です

実装されていません

  • ToSByteUInt163264: これらの型は SQL に存在しません。

    To<integer type>(String, Int32) 
    ToString(..., Int32)       any overload ending with an Int32 toBase
    IsDBNull(Object)
    GetTypeCode(Object)
    ChangeType(...)
    
  • IFormatProvider パラメーターを持つバージョン。

  • 配列を含むメソッド (To/FromBase64CharArray, To/FromBase64String)。

System.TimeSpan

Implemented

  • コンストラクター:

       TimeSpan(Long)
       TimeSpan (year, month, day)
       TimeSpan (year, month, day, hour, minutes, seconds)
       TimeSpan (year, month, day, hour, minutes, seconds, milliseconds)
    
  • 演算子:

       Comparison operators: <,==, and so on in C#; <, =, and so on in Visual Basic
    
       +, -
    
  • Static (Visual Basic では Shared) メソッド:

       Compare(t1,t2)
    
  • 非静的 (インスタンス) メソッド/プロパティ:

       Ticks, Milliseconds, Seconds, Hours, Days
       TotalMilliseconds, TotalSeconds, TotalMinutes, TotalHours, TotalDays,
       Equals, CompareTo(TimeSpan)
       Add(TimeSpan), Subtract(TimeSpan)
       Duration() [= ABS], Negate()
    

未実装

   ToString()
   TimeSpan FromDay(Double), FromHours,   all From Variants
   TimeSpan Parse(String)

System.DateTime

Implemented

  • コンストラクター:

       DateTime(year, month, day)
       DateTime(year, month, day, hour, minutes, seconds)
       DateTime(year, month, day, hour, minutes, seconds, milliseconds)
    
  • 演算子:

       Comparisons
       DateTime – DateTime (gives TimeSpan)
       DateTime + TimeSpan (gives DateTime)
       DateTime – TimeSpan (gives DateTime)
    
  • 静的 (Shared) メソッド:

       Add(TimeSpan), AddTicks(Long),
       AddDays/Hours/Milliseconds/Minutes (Double)
       AddMonths/Years(Int32)
       Equals
    
  • 非静的 (インスタンス) メソッド/プロパティ:

       Day, Month, Year, Hour, Minute, Second, Millisecond, DayOfWeek
       CompareTo(DateTime)
       TimeOfDay()
       Equals
       ToString()
    

.NET との違い

SQL の datetime 値は .000、.003、または .007 秒に丸められます。そのため、.NET の値よりも精度が低くなります。

SQL の datetime の範囲は、1753 年 1 月 1 日から始まります。

SQL には TimeSpan の組み込み型がありません。 32 ビット整数を返すさまざまな DATEDIFF メソッドを使用します。 1 つは DATEDIFF(DAY,...)です。これは日数を示します。もう 1 つは DATEDIFF(MILLISECOND,...)です。これはミリ秒の数を示します。 DateTimes が 24 日を超えると、エラーが発生します。 これに対し、.NET は 64 ビット整数を使用し、 TimeSpans を ティック単位で測定します。

SQL の .NET セマンティクスにできるだけ近づくために、LINQ to SQLは TimeSpans を 64 ビット整数に変換し、上記の 2 つの DATEDIFF メソッドを使用して 2 つの日付間のティック数を計算します。

Datetime UtcNow は、クエリが変換されるときにクライアントで評価されます (データベース データを含まない式と同様)。

未実装

   IsDaylightSavingTime()
   IsLeapYear(Int32)
   DaysInMonth(Int32, Int32)
   ToBinary()
   ToFileTime()
   ToFileTimeUtc()
   ToLongDateString()
   ToLongTimeString()
   ToOADate()
   ToShortDateString()
   ToShortTimeString()
   ToUniversalTime()
   FromBinary(Long), FileTime, FileTimeUtc, OADate
   GetDateTimeFormats(...)
   constructor DateTime(Long)
   Parse(String)
   DayOfYear

デバッグのサポート

DataContext には、クエリと変更処理のために生成された SQL を取得するためのメソッドとプロパティが用意されています。 これらのメソッドは、LINQ to SQL機能を理解したり、特定の問題をデバッグしたりするのに役立ちます。

生成された SQL を取得するための DataContext メソッド

メンバー 目的
ログ SQL を実行する前に出力します。 クエリ、挿入、更新、削除の各コマンドについて説明します。 用途:

C#

db.Log = Console.Out;

Visual Basic

db.Log = Console.Out

GetQueryText(query) クエリを実行せずにクエリのクエリ テキストを返します。 用途:

C#

Console.WriteLine(db.GetQueryText(db.Customers));

Visual Basic

Console.WriteLine(db.GetQueryTest(db.Customers))

GetChangeText() 実行せずに挿入、更新、削除を行う SQL コマンドのテキストを返します。 用途:

C#

Console.WriteLine(db.GetChangeText());

Visual Basic

Console.WriteLine(db.GetChangeText())