LINQ to SQL: 관계형 데이터에 대한 .NET LINQ(Language-Integrated Query)

 

Dinesh Kulkarni, Luca 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와 같은 디자인 타임 기능을 활용할 수 없는 언어는 불투명합니다.

물론, 차이는 그보다 훨씬 더 깊어집니다. 정보 표시 방법(데이터 모델)은 둘 간에 매우 다릅니다. 최신 프로그래밍 언어는 개체 형식으로 정보를 정의합니다. 관계형 데이터베이스는 행을 사용합니다. 각 instance 물리적으로 다른 개체와 다르기 때문에 개체에는 고유한 ID가 있습니다. 행은 기본 키 값으로 식별됩니다. 개체에는 인스턴스를 식별하고 함께 연결하는 참조가 있습니다. 행은 외세 키를 사용하여 관련 행을 느슨하게 연결해야 하는 의도적으로 고유하게 남아 있습니다. 개체는 다른 개체에서 계속 참조되는 한 존재하며 독립형입니다. 행은 테이블의 요소로 존재하며 제거되는 즉시 사라집니다.

이러한 격차를 해소할 것으로 예상되는 애플리케이션은 빌드 및 유지 관리가 어려운 것은 당연합니다. 그것은 확실히 한쪽 또는 다른 제거 방정식을 단순화 할 것이다. 그러나 관계형 데이터베이스는 장기 스토리지 및 쿼리 처리를 위한 중요한 인프라를 제공하며, 최신 프로그래밍 언어는 민첩한 개발과 풍부한 계산에 없어서는 안 됩니다.

지금까지는 애플리케이션 개발자가 각 애플리케이션에서 이 불일치를 개별적으로 resolve 것이 중요했습니다. 지금까지 가장 좋은 솔루션은 애플리케이션 도메인별 개체 모델과 데이터베이스의 테이블 형식 표현 간에 정보를 제공하는 정교한 데이터베이스 추상화 계층으로, 각 방법마다 데이터를 재구성하고 다시 포맷하는 것이었습니다. 그러나 실제 데이터 원본을 가리면 이러한 솔루션은 관계형 데이터베이스의 가장 매력적인 기능을 제거하게 됩니다. 데이터를 쿼리할 수 있는 기능입니다.

Visual Studio Code 이름 "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 샘플 데이터베이스의 고객 테이블과 연결합니다. 이렇게 하려면 클래스 선언의 맨 위에 사용자 지정 특성만 적용해야 합니다. 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 특성에는 필드와 데이터베이스 열 간의 정확한 매핑을 사용자 지정하는 데 사용할 수 있는 다양한 속성이 있습니다. 참고 속성 중 하나는 Id 속성입니다. 데이터베이스 열이 테이블의 기본 키의 일부임을 LINQ to SQL 알려줍니다.

Table 특성과 마찬가지로 Column 특성이 필드 또는 속성 선언에서 추론할 수 있는 것과 다른 경우에만 Column 특성에 정보를 제공해야 합니다. 이 예제에서는 CustomerID 필드가 테이블의 기본 키의 일부임을 LINQ to SQL 알려야 하지만 정확한 이름이나 형식을 지정할 필요는 없습니다.

열로 선언된 필드 및 속성만 데이터베이스에 유지되거나 데이터베이스에서 검색됩니다. 다른 부분은 애플리케이션 논리의 일시적인 부분으로 간주됩니다.

The DataContext

DataContext는 데이터베이스에서 개체를 검색하고 변경 내용을 다시 제출하는 기본 도관입니다. ADO.NET Connection을 사용하는 것과 동일한 방식으로 사용합니다. 실제로 DataContext 는 사용자가 제공하는 연결 또는 연결 문자열을 사용하여 초기화됩니다. DataContext의 목적은 개체에 대한 요청을 데이터베이스에 대해 수행된 SQL 쿼리로 변환한 다음 결과에서 개체를 어셈블하는 것입니다. DataContext를 사용하면 위치 및 선택과 같은 표준 쿼리 연산자와 동일한 연산자 패턴을 구현하여 언어 통합 쿼리를 사용할 수 있습니다.

예를 들어 DataContext 를 사용하여 다음과 같이 런던인 고객 개체를 검색할 수 있습니다.

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

각 데이터베이스 테이블은 해당 엔터티 클래스를 사용하여 GetTable() 메서드를 통해 액세스할 수 있는 Table 컬렉션으로 표시됩니다. 기본 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

그런 다음 런던의 고객에 대한 쿼리를 다음과 같이 더 간단하게 표현할 수 있습니다.

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 클래스를 계속 사용합니다.

관계 정의

관계형 데이터베이스의 관계는 일반적으로 다른 테이블의 기본 키를 참조하는 외래 키 값으로 모델링됩니다. 둘 사이를 탐색하려면 관계형 조인 작업을 사용하여 두 테이블을 명시적으로 함께 가져와야 합니다. 반면에 개체는 속성 참조 또는 "점" 표기법을 사용하여 탐색된 참조 컬렉션을 사용하여 서로를 참조합니다. 탐색할 때마다 명시적 조인 조건을 기억할 필요가 없으므로 점 지정은 조인보다 간단합니다.

항상 동일한 데이터 관계의 경우 엔터티 클래스에서 속성 참조로 인코딩하는 것이 매우 편리해집니다. 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 형식입니다. 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 경우에 필수적입니다. 스토리지 속성을 지정하지 않으면 공용 접근자가 대신 사용됩니다. 특성과 함께 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 속성을 사용하여 고객과 주문 간의 교차 제품을 형성하여 새로운 고객주문 쌍 시퀀스를 생성합니다.

반대로 할 수도 있습니다.

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 샘플 데이터베이스의 메타데이터에서 도구로 생성된 CustomerOrder 클래스를 사용합니다. 클래스 정의는 간결하게 표시되지 않았습니다.

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

이 instance 실제 q 유형은 IQueryable<Customer>입니다. 애플리케이션이 실제로 실행되는 쿼리의 내용을 열거하려고 할 때까지는 그렇지 않습니다. 이 예제에서는 foreach 문으로 인해 실행이 발생합니다.

IQueryable 개체는 ADO.NET 명령 개체와 비슷합니다. 쿼리가 실행되었음을 의미하지는 않습니다. 명령 개체는 쿼리를 설명하는 문자열을 보유합니다. 마찬가지로 IQueryable 개체는 Expression이라는 데이터 구조로 인코딩된 쿼리에 대한 설명을 포함합니다. 명령 개체에는 실행을 유발하는 ExecuteReader() 메서드가 있으며 결과를 DataReader로 반환합니다. IQueryable 개체에는 실행을 유발하는 GetEnumerator() 메서드가 있으며 결과를 IEnumerator<Customer로 반환합니다>.

따라서 쿼리가 두 번 열거되면 두 번 실행됩니다.

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 

지연 실행의 한 가지 이점은 생성이 완료될 때만 실행이 수행되어 쿼리를 조각화하여 생성할 수 있다는 것입니다. 쿼리의 일부를 작성하여 지역 변수에 할당한 다음 나중에 더 많은 연산자를 계속 적용할 수 있습니다.

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가 있습니다. 두 변수가 동일한 개체를 참조하는 경우 실제로 동일한 개체 instance 참조합니다. 이 때문에 한 변수를 통해 경로를 통해 변경된 내용이 다른 변수를 통해 즉시 표시됩니다. 관계형 데이터베이스 테이블의 행에는 고유한 ID가 없습니다. 그러나 기본 키가 있고 기본 키가 고유할 수 있습니다. 즉, 두 행이 동일한 키를 공유할 수 없습니다. 그러나 데이터베이스 테이블의 내용만 제한됩니다. 따라서 원격 명령을 통해서만 데이터와 상호 작용하는 한 거의 동일한 것에 해당합니다.

그러나 이 경우는 거의 없습니다. 대부분의 경우 데이터는 데이터베이스에서 가져와 애플리케이션이 조작하는 다른 계층으로 가져옵니다. 분명히 LINQ to SQL 지원하도록 설계된 모델입니다. 데이터를 데이터베이스에서 행으로 가져오는 경우 동일한 데이터를 나타내는 두 행이 실제로 동일한 행 인스턴스에 해당할 것으로 예상할 수 없습니다. 특정 고객을 두 번 쿼리하면 각각 동일한 정보를 포함하는 두 개의 데이터 행이 표시됩니다.

그러나 개체를 사용하면 매우 다른 것을 기대할 수 있습니다. DataContext에 동일한 정보를 다시 요청하면 실제로 동일한 개체 instance 다시 제공할 것으로 예상합니다. 개체는 애플리케이션에 특별한 의미가 있고 일반 개체처럼 동작할 것으로 예상하기 때문에 이를 예상합니다. 계층 구조 또는 그래프로 디자인했으며, 동일한 항목을 두 번 요청했기 때문에 복제된 인스턴스의 무리 없이 이러한 인스턴스를 검색할 것으로 예상합니다.

이 때문에 DataContext 는 개체 ID를 관리합니다. 데이터베이스에서 새 행을 검색할 때마다 기본 키로 ID 테이블에 기록되고 새 개체가 만들어집니다. 동일한 행을 다시 검색할 때마다 instance 원래 개체가 애플리케이션에 다시 전달됩니다. 이러한 방식으로 DataContext 는 ID(키)의 데이터베이스 개념을 언어 개념(인스턴스)으로 변환합니다. 애플리케이션은 개체가 처음 검색된 상태에서만 볼 수 있습니다. 새 데이터(다른 경우)가 throw됩니다.

애플리케이션이 데이터를 버리는 이유는 무엇인가요? 이는 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

물론 교차 제품을 형성하고 데이터의 모든 상대 비트를 하나의 큰 프로젝션으로 검색하여 쿼리에서 고객과 주문을 함께 조인하는 방법을 항상 찾을 수 있습니다. 그러나 결과는 엔터티가 아닙니다. 엔터티는 수정할 수 있는 ID를 가진 개체이지만 결과는 변경 및 유지할 수 없는 프로젝션입니다. 더 나쁜 것은 각 고객이 평면화된 조인 출력에서 각 주문에 대해 반복할 때 엄청난 양의 중복 데이터를 검색하는 것입니다.

실제로 필요한 것은 일련의 관련 개체를 동시에 검색하는 방법입니다. 그래프의 설명된 부분이므로 의도한 용도에 필요한 것보다 더 이상 또는 더 적게 검색하지 않습니다.

LINQ to SQL 이러한 이유로 개체 모델의 영역을 즉시 로드하도록 요청할 수 있습니다. DataContext에 대한 DataShape 사양을 허용하여 이 작업을 수행합니다. 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

이전 쿼리에서는 쿼리가 실행될 때 런던에 거주하는 모든 고객에 대한 모든 주문이 검색되므로 Customer 개체의 Orders 속성에 대한 연속 액세스가 데이터베이스 쿼리를 트리거하지 않습니다.

DataShape 클래스를 사용하여 관계 탐색에 적용되는 하위 쿼리를 지정할 수도 있습니다. 예를 들어 오늘 배송된 주문만 검색하려는 경우 다음과 같이 DataShape에서 AssociateWith 메서드를 사용할 수 있습니다.

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 문은 이러한 주문이 데이터베이스에서 검색되었기 때문에 오늘 배송된 주문 바로 위에 반복됩니다.

DataShape 클래스에 대한 두 가지 사실을 알아두는 것이 중요합니다.

  1. DataShapeDataContext에 할당한 후에는 DataShape를 수정할 수 없습니다. 이러한 DataShape에 대한 LoadWith 또는 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

위의 쿼리는 특정 고객과 동일한 도시에 있지 않은 공급업체를 제거합니다. 그러나 임시 관계에서 엔터티 중 하나를 제거하지 않으려는 경우가 있습니다. 다음 쿼리는 각 공급자에 대한 고객 그룹이 있는 모든 공급자를 나열합니다. 특정 공급업체에 동일한 도시에 고객이 없는 경우 결과는 해당 공급업체에 해당하는 고객의 빈 컬렉션입니다. 결과는 평평하지 않습니다. 각 공급자에는 연결된 컬렉션이 있습니다. 효과적으로 그룹 조인을 제공합니다. 두 시퀀스와 두 번째 시퀀스의 그룹 요소를 첫 번째 시퀀스의 요소로 조인합니다.

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

그룹 조인의 결과를 평면화할 수도 있습니다. 공급업체와 고객 간의 그룹 조인을 평면화한 결과는 해당 도시에 여러 고객이 있는 공급업체에 대한 여러 항목으로, 고객당 하나씩입니다. 빈 컬렉션은 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

기본 조인 연산자의 서명은 표준 쿼리 연산자 문서에 정의되어 있습니다. 동등 조인만 지원되며 호의 두 피연산자는 동일한 형식이어야 합니다.

프로젝션

지금까지 데이터베이스 테이블과 직접 연결된 개체인 엔터티를 검색하기 위한 쿼리만 살펴보았습니다. 우리는 이것을 제한할 필요가 없습니다. 쿼리 언어의 매력은 원하는 형식으로 정보를 검색할 수 있다는 것입니다. 이렇게 하면 자동 변경 내용 추적 또는 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

이 쿼리는 고객 및 주문 개체 쌍의 시퀀스를 생성합니다.

쿼리의 모든 단계에서 프로젝션을 만들 수도 있습니다. 데이터를 새로 생성된 개체에 프로젝터한 다음 후속 쿼리 작업에서 해당 개체의 멤버를 참조할 수 있습니다.

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

쿼리를 순수 관계형 SQL 로컬로 정의된 개체 형식으로 변환하려는 LINQ to SQL 서버에서 실제로 생성할 수 없기 때문입니다. 데이터베이스에서 데이터를 다시 검색할 때까지 모든 개체 생성이 실제로 연기됩니다. 실제 생성자 대신 생성된 SQL은 일반 SQL 열 프로젝션을 사용합니다. 쿼리 번역기가 생성자 호출 중에 발생하는 작업을 이해할 수 없으므로 MyType이름 필드에 대한 의미를 설정할 수 없습니다.

대신 항상 개체 이니셜라이저를 사용하여 프로젝션을 인코딩하는 것이 좋습니다.

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로의 알려진 변환이 없는 메서드 호출은 쿼리의 일부로 사용할 수 없습니다. 이 규칙의 한 가지 예외는 쿼리 변수에 종속된 인수가 없는 메서드 호출입니다. 이러한 쿼리는 번역된 쿼리의 일부로 간주되지 않으며 대신 매개 변수로 처리됩니다.

여전히 정교한 프로젝션(변환)을 구현하려면 로컬 절차 논리가 필요할 수 있습니다. 최종 프로젝션에서 고유한 로컬 메서드를 사용하려면 두 번 프로젝션해야 합니다. 첫 번째 프로젝션은 참조해야 하는 모든 데이터 값을 추출하고 두 번째 프로젝션은 변환을 수행합니다. 이러한 두 프로젝션 사이에는 해당 시점에서 처리를 LINQ to SQL 쿼리에서 로컬로 실행된 쿼리로 이동하는 AsEnumerable() 연산자에 대한 호출이 있습니다.

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)}

참고 ToList() 및 ToArray()와 달리 AsEnumerable() 연산자는 쿼리를 실행하지 않습니다. 여전히 지연됩니다. AsEnumerable() 연산자는 쿼리의 정적 입력만 변경하여 Visual Basic의 IQueryable<T>(IQueryable(ofT))를 Visual Basic의 IEnumerable<T> (IEnumerable(ofT))로 전환하여 컴파일러가 나머지 쿼리를 로컬로 실행된 것으로 처리하게 합니다.

컴파일된 쿼리

많은 애플리케이션에서 구조적으로 유사한 쿼리를 여러 번 실행하는 것이 일반적입니다. 이러한 경우 쿼리를 한 번 컴파일하고 다른 매개 변수를 사용하여 애플리케이션에서 여러 번 실행하여 성능을 높일 수 있습니다. 이 결과는 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 여러 부분에서 어셈블된 경우에도 전체 쿼리를 검사할 수 있습니다.

관계형 데이터베이스 서버가 실제로 IL을 실행하지 않으므로(SQL Server 2005의 CLR 통합 제외) 쿼리는 IL로 서버에 전송되지 않습니다. 실제로 텍스트 형식의 매개 변수가 있는 SQL 쿼리로 전송됩니다.

물론 SQL(CLR 통합을 사용하는 T-SQL)조차도 프로그램에서 로컬로 사용할 수 있는 다양한 메서드를 실행할 수 없습니다. 따라서 작성하는 쿼리는 SQL 환경 내에서 사용할 수 있는 동등한 작업 및 함수로 변환되어야 합니다.

.Net Framework 기본 제공 형식의 대부분의 메서드 및 연산자는 SQL로 직접 변환됩니다. 일부는 사용 가능한 함수에서 생성될 수 있습니다. 번역할 수 없는 예외는 허용되지 않습니다. 이 예외를 사용하려고 하면 런타임 예외가 생성됩니다. 문서의 뒷부분에는 SQL로 변환하기 위해 구현되는 프레임워크 메서드에 대해 자세히 설명하는 섹션이 있습니다.

엔터티 수명 주기

LINQ to SQL 관계형 데이터베이스에 대한 표준 쿼리 연산자의 구현 이상입니다. 쿼리를 번역하는 것 외에도 수명 동안 개체를 관리하여 데이터의 무결성을 유지하고 수정 내용을 저장소로 다시 변환하는 프로세스를 자동화하는 데 도움이 되는 서비스입니다.

일반적인 시나리오에서 개체는 하나 이상의 쿼리를 통해 검색된 다음 애플리케이션이 변경 내용을 서버로 다시 보낼 준비가 될 때까지 어떤 식으로든 조작됩니다. 이 프로세스는 애플리케이션이 이 정보에 더 이상 사용하지 않을 때까지 여러 번 반복될 수 있습니다. 이 시점에서 개체는 일반 개체와 마찬가지로 런타임에 의해 회수됩니다. 그러나 데이터는 데이터베이스에 남아 있습니다. 런타임 존재에서 지워진 후에도 동일한 데이터를 나타내는 개체를 계속 검색할 수 있습니다. 이러한 의미에서 개체의 실제 수명은 단일 런타임 표현을 넘어 존재합니다.

이 챕터의 초점은 주기가 특정 런타임 컨텍스트 내에서 엔터티 개체의 단일 표현의 시간 범위를 참조하는 엔터티 수 명 주기 입니다. 이 주기는 DataContext가 새 instance 인식할 때 시작되고 개체 또는 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의 주문 컬렉션에 추가했을 수 있습니다.

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()에 대한 호출에서 변경 내용이 제출될 때 발생합니다. 개체 자체는 삭제되지 않습니다. 런타임은 개체 인스턴스의 수명을 관리하므로 여전히 참조를 보유하는 한 계속 유지됩니다. 그러나 개체가 테이블에서 제거되고 변경 내용이 제출된 후에는 변경 내용 추적 서비스에서 더 이상 추적되지 않습니다.

엔터티가 추적되지 않은 상태로 남아 있는 유일한 경우는 DataContext 가 인식하기 전에 존재하는 경우입니다. 이 문제는 코드에서 새 개체를 만들 때마다 발생합니다. 데이터베이스에서 엔터티 클래스를 검색하지 않고도 애플리케이션에서 엔터티 클래스의 인스턴스를 자유롭게 사용할 수 있습니다. 변경 태킹 및 ID 관리는 DataContext 가 인식하는 개체에만 적용됩니다. 따라서 두 서비스는 DataContext에 추가할 때까지 새로 만든 인스턴스에 대해 사용하도록 설정되지 않습니다.

이 문제는 두 가지 방법 중 하나로 발생할 수 있습니다. 관련 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가 이미 알고 있는 개체에 새 instance 연결할 수 있습니다.

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 
      }
   } )

기본적으로 DataContextAdd() 메서드를 호출했는지 여부에 관계없이 현재 새 instance 추적되지 않는 개체 그래프의 엔터티를 인식합니다.

읽기 전용 DataContext 사용

대부분의 시나리오에서는 데이터베이스에서 검색된 엔터티를 업데이트할 필요가 없습니다. 웹 페이지에 고객 테이블을 표시하는 것은 한 가지 명백한 예입니다. 이러한 모든 경우 DataContext 에 엔터티의 변경 내용을 추적하지 않도록 지시하여 성능을 향상시킬 수 있습니다. 이 작업은 다음 코드와 같이 DataContext에서 ObjectTracking 속성을 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

변경 내용 제출

개체에 대한 변경 내용 수에 관계없이 이러한 변경 내용은 메모리 내 복제본에만 적용되었습니다. 데이터베이스의 실제 데이터에는 아직 아무 일도 일어나지 않았습니다. DataContext에서 SubmitChanges()를 호출하여 명시적으로 요청할 때까지 이 정보를 서버에 전송하지 않습니다.

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()를 호출할 때 가장 먼저 발생하는 일은 알려진 개체 집합을 검사하여 새 인스턴스가 연결되었는지 확인하는 것입니다. 이러한 새 인스턴스는 추적된 개체 집합에 추가됩니다. 다음으로, 보류 중인 변경 내용이 있는 모든 개체는 개체 간의 종속성에 따라 개체 시퀀스로 정렬됩니다. 변경 내용이 다른 개체에 종속된 개체는 종속성 이후에 시퀀스됩니다. 데이터베이스의 외래 키 제약 조건 및 고유성 제약 조건은 올바른 변경 순서를 결정하는 데 큰 역할을 합니다. 그런 다음 실제 변경 내용이 전송되기 직전에 트랜잭션이 이미 scope 없는 한 일련의 개별 명령을 캡슐화하기 시작합니다. 마지막으로 개체에 대한 변경 내용이 하나씩 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 라는 속성이 있으며 , 이 속성은 Always, NeverWhenChanged의 세 가지 값 중 하나를 할당할 수 있습니다. 특성의 기본값을 설정하지 않으면 Always입니다. 즉, 버전 스탬프와 같은 명백한 타이 브레이커가 없는 한 해당 멤버가 나타내는 데이터 값이 항상 충돌을 확인합니다. Column 특성에는 데이터 값이 데이터베이스에서 유지 관리하는 버전 스탬프를 구성하는지 여부를 지정할 수 있는 IsVersion 속성이 있습니다. 버전이 있는 경우 버전만 사용하여 충돌이 발생했는지 확인합니다.

변경 충돌이 발생하면 다른 오류인 것처럼 예외가 throw됩니다. 제출을 둘러싼 트랜잭션은 중단되지만 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

중간 계층 또는 서버에서 변경하는 경우 변경 충돌을 해결하기 위해 수행할 수 있는 가장 쉬운 작업은 단순히 다시 시작하고 다시 시도하여 컨텍스트를 다시 만들고 변경 내용을 다시 적용하는 것입니다. 추가 옵션은 다음 섹션에 설명되어 있습니다.

트랜잭션

트랜잭션은 일련의 개별 작업이 자동으로 수행되도록 보장하는 데 사용할 수 있는 데이터베이스 또는 기타 리소스 관리자에서 제공하는 서비스입니다. 즉, 모두 성공하거나 모두 성공하지 못합니다. 그렇지 않으면 다른 작업이 허용되기 전에 모두 자동으로 실행 취소됩니다. 아직 scope 트랜잭션이 없는 경우 DataContextSubmitChanges()를 호출할 때 업데이트를 보호하도록 데이터베이스 트랜잭션을 자동으로 시작합니다.

직접 시작하여 사용된 트랜잭션 유형, 해당 격리 수준 또는 실제로 포괄하는 항목을 제어하도록 선택할 수 있습니다. 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

위의 예제에서는 새 트랜잭션 scope 개체를 만들어 완전히 직렬화된 트랜잭션을 시작합니다. 트랜잭션의 scope 내에서 실행되는 모든 데이터베이스 명령은 트랜잭션에 의해 보호됩니다.

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 는 누적된 모든 추적 정보를 throw하고 엔터티의 새 상태를 변경되지 않은 상태로 처리합니다. 그러나 트랜잭션이 실패할 경우 개체에 대한 변경 내용을 롤백하지는 않습니다. 이렇게 하면 변경 제출 중에 문제를 유연하게 처리할 수 있습니다.

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 사용됩니다. 두 매개 변수는 낙관적 동시성 충돌 검색에 사용할 수 있습니다.

참고 기본 업데이트 논리를 재정의하는 경우 충돌 검색은 사용자의 책임입니다.

저장 프로시저 UpdateProductStockDataContextExecuteCommand() 메서드를 사용하여 호출됩니다. 영향을 받는 행 수를 반환하며 다음 서명이 있습니다.

C#

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

Visual Basic

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

개체 배열은 명령을 실행하는 데 필요한 매개 변수를 전달하는 데 사용됩니다.

update 메서드와 마찬가지로 삽입 및 삭제 메서드를 지정할 수 있습니다. 삽입 및 삭제 메서드는 업데이트할 엔터티 형식의 매개 변수를 하나만 사용합니다. 예를 들어 Product instance 삽입하고 삭제하는 메서드는 다음과 같이 지정할 수 있습니다.

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 serialization 특성과 비슷합니다. 이러한 "데이터" 특성은 개체에 대한 쿼리를 데이터베이스에 대한 SQL 쿼리로 변환하고 개체의 변경 내용을 SQL 삽입, 업데이트 및 삭제 명령으로 변환할 수 있는 충분한 정보를 LINQ to SQL 제공합니다.

특성 대신 XML 매핑 파일을 사용하여 매핑 정보를 나타낼 수도 있습니다. 이 시나리오는 외부 매핑 섹션에서 자세히 설명합니다.

Database 특성

데이터베이스 특성은 연결에서 제공하지 않는 경우 데이터베이스의 기본 이름을 지정하는 데 사용됩니다. 데이터베이스 특성은 강력한 형식의 DataContext 선언에 적용할 수 있습니다. 이 특성은 선택 사항입니다.

Database 특성

속성 형식 Description
이름 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 특성이 있는 클래스는 LINQ to SQL 특별히 처리됩니다.

테이블 특성

속성 형식 Description
이름 String 테이블의 이름을 지정합니다. 이 정보를 지정하지 않으면 테이블의 이름이 엔터티 클래스와 동일한 것으로 간주됩니다.

C#

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

Visual Basic

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

Column 특성

Column 특성은 데이터베이스 테이블의 열을 나타내는 엔터티 클래스의 멤버를 지정하는 데 사용됩니다. 모든 필드 또는 속성, public, private 또는 internal에 적용할 수 있습니다. LINQ to SQL 데이터베이스에 변경 내용을 저장할 때 열로 식별된 멤버만 유지됩니다.

열 특성

속성 형식 Description
이름 String 테이블 또는 뷰에 있는 열의 이름입니다. 지정하지 않으면 열의 이름이 클래스 멤버와 동일한 것으로 간주됩니다.
스토리지 문자열 기본 스토리지의 이름입니다. 지정된 경우 데이터 멤버에 대한 공용 속성 접근자를 무시하고 원시 값 자체와 상호 작용하는 방법을 LINQ to SQL 알려줍니다. 지정하지 않으면 LINQ to SQL public 접근자를 사용하여 값을 가져오고 설정합니다.
Dbtype 문자열 데이터베이스 형식 및 한정자를 사용하여 지정된 데이터베이스 열의 형식입니다. T-SQL 테이블 선언 명령에서 열을 정의하는 데 사용되는 정확한 텍스트입니다. 지정하지 않으면 데이터베이스 열 형식이 멤버 형식에서 유추됩니다. 특정 데이터베이스 형식은 CreateDatabase() 메서드를 사용하여 데이터베이스의 instance 만들어야 하는 경우에만 필요합니다.
IsPrimaryKey Bool true로 설정하면 클래스 멤버는 테이블의 기본 키에 포함된 열을 나타냅니다. 클래스의 멤버가 둘 이상 ID로 지정된 경우 기본 키는 연결된 열의 복합 키라고 합니다.
IsDbGenerated 부울 멤버의 열 값이 데이터베이스에서 자동으로 생성됨을 식별합니다. IsDbGenerated=true로 지정된 기본 키에는 IDENTITY 한정자가 있는 DBType도 있어야 합니다. IsDbGenerated 멤버는 데이터 행이 삽입된 직후에 동기화되며 SubmitChanges() 가 완료된 후에 사용할 수 있습니다.
IsVersion 부울 멤버의 열 형식을 데이터베이스 타임스탬프 또는 버전 번호로 식별합니다. 버전 번호는 증가하며 연결된 행이 업데이트될 때마다 데이터베이스에서 타임스탬프 열을 업데이트합니다. IsVersion=true를 사용하는 멤버는 데이터 행이 업데이트된 직후에 동기화됩니다. SubmitChanges()가 완료된 후에 새 값이 표시됩니다.
UpdateCheck UpdateCheck LINQ to SQL 낙관적 동시성 충돌 검색을 구현하는 방법을 결정합니다. IsVersion=true 검색으로 지정된 멤버가 없는 경우 원래 멤버 값을 현재 데이터베이스 상태와 비교하여 수행합니다. 각 멤버에게 UpdateCheck 열거형 값을 제공하여 충돌 검색 중에 LINQ to SQL 사용하는 멤버를 제어할 수 있습니다.
  • 항상: 항상 충돌 검색에 이 열을 사용합니다.
  • 안 됨: 충돌 검색에 이 열을 사용하지 마세요.
  • WhenChanged: 애플리케이션에서 멤버를 변경한 경우에만 이 열을 사용합니다.
IsDiscriminator 부울 클래스 멤버가 상속 계층 구조에 대한 판별자 값을 보유하는지 여부를 결정합니다.
String LINQ to SQL 작업에는 영향을 주지 않지만 동안 사용됩니다.CreateDatabase()는 계산 열 식을 나타내는 원시 SQL 식입니다.
CanBeNull 부울 값에 null 값이 포함될 수 있음을 나타냅니다. 일반적으로 엔터티 멤버의 CLR 형식에서 유추됩니다. 이 특성을 사용하여 문자열 값이 데이터베이스에서 null을 허용하지 않는 열로 표시됨을 나타냅니다.
AutoSync AutoSync 삽입 또는 업데이트 명령에서 데이터베이스에서 생성된 값에서 열이 자동으로 동기화되는지 여부를 지정합니다. 이 태그의 유효한 값은 OnInsert, AlwaysNever입니다.

일반적인 엔터티 클래스는 공용 속성에서 특성을 사용하고 실제 값을 프라이빗 필드에 저장합니다.

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

DBTypeCreateDatabase() 메서드가 가장 정확한 형식으로 테이블을 생성할 수 있도록만 지정됩니다. 그렇지 않으면 기본 열이 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() 메서드를 통해 Database를 만들 때 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 부울  
decimal, numeric, smallmoney, money Decimal 크기 조정 차이로 인해 변환이 손실 될 수 있습니다. 왕복하지 않을 수 있습니다.
real, float Single, Double 정밀도 차이.
char, varchar, text, nchar, nvarchar, ntext 문자열 가능한 로캘 차이입니다.
datetime, smalldatetime DateTime 정밀도가 다르면 손실 변환 및 왕복 문제가 발생할 수 있습니다.
uniqueidentifier Guid 다른 데이터 정렬 규칙. 정렬이 예상대로 작동하지 않을 수 있습니다.
timestamp Byte[] (Byte() in Visual Basic), Binary 바이트 배열은 스칼라 형식으로 처리됩니다. 생성자가 호출될 때 사용자는 적절한 스토리지를 할당해야 합니다. 변경할 수 없는 것으로 간주되며 변경 내용은 추적되지 않습니다.
binary, varbinary Byte[] (Byte() in Visual Basic), Binary  

연결 특성

Association 특성은 외래 키와 기본 키 관계와 같은 데이터베이스 연결을 나타내는 속성을 지정하는 데 사용됩니다.

연결 특성

속성 형식 Description
이름 String 연결의 이름입니다. 데이터베이스의 외래 키 제약 조건 이름과 동일한 경우가 많습니다. CreateDatabase()를 사용하여 관련 제약 조건을 생성하기 위해 데이터베이스의 instance 만들 때 사용됩니다. 동일한 대상 엔터티 클래스를 참조하는 단일 엔터티 클래스의 여러 관계를 구분하는 데도 사용됩니다. 이 경우 관계 측면의 관계 속성(둘 다 정의된 경우)의 이름은 같아야 합니다.
스토리지 문자열 기본 스토리지 멤버의 이름입니다. 지정된 경우 데이터 멤버에 대한 공용 속성 접근자를 무시하고 원시 값 자체와 상호 작용하는 방법을 LINQ to SQL 알려줍니다. 지정하지 않으면 LINQ to SQL public 접근자를 사용하여 값을 가져오고 설정합니다. 모든 연결 멤버는 별도의 스토리지 멤버가 식별된 속성이 되는 것이 좋습니다.
ThisKey 문자열 연결의 이 쪽에 있는 키 값을 나타내는 이 엔터티 클래스의 하나 이상의 멤버 이름에 대한 쉼표로 구분된 목록입니다. 지정하지 않으면 멤버는 기본 키를 구성하는 멤버로 간주됩니다.
OtherKey 문자열 연결의 다른 쪽에 있는 키 값을 나타내는 대상 엔터티 클래스의 하나 이상의 멤버에 대한 쉼표로 구분된 이름 목록입니다. 지정하지 않으면 멤버는 다른 엔터티 클래스의 기본 키를 구성하는 멤버로 간주됩니다.
IsUnique 부울 True 이면 외래 키에 고유성 제약 조건이 있으며 이는 실제 1:1 관계를 나타냅니다. 이 속성은 데이터베이스 내에서 1:1 관계를 관리하는 것이 거의 불가능하기 때문에 거의 사용되지 않습니다. 대부분 엔터티 모델은 애플리케이션 개발자가 1:1로 처리하는 경우에도 1:n 관계를 사용하여 정의됩니다.
IsForeignKey 부울 연결 의 대상 "other" 형식이 원본 형식의 부모이면 True입니다. 외래 키를 기본 키 관계에 사용하면 외래 키를 보유하는 쪽은 자식이고 기본 키를 보유하는 쪽은 부모입니다.
DeleteRule 문자열 이 연결에 삭제 동작을 추가하는 데 사용됩니다. 예를 들어 "CASCADE"는 FK 관계에 "ON DELETE CASCADE"를 추가합니다. null로 설정하면 삭제 동작이 추가되지 않습니다.

연결 속성은 instance 다른 엔터티 클래스에 대한 단일 참조를 나타내거나 참조 컬렉션을 나타냅니다. 실제 참조를 저장하려면 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 을 반환 형식으로 사용하는 것이 유효합니다. 대신 ICollection T>(Visual Basic의 ICollection<(OfT) 인터페이스를 사용하여 컬렉션의 실제 형식을 위장하는 것도 유효합니다.

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

속성에 대한 공용 setter를 노출하는 경우 EntitySet에서 Assign() 메서드를 사용해야 합니다. 이렇게 하면 엔터티 클래스가 변경 내용 추적 서비스에 이미 연결되어 있을 수 있으므로 동일한 컬렉션 instance 계속 사용할 수 있습니다.

ResultType 특성

이 특성은 IMultipleResults 인터페이스를 반환하도록 선언된 함수에서 반환할 수 있는 열거 가능한 시퀀스의 요소 형식을 지정합니다. 이 특성을 두 번 이상 지정할 수 있습니다.

ResultType 특성

속성 형식 Description
형식 유형 반환된 결과의 형식입니다.

StoredProcedure 특성

StoredProcedure 특성은 DataContext 또는 스키마 형식에 정의된 메서드에 대한 호출이 데이터베이스 저장 프로시저에 대한 호출로 변환되었음을 선언하는 데 사용됩니다.

StoredProcedure 특성

속성 형식 Description
이름 String 데이터베이스에 있는 저장 프로시저의 이름입니다. 지정하지 않으면 저장 프로시저의 이름이 메서드와 동일한 것으로 간주됩니다.

함수 특성

Function 특성은 DataContext 또는 Schema에 정의된 메서드에 대한 호출이 데이터베이스 사용자 정의 스칼라 또는 테이블 반환 함수에 대한 호출로 변환됨을 선언하는 데 사용됩니다.

함수 특성

속성 형식 Description
이름 String 데이터베이스에 있는 함수의 이름입니다. 지정하지 않으면 함수가 메서드와 동일한 이름을 갖는 것으로 간주됩니다.

매개 변수 특성

Parameter 특성은 메서드와 데이터베이스 저장 프로시저 또는 사용자 정의 함수의 매개 변수 간에 매핑을 선언하는 데 사용됩니다.

매개 변수 특성

속성 형식 Description
이름 String 데이터베이스에 있는 매개 변수의 이름입니다. 지정하지 않으면 매개 변수가 메서드 매개 변수 이름에서 유추됩니다.
Dbtype 문자열 데이터베이스 형식 및 한정자를 사용하여 지정된 매개 변수의 형식입니다.

InheritanceMapping 특성

InheritanceMapping 특성은 특정 판별자 코드와 상속 하위 형식 간의 대응을 설명하는 데 사용됩니다. 상속 계층 구조에 사용되는 모든 InheritanceMapping 특성은 계층의 루트 형식에서 선언되어야 합니다.

InheritanceMapping 특성

Propety 형식 Description
코드 개체 판별자 코드 값입니다.
유형 유형 상속 하위 형식입니다. 루트 형식을 포함하여 상속 계층의 추상이 아닌 형식일 수 있습니다.
IsDefault 부울 LINQ to SQL InheritanceMapping 특성으로 정의되지 않은 판별자 코드를 발견할 때 지정된 상속 하위 형식이 기본 형식인지 확인합니다. 정확히 InheritanceMapping 특성 중 하나는 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) 형식에는 콜백으로 사용할 두 개의 대리자를 제공할 수 있는 생성자가 있습니다. 첫 번째는 항목이 컬렉션에 추가될 때, 두 번째는 제거될 때입니다. 예제에서 볼 수 있듯이 이러한 대리자용으로 지정한 코드는 역방향 관계 속성을 업데이트하기 위해 작성할 수 있고 작성되어야 합니다. 이는 주문이 고객의Orders 컬렉션에 추가되면 Order instance 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

setter를 살펴보세요. Customer 속성이 변경되면 주문 instance 먼저 현재 고객의 Orders 컬렉션에서 제거된 다음 나중에 새 고객의 컬렉션에 추가됩니다. Remove()를 호출하기 전에 실제 엔터티 참조가 null로 설정됩니다. 이는 Remove() 메서드가 호출될 때 재귀를 방지하기 위해 수행됩니다. EntitySet은 콜백 대리자를 사용하여 이 개체의 Customer 속성을 null에 할당합니다. Add()를 호출하기 바로 전에 동일한 일이 발생합니다. 실제 엔터티 참조가 새 값으로 업데이트됩니다. 이렇게 하면 잠재적인 재귀가 다시 줄어들고 물론 처음부터 setter의 작업을 수행할 수 있습니다.

일대일 관계의 정의는 싱글톤 참조 쪽에서 일대다 관계의 정의와 매우 유사합니다. Add() Remove()가 호출되는 대신 새 개체가 할당되거나 관계를 끊기 위해 null이 할당됩니다.

다시 말하지만 관계 속성은 개체 그래프의 일관성을 유지하는 것이 중요합니다. 메모리 내 개체 그래프가 데이터베이스 데이터와 일치하지 않으면 SubmitChanges 메서드가 호출될 때 런타임 예외가 생성됩니다. 코드 생성 도구를 사용하여 일관성 작업을 유지하는 것이 좋습니다.

변경 알림

개체가 변경 내용 추적 프로세스에 참여할 수 있습니다. 필수는 아니지만 잠재적 개체 변경을 추적하는 데 필요한 오버헤드 양을 상당히 줄일 수 있습니다. 애플리케이션이 쿼리에서 수정되는 것보다 더 많은 개체를 검색할 가능성이 높습니다. 개체의 사전 예방적 도움이 없으면 변경 내용 추적 서비스가 실제로 변경 내용을 추적하는 방법이 제한됩니다.

런타임에는 실제 가로채기 서비스가 없으므로 공식적인 추적이 실제로 발생하지 않습니다. 대신 개체의 중복 복사본은 처음 검색될 때 저장됩니다. 나중에 SubmitChanges()를 호출하면 이러한 복사본을 사용하여 제공된 복사본과 비교합니다. 값이 다르면 개체가 수정되었습니다. 즉, 모든 개체를 변경하지 않더라도 메모리에 두 개의 복사본이 필요합니다.

더 나은 해결 방법은 개체가 실제로 변경될 때 개체가 변경 내용 추적 서비스에 알릴 수 있도록 하는 것입니다. 이 작업은 개체가 콜백 이벤트를 노출하는 인터페이스를 구현하도록 하여 수행할 수 있습니다. 그런 다음 변경 내용 추적 서비스는 각 개체를 연결하고 변경 시 알림을 받을 수 있습니다.

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이라는 이벤트만 정의하면 됩니다. 변경 내용 추적 서비스는 개체를 소유할 때 이벤트에 등록합니다. 속성 값을 변경하기 직전에 이 이벤트를 발생하기만 하면 됩니다.

관계 속성 setter에도 동일한 이벤트 발생 논리를 배치하는 것을 잊지 마세요. 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 전체 상속 계층이 단일 데이터베이스 테이블에 저장되는 단일 테이블 매핑을 지원합니다. 테이블은 전체 계층 구조에 대해 가능한 모든 데이터 열의 평면화된 결합을 포함하며 각 행에는 행이 나타내는 instance 형식에 적용되지 않는 열에 null이 있습니다. 단일 테이블 매핑 전략은 상속을 나타내는 가장 간단한 방법이며 여러 다른 쿼리 범주에 대한 탁월한 성능 특징을 제공합니다.

매핑

LINQ to SQL 사용하여 이 매핑을 구현하려면 상속 계층의 루트 클래스에서 다음 특성 및 특성 속성을 지정해야 합니다.

  • [Table](<Visual Basic의 Table>) 특성입니다.
  • 계층 구조의 각 클래스에 대한 [InheritanceMapping] (<Visual Basic의 InheritanceMapping> ) 특성입니다. 비 추상 클래스의 경우 이 특성은 Code 속성(이 데이터 행이 속한 클래스 또는 하위 클래스를 나타내기 위해 상속 판별자 열의 데이터베이스 테이블에 표시되는 값) 및 Type 속성(키 값이 나타내는 클래스 또는 서브클래스를 지정)을 정의해야 합니다.
  • 단일 [InheritanceMapping](<Visual Basic의 InheritanceMapping>) 특성에 대한 IsDefault 속성입니다. 이 속성은 데이터베이스 테이블의 판별자 값이 상속 매핑의 Code 값과 일치하지 않는 경우 "대체" 매핑을 지정하는 역할을 합니다.
  • 상속 매핑에 대한 코드 값을 보유하는 열임을 나타내는 [Column](<Visual Basic의 Column>) 특성에 대한 IsDiscriminator 속성입니다.

서브클래스에는 특수 특성 또는 속성이 필요 없습니다. 특히 서브클래스에 는 [Table] (<Visual Basic의 Table> ) 특성이 없습니다.

다음 예제에서는 CarTruck 서브클래스에 포함된 데이터가 단일 데이터베이스 테이블 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. 차량 클래스 다이어그램

서버 Explorer 결과 데이터베이스 다이어그램을 보면 다음과 같이 열이 모두 단일 테이블에 매핑된 것을 볼 수 있습니다.

그림 2. 단일 테이블에 매핑된 열

하위 형식의 필드를 나타내는 열의 형식은 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))

고급 항목

데이터베이스 만들기

엔터티 클래스에는 관계형 데이터베이스 테이블 및 열의 구조를 설명하는 특성이 있으므로 이 정보를 사용하여 데이터베이스의 새 인스턴스를 만들 수 있습니다. DataContext에서 CreateDatabase() 메서드를 호출하여 LINQ to SQL 개체에 정의된 구조로 새 데이터베이스 instance 생성하도록 할 수 있습니다. 이 작업을 수행하는 데는 여러 가지 이유가 있습니다. 고객 시스템에 자동으로 설치되는 애플리케이션 또는 오프라인 상태를 저장하기 위해 로컬 데이터베이스가 필요한 클라이언트 애플리케이션을 빌드할 수 있습니다. 이러한 시나리오에서 CreateDatabase()는 특히 SQL Server Express 2005와 같은 알려진 데이터 공급자를 사용할 수 있는 경우에 이상적입니다.

그러나 데이터 특성은 기존 데이터베이스 구조에 대한 모든 것을 인코딩하지 않을 수 있습니다. 사용자 정의 함수, 저장 프로시저, 트리거 및 검사 제약 조건의 내용은 특성으로 표현되지 않습니다. CreateDatabase() 함수는 데이터베이스의 구조와 각 테이블의 열 유형인 알고 있는 정보를 사용하여 데이터베이스의 복제본(replica) 만듭니다. 그러나 다양한 데이터베이스의 경우 이것으로 충분합니다.

다음은 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 공급자 모델에서 제공하는 서비스를 기반으로 하므로 LINQ to SQL 코드를 기존 ADO.NET 애플리케이션과 혼합할 수 있습니다.

LINQ to SQL DataContext를 만들 때 기존 ADO.NET 연결을 제공할 수 있습니다. 쿼리를 포함하여 DataContext에 대한 모든 작업은 제공한 연결을 사용합니다. 연결이 이미 열려 있는 경우 LINQ to SQL 연결에 대한 권한을 존중하고 연결이 완료되면 그대로 둡니다. 일반적으로 LINQ to SQL 트랜잭션이 scope 않는 한 작업이 완료되는 즉시 연결을 닫습니다.

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

항상 Connection 속성을 통해 DataContext에서 사용하는 연결에 액세스하고 직접 닫을 수 있습니다.

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

트랜잭션이 설정될 때마다 DataContext는 쿼리를 실행하거나 명령을 실행할 때마다 이를 사용합니다. 완료되면 속성을 null 로 다시 할당해야 합니다.

그러나 .NET Framework 트랜잭션을 수행하는 기본 방법은 TransactionScope 개체를 사용하는 것입니다. 이를 통해 데이터베이스 및 기타 메모리 상주 리소스 관리자 간에 작동하는 분산 트랜잭션을 만들 수 있습니다. 즉, 트랜잭션 범위는 트랜잭션의 scope 내에서 여러 데이터베이스 또는 여러 연결을 참조할 때만 분산 트랜잭션에서 전체로 승격됩니다.

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라는 두 테이블에 분산되어 있다고 가정하면 다음 쿼리는 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)와 같은 생성된 매개 변수 이름으로 대체합니다.

충돌 해결 변경

Description

변경 충돌은 클라이언트가 개체에 변경 내용을 제출하려고 시도하고 업데이트 검사 사용된 하나 이상의 값이 클라이언트가 마지막으로 읽은 이후 데이터베이스에서 업데이트된 경우에 발생합니다.

참고UpdateCheck.Always 또는 UpdateCheck.WhenChanged 로 매핑된 멤버만 낙관적 동시성 검사에 참여합니다. UpdateCheck.Never로 표시된 멤버에 대해 검사 수행되지 않습니다.

이 충돌 해결에는 충돌 중인 개체의 멤버를 검색한 다음 이에 대해 수행할 작업을 결정하는 것이 포함됩니다. 낙관적 동시성은 특정 상황에서 최상의 전략이 아닐 수 있습니다. 때로는 "마지막 업데이트가 승리하게"하는 것이 완벽하게 합리적입니다.

LINQ to SQL 충돌 검색, 보고 및 해결

충돌 해결은 데이터베이스를 다시 쿼리하고 차이점을 조정하여 충돌하는 항목을 새로 고치는 프로세스입니다. 개체를 새로 고치면 변경 추적기에서 이전 원래 값과 새 데이터베이스 값이 있습니다. 그런 다음 LINQ to SQL 개체가 충돌하는지 여부를 결정합니다. 이 경우 LINQ to SQL 관련된 멤버를 결정합니다. 멤버의 새 데이터베이스 값이 이전 원본(실패한 업데이트 검사 사용됨)와 다른 경우 이는 충돌입니다. 멤버 충돌이 충돌 목록에 추가됩니다.

예를 들어 다음 시나리오에서 User1은 데이터베이스에서 행을 쿼리하여 업데이트를 준비하기 시작합니다. User1에서 변경 내용을 제출하기 전에 User2에서 데이터베이스를 변경했습니다. Col B 및 Col C에 필요한 값이 변경되었으므로 User1의 제출이 실패합니다.

데이터베이스 업데이트 충돌

사용자 Col A Col B Col C
원래 상태 Alfreds Maria Sales
사용자 1 Alfred   Marketing
사용자 2   Mary 서비스

LINQ to SQL 낙관적 동시성 충돌로 인해 업데이트에 실패한 개체는 예외(ChangeConflictException)를 throw합니다. 첫 번째 오류에서 예외를 throw해야 하는지 또는 예외에 누적되고 보고되는 모든 실패와 함께 모든 업데이트를 시도해야 하는지 여부를 지정할 수 있습니다.

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

throw되면 예외는 ObjectChangeConflict 컬렉션에 대한 액세스를 제공합니다. MemberConflicts 목록에 대한 액세스를 포함하여 각 충돌(실패한 단일 업데이트 시도에 매핑됨)에 대한 세부 정보를 사용할 수 있습니다. 각 멤버 충돌은 동시성 확인에 실패한 업데이트의 단일 멤버에 매핑됩니다.

충돌 처리

이전 시나리오에서 User1에는 다시 제출하기 전에 차이점을 조정하기 위해 아래에 설명된 RefreshMode 옵션이 있습니다. 모든 경우에 클라이언트의 레코드는 먼저 데이터베이스에서 업데이트된 데이터를 끌어와서 "새로 고침"됩니다. 이 작업을 수행하면 동일한 동시성 검사에서 다음 업데이트 시도가 실패하지 않습니다.

여기서 User1은 현재 변경 집합이 해당 값을 수정한 경우에만 데이터베이스 값을 덮어쓰도록 데이터베이스 값을 현재 클라이언트 값과 병합하도록 선택합니다. (이 섹션의 뒷부분에 있는 예제 1을 참조하세요.)

위의 시나리오에서 충돌 해결 후 데이터베이스의 결과는 다음과 같습니다.

KeepChanges

  Col A Col B Col C
KeepChanges Alfred(사용자 1) Mary(사용자 2) 마케팅(사용자 1)
  • Col A: User1의 변경 내용(Alfred)이 나타납니다.
  • Col B: User2의 변경 내용(Mary)이 나타납니다. User1에서 이 값을 변경하지 않았기 때문에 이 값이 병합되었습니다.
  • Col C: User1의 변경 내용(마케팅)이 나타납니다. User1도 해당 항목을 변경했기 때문에 User2의 변경 내용(서비스)이 병합되지 않습니다.

아래에서 User1은 모든 데이터베이스 값을 현재 값으로 덮어쓰도록 선택합니다. (이 섹션의 뒷부분에 있는 예제 2를 참조하세요.)

새로 고침 후 User1의 변경 내용이 제출됩니다. 데이터베이스의 결과는 다음과 같습니다.

KeepCurrentValues

  Col A Col B Col C
KeepCurrentValues Alfred(사용자 1) Maria(원본) 마케팅(사용자 1)
  • Col A: User1의 변경 내용(Alfred)이 나타납니다.
  • Col B: 원래 마리아는 그대로 남아 있습니다. User2의 변경 내용은 삭제됩니다.
  • Col C: User1의 변경 내용(마케팅)이 나타납니다. User2의 변경 내용(서비스)은 삭제됩니다.

다음 시나리오에서 User1은 데이터베이스 값이 클라이언트의 현재 값을 덮어쓰도록 허용하도록 선택합니다. (이 섹션의 뒷부분에 있는 예제 3을 참조하세요.)

위의 시나리오에서 충돌 해결 후 데이터베이스의 결과는 다음과 같습니다.

OverwriteCurrentValues

  Col A Col B Col C
OverwriteCurrentValues 알프레드 (오리지널) Mary(사용자 2) 서비스(사용자 2)
  • Col A: 원래 값(Alfreds)은 그대로 유지됩니다. User1의 값(Alfred)은 삭제됩니다.
  • Col B: User2의 변경 내용(Mary)이 나타납니다.
  • Col C: User2의 변경 내용(서비스)이 나타납니다. User1의 변경 내용(마케팅)은 삭제됩니다.

충돌이 해결된 후 다시 제출을 시도할 수 있습니다. 이 두 번째 업데이트도 실패할 수 있으므로 업데이트 시도에 루프를 사용하는 것이 좋습니다.

예제

다음 코드 발췌문은 멤버 충돌을 검색하고 해결하기 위한 다양한 정보 멤버 및 기술을 보여 줍니다.

예제 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 특성을 사용하여 저장 프로시저 및 함수를 메서드에 매핑합니다. StoredProcedure, ParameterFunction 특성은 모두 Name 속성을 지원하며 Parameter 특성은 DBType 속성도 지원합니다. 다음 두 가지 예제를 살펴보세요.

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

다음 저장 프로시저는 단일 입력 매개 변수를 사용하고 정수를 반환합니다.

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와 마찬가지로 이 절차를 매핑합니다. 그러나 이 경우 두 개의 순차적 결과 집합이 있습니다.

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 하고 값 형식의 경우 매개 변수를 nullable로 선언합니다(예: int?). 다음 예제의 프로시저는 단일 입력 매개 변수를 사용하고 매개 변수를 반환합니다 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(테이블 반환 함수)는 여러 결과 셰이프를 반환할 수 있는 저장 프로시저와 달리 단일 결과 집합을 반환합니다. TVF 반환 형식은 테이블이므로 테이블을 사용할 수 있는 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 쿼리는 다음과 같이 렌더링됩니다(새 '조인' 구문 사용).

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 메타데이터를 추출하고 엔터티 클래스 선언이 포함된 원본 파일을 생성할 수 있습니다. 또는 프로세스를 두 단계로 분할하여 먼저 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단계 접근 방식을 사용할 수 있습니다.

SQLMetal에 DBML 파일을 생성하도록 지시하려면 다음과 같이 도구를 사용합니다.

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에 대한 명령줄 옵션

옵션 Description
/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:<seconds> 데이터베이스 명령에 사용할 시간 제한 값(초)입니다.

참고 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 특성에 느슨하게 매핑됩니다.

데이터베이스 특성

attribute Type 기본값 Description
이름 String 없음 데이터베이스의 이름입니다. 있는 경우 DataContext를 생성하는 경우 데이터베이스 특성을 이 이름으로 연결합니다. 클래스 특성이 없는 경우 DataContext 클래스의 이름으로도 사용됩니다.
EntityNamespace 강력 없음 Table 요소 내의 Type 요소에서 생성된 클래스의 기본 네임스페이스입니다. 여기에 지정된 네임스페이스가 없으면 루트 네임스페이스에 엔터티 클래스가 생성됩니다.
ContextNamespace 문자열 없음 생성된 DataContext 클래스의 기본 네임스페이스입니다. 여기에 지정된 네임스페이스가 없으면 DataContext 클래스가 루트 네임스페이스에 생성됩니다.
클래스 문자열 Database.Name 생성된 DataContext 클래스의 이름입니다. 없는 경우 Database 요소의 Name 특성을 사용합니다.
AccessModifier AccessModifier 공용 생성된 DataContext 클래스의 접근성 수준입니다. 유효한 값은 Public, Protected, InternalPrivate입니다.
BaseType 문자열 "System.Data.Linq.DataContext" DataContext 클래스의 기본 형식입니다.
공급자 문자열 "System.Data.Linq.SqlClient.Sql2005Provider" DataContext 공급자는 Sql2005 공급자를 기본값으로 사용합니다.
ExternalMapping 부울 아니요 DBML이 외부 매핑 파일을 생성하는 데 사용되는지 지정합니다.
Serialization SerializationMode SerializationMode.None 생성된 DataContext 및 엔터티 클래스를 직렬화할 수 있는지 지정합니다.

데이터베이스 Sub-Element 특성

Sub-Element 요소 형식 발생 범위 Description
<테이블> 테이블 0-unbounded 단일 형식 또는 상속 계층 구조에 매핑되는 SQL Server 테이블 또는 뷰를 나타냅니다.
<Function> 함수 0-unbounded 생성된 DataContext 클래스의 메서드에 매핑될 SQL Server 저장 프로시저 또는 db 함수를 나타냅니다.
<연결> 연결 0-1 DataContext에서 사용할 데이터베이스 연결을 나타냅니다.

테이블

이 요소는 단일 형식 또는 상속 계층 구조에 매핑되는 데이터베이스 테이블(또는 뷰)을 나타냅니다. 이 요소는 생성된 엔터티 클래스의 Table 특성에 느슨하게 매핑됩니다.

테이블 특성

attribute Type 기본값 Description
이름 String (필수) 데이터베이스 내의 테이블 이름입니다. 필요한 경우 테이블 어댑터의 기본 이름 기반 역할을 합니다.
멤버 문자열 Table.Name DataContext 클래스 내에서 이 테이블에 대해 생성된 멤버 필드의 이름입니다.
AccessModifier AccessModifier 공용 DataContext 내에서 Table<T> 참조의 접근성 수준입니다. 유효한 값은 Public, Protected, InternalPrivate입니다.

테이블 Sub-Element 특성

Sub-Element 요소 형식 발생 범위 Description
<형식> 형식 1-1 이 테이블에 매핑된 형식 또는 상속 계층 구조를 나타냅니다.
<InsertFunction> TableFunction 0-1 삽입할 메서드입니다. 있는 경우 InsertT 메서드가 생성됩니다.
<UpdateFunction> TableFunction 0-1 업데이트할 메서드입니다. 있는 경우 UpdateT 메서드가 생성됩니다.
<DeleteFunction> TableFunction 0-1 삭제하는 메서드입니다. 있는 경우 DeleteT 메서드가 생성됩니다.

형식

이 요소는 Table 또는 저장 프로시저 결과 셰이프에 대한 형식 정의를 나타냅니다. 그러면 지정된 열 및 연결이 있는 새 CLR 형식으로 코드 생성됩니다.

형식은 여러 형식이 동일한 테이블에 매핑되는 상속 계층 구조의 구성 요소를 나타낼 수도 있습니다. 이 경우 Type 요소는 부모-자식 상속 관계를 나타내기 위해 중첩되고 지정된 InheritanceCode 로 데이터베이스에서 구분됩니다.

형식 특성

attribute Type 기본값 Description
이름 String (필수) 생성할 CLR 형식의 이름입니다.
InheritanceCode 문자열 없음 이 형식이 상속에 참여하는 경우 테이블에서 행을 로드할 때 CLR 형식을 구분하는 연결된 상속 코드가 있을 수 있습니다. InheritanceCodeIsDiscriminator 열의 값과 일치하는 Type은 로드된 개체를 인스턴스화하는 데 사용됩니다. 상속 코드가 없는 경우 생성된 엔터티 클래스는 추상입니다.
IsInheritanceDefault 부울 아니요 상속 계층 구조의 Type 에 대해 true이면 정의된 상속 코드에서 일치하지 않는 행을 로드할 때 이 형식이 사용됩니다.
AccessModifier AccessModifier 공용 만들 CLR 형식의 접근성 수준입니다. 유효한 값은 Public, Protected, InternalPrivate입니다.
Id String 없음 형식에는 고유한 ID가 있을 수 있습니다. 형식의 ID는 다른 테이블이나 함수에서 사용할 수 있습니다. ID는 개체 모델이 아닌 DBML 파일에만 표시됩니다.
Idref 문자열 없음 IdRef 는 다른 형식의 ID를 참조하는 데 사용됩니다. IdRef 가 형식 요소에 있는 경우 형식 요소는 IdRef 정보만 포함해야 합니다. IdRef 는 개체 모델이 아닌 DBML 파일에만 나타납니다.

형식 Sub-Element 특성

Sub-Element 요소 형식 발생 범위 Description
<열> 0-unbounded 이 형식의 테이블에 있는 필드에 바인딩될 이 형식 내의 속성을 나타냅니다.
<연결> 연결 0-unbounded 테이블 간의 외래 키 관계의 한쪽 끝에 바인딩되는 이 형식 내의 속성을 나타냅니다.
<형식> 하위 유형 0-unbounded 상속 계층 구조 내에서 이 형식의 하위 형식을 나타냅니다.

하위 유형

이 요소는 상속 계층 구조에서 파생된 형식을 나타냅니다. 이 형식에 지정된 열 및 연결이 있는 새 CLR 형식으로 생성됩니다. 하위 형식에 대한 상속 특성이 생성되지 않습니다.

Type과 비교하면 파생된 모든 형식이 public이어야 하므로 SubType 요소에는 AccessModifier가 없습니다. 하위 형식은 다른 테이블 및 함수에서 재사용할 수 없으므로 IDIdRef 가 없습니다.

하위 유형 특성

attribute Type 기본값 Description
이름 String (필수) 생성할 CLR 형식의 이름입니다.
InheritanceCode 문자열 없음 이 형식이 상속에 참여하는 경우 테이블에서 행을 로드할 때 CLR 형식을 구분하는 연결된 상속 코드가 있을 수 있습니다. InheritanceCodeIsDiscriminator 열의 값과 일치하는 Type은 로드된 개체를 인스턴스화하는 데 사용됩니다. 상속 코드가 없는 경우 생성된 엔터티 클래스는 추상입니다.
IsInheritanceDefault 부울 아니요 상속 계층 구조의 Type 에 대해 true이면 정의된 상속 코드에서 일치하지 않는 행을 로드할 때 이 형식이 사용됩니다.

SubType Sub-Element 특성

Sub-Element 요소 형식 발생 범위 Description
<열> 0-unbounded 이 형식의 테이블에 있는 필드에 바인딩될 이 형식 내의 속성을 나타냅니다.
<연결> 연결 0-unbounded 테이블 간의 외래 키 관계의 한쪽 끝에 바인딩될 이 형식 내의 속성을 나타냅니다.
<형식> 하위 유형 0-unbounded 상속 계층 구조 내에서 이 형식의 하위 형식을 나타냅니다.

이 요소는 클래스 내의 속성(및 지원 필드)에 매핑되는 테이블 내의 열을 나타냅니다. 그러나 연결 요소로 완전히 표현되기 때문에 외래 키 관계의 양쪽 끝에는 Column 요소가 없습니다.

열 특성

특성 Type 기본값 Description
이름 String 없음 이 열이 매핑할 데이터베이스 필드의 이름입니다.
멤버 문자열 이름 포함하는 형식에서 생성할 CLR 속성의 이름입니다.
스토리지 문자열 _멤버 이 열의 값을 저장할 프라이빗 CLR 지원 필드의 이름입니다. 직렬화할 때는 기본값인 경우에도 스토리지 를 제거하지 마세요.
AccessModifier AccessModifier 공용 만들 CLR 속성의 접근성 수준입니다. 유효한 값은 Public, Protected, InternalPrivate입니다.
Type String (필수) 생성되는 CLR 속성 및 지원 필드 형식의 이름입니다. 생성된 코드가 컴파일될 때 이름이 궁극적으로 scope 있는 한 정규화된 이름에서 클래스의 직접 이름에 이르기까지 무엇이든 될 수 있습니다.
DbType 문자열 없음 이 열에 대한 전체 SQL Server 형식(NOT NULL과 같은 주석 포함) LINQ to SQL 생성되는 쿼리를 최적화하고 CreateDatabase()를 수행할 때 보다 구체적이도록 제공하는 경우 사용됩니다. 항상 DbType을 serialize합니다.
IsReadOnly 부울 아니요 IsReadOnly가 설정된 경우 속성 setter가 만들어지지 않습니다. 즉, 사용자가 해당 개체를 사용하여 이 열의 값을 변경할 수 없습니다.
IsPrimaryKey 부울 아니요 이 열이 테이블의 기본 키에 참여했음을 나타냅니다. LINQ to SQL 제대로 작동하려면 이 정보가 필요합니다.
IsDbGenerated 부울 아니요 이 필드의 데이터가 데이터베이스에 의해 생성됨을 나타냅니다. 이는 주로 일련 번호 필드 및 계산 필드의 경우입니다. 이러한 필드에 값을 할당하는 것은 의미가 없으므로 자동으로 IsReadOnly입니다.
CanBeNull 부울 없음 값에 null 값이 포함될 수 있음을 나타냅니다. 실제로 CLR에서 null 값을 사용하려면 ClrTypeNullable<T>로 지정해야 합니다.
UpdateCheck UpdateCheck 항상(하나 이상의 다른 멤버에 IsVersion 이 설정되어 있지 않으면 안 됨) 낙관적 동시성 충돌 검색 중에 LINQ to SQL 이 열을 사용해야 하는지 여부를 나타냅니다. 일반적으로 IsVersion 열이 없으면 기본적으로 모든 열이 참여합니다. 이 열은 자체적으로 참여합니다. 가능: Always, Never 또는 WhenChanged (즉, 고유한 값이 변경된 경우 열이 참여함).
IsDiscriminator 부울 아니요 이 필드에 상속 계층 구조의 형식 중에서 선택하는 데 사용되는 판별자 코드가 포함되어 있는지 여부를 나타냅니다.
String 없음 LINQ to SQL 작업에는 영향을 주지 않지만 동안 사용됩니다.CreateDatabase()는 계산 열 식을 나타내는 원시 SQL 식입니다.
IsVersion 부울 아니요 이 필드는 행이 변경 될 때마다 자동으로 업데이트 되는 SQL Server 타임스탬프 필드를 나타냅니다. 그런 다음 이 필드를 사용하여 보다 효율적인 낙관적 동시성 충돌 검색을 사용하도록 설정할 수 있습니다.
IsDelayLoaded 부울 아니요 개체 구체화 시 이 열을 즉시 로드하지 말고 관련 속성에 처음 액세스할 때만 로드해야 했음을 나타냅니다. 이는 항상 필요하지 않은 행의 큰 메모 필드 또는 이진 데이터에 유용합니다.
AutoSync AutoSync If (IsDbGenerated && IsPrimaryKey) OnInsert;

그렇지 않으면(IsDbGenerated) Always

그렇지 않은 경우

데이터베이스에서 생성된 값에서 열이 자동으로 동기화되는지를 지정합니다. 이 태그의 유효한 값은 OnInsert, AlwaysNever입니다.

연결

이 요소는 외래 키 관계의 양쪽 끝을 나타냅니다. 일대다 관계의 경우 한쪽의 EntitySet<T> 와 여러 쪽의 EntityRef<T> 가 됩니다. 일대일 관계의 경우 양쪽의 EntityRef<T> 가 됩니다.

연결의 양쪽에 연결 항목이 필요하지는 않습니다. 이 경우 속성은 항목이 있는 쪽에서만 생성됩니다(단방향 관계 형성).

연결 특성

attribute Type 기본값 Description
이름 String (필수) 관계의 이름(일반적으로 외래 키 제약 조건 이름)입니다. 이는 기술적으로 선택 사항이 될 수 있지만 동일한 두 테이블 간에 여러 관계가 있는 경우 모호성을 방지하기 위해 항상 코드에서 생성해야 합니다.
멤버 문자열 이름 연결의 이 쪽에서 생성할 CLR 속성의 이름입니다.
스토리지 문자열 OneToMany 및 IsForeignKey가 아닌 경우:

_OtherTable

다른:

_TypeName(OtherTable)

이 열의 값을 저장할 프라이빗 CLR 지원 필드의 이름입니다.
AccessModifier AccessModifier 공용 만들 CLR 속성의 접근성 수준입니다. 유효한 값은 Public, Protected, InternalPrivate입니다.
ThisKey 문자열 포함하는 클래스 내의 IsIdentity 속성 연결의 이 쪽에 있는 키의 쉼표로 구분된 목록입니다.
OtherTable 문자열 설명을 참조하세요. 관계의 다른 쪽 끝에 있는 테이블입니다. 일반적으로 관계 이름을 일치시켜 LINQ to SQL 런타임에 의해 확인할 수 있지만 단방향 연결 또는 익명 연결에는 불가능합니다.
OtherKey 문자열 외래 클래스 내의 기본 키 연결의 반대편에 있는 키의 쉼표로 구분된 목록입니다.
IsForeignKey 부울 아니요 이것이 관계의 "자식" 쪽인지, 일대다의 다측인지를 나타냅니다.
RelationshipType RelationshipType OneToMany 사용자가 이 연결과 관련된 데이터가 일대일 데이터의 조건을 충족하는지 아니면 일대다의 일반적인 사례에 맞는지 여부를 나타냅니다. 일대일의 경우 사용자는 기본 키("일") 쪽의 모든 행에 대해 외래 키("다") 쪽에 행이 하나만 있다고 주장합니다. 이렇게 하면 EntityRef<T가 EntitySet T> 대신 "일" 쪽에서 생성됩니다.>< 유효한 값은 OneToOneOneToMany입니다.
DeleteRule 문자열 없음 이 연결에 삭제 동작을 추가하는 데 사용됩니다. 예를 들어 "CASCADE"는 FK 관계에 "ONDELETECASCADE"를 추가합니다. Null로 설정 삭제 동작이 없는 경우 추가 합니다.

함수

이 요소는 저장 프로시저 또는 데이터베이스 함수를 나타냅니다. 모든 함수 노드에 대해 메서드는 DataContext 클래스에서 생성됩니다.

함수 특성

attribute Type 기본값 Description
이름 String (필수) 데이터베이스 내 저장 프로시저의 이름입니다.
메서드 문자열 메서드 저장 프로시저 호출을 허용하는 생성할 CLR 메서드의 이름입니다. 메서드의 기본 이름에는 [dbo]와 같은 항목이 있습니다. 이름이 제거되었습니다.
AccessModifier AccessModifier 공용 저장 프로시저 메서드의 접근성 수준입니다. 유효한 값은 Public, Protected, InternalPrivate입니다.
HasMultipleResults 부울 형식 > 1의 #입니다. 함수 노드가 나타내는 저장 프로시저가 여러 결과 집합을 반환하는지를 지정합니다. 모든 결과 집합은 테이블 형식이며 기존 형식 이거나 열 집합일 수 있습니다. 후자의 경우 열 집합에 대한 형식 노드가 만들어집니다.
IsComposable 부울 아니요 함수/저장 프로시저를 LINQ to SQL 쿼리에서 구성할 수 있는지를 지정합니다. void를 반환하지 않는 DB 함수만 구성할 수 있습니다.

함수 Sub-Element 특성

Sub-Element 요소 형식 발생 범위 Description
<매개 변수> 매개 변수 0-unbounded 이 저장 프로시저의 in 및 out 매개 변수를 나타냅니다.
<ElementType> 형식 0-unbounded 해당 저장 프로시저가 반환할 수 있는 테이블 형식 셰이프를 나타냅니다.
<Return> 반환 값 0-1 이 db 함수 또는 저장 프로시저의 반환된 스칼라 형식입니다. Return이 null이면 함수는 void를 반환합니다. 함수에는 ReturnElementType이 모두 있을 수 없습니다.

TableFunction

이 요소는 테이블에 대한 CUD 재정의 함수를 나타냅니다. LINQ to SQL 디자이너를 사용하면 LINQ TO SQL에 대한 Insert, UpdateDelete 재정의 메서드를 만들 수 있으며 엔터티 속성 이름을 저장 프로시저 매개 변수 이름에 매핑할 수 있습니다.

CUD 함수의 메서드 이름은 고정되므로 TableFunction 요소에 대한 DBML에 메서드 특성이 없습니다. 예를 들어 Customer 테이블의 경우 CUD 메서드의 이름은 InsertCustomer, UpdateCustomerDeleteCustomer입니다.

테이블 함수는 테이블 형식 셰이프를 반환할 수 없으므로 TableFunction 요소에 ElementType 특성이 없습니다.

TableFunction 특성

attribute Type 기본값 Description
이름 String (필수) 데이터베이스 내 저장 프로시저의 이름입니다.
AccessModifier AccessModifier 프라이빗 저장 프로시저 메서드의 접근성 수준입니다. 유효한 값은 Public, Protected, InternalPrivate입니다.
HasMultipleResults 부울 형식 > 1의 #입니다. 이 함수 노드가 나타내는 저장 프로시저가 여러 결과 집합을 반환하는지를 지정합니다. 모든 결과 집합은 테이블 형식이며 기존 형식 이거나 열 집합일 수 있습니다. 후자의 경우 열 집합에 대한 형식 노드가 만들어집니다.
IsComposable 부울 아니요 함수/저장 프로시저를 LINQ to SQL 쿼리에서 구성할 수 있는지를 지정합니다. void를 반환하지 않는 DB 함수만 구성할 수 있습니다.

TableFunction Sub-Element 특성

Sub-Elements 요소 형식 발생 범위 Description
<매개 변수> TableFunctionParameter 0-unbounded 이 테이블 함수의 in 및 out 매개 변수를 나타냅니다.
<Return> TableFunctionReturn 0-1 이 테이블 함수의 반환된 스칼라 형식입니다. Return이 null이면 함수는 void를 반환합니다.

매개 변수

이 요소는 저장 프로시저/함수 매개 변수를 나타냅니다. 매개 변수는 데이터를 전달 및 전달할 수 있습니다.

매개 변수 특성

attribute Type 기본값 설명
이름 String (필수) 저장된 proc/function 매개 변수의 데이터베이스 이름입니다.
매개 변수 문자열 이름 메서드 매개 변수의 CLR 이름입니다.
  문자열 (필수) 메서드 매개 변수의 CLR 이름입니다.
DbType 문자열 없음 저장된 proc/function 매개 변수의 DB 형식입니다.
Direction ParameterDirection 위치 매개 변수가 흐르는 방향입니다. In, OutInOut 중 하나일 수 있습니다.

반환 값

이 요소는 저장 프로시저/함수의 반환 형식을 나타냅니다.

반환 특성

attribute Type 기본값 Description
Type String (필수) 저장된 proc/function 결과의 CLR 형식입니다.
DbType 문자열 없음 저장된 proc/function 결과의 DB 형식입니다.

TableFunctionParameter

이 요소는 CUD 함수의 매개 변수를 나타냅니다. 매개 변수는 데이터를 전달 및 전달할 수 있습니다. 모든 매개 변수는 이 CUD 함수가 속한 Table 열에 매핑됩니다. 매개 변수가 매핑되는 열에서 형식 정보를 가져올 수 있으므로 이 요소에는 Type 또는DbType 특성이 없습니다.

TableFunctionParameter 특성

attribute Type 기본값 Description
이름 String (필수) CUD 함수 매개 변수의 데이터베이스 이름입니다.
매개 변수 문자열 이름 메서드 매개 변수의 CLR 이름입니다.
문자열 이름 이 매개 변수가 매핑되는 열 이름입니다.
Direction ParameterDirection 위치 매개 변수가 흐르는 방향입니다. In, Out 또는 InOut 중 하나일 수 있습니다.
버전 버전 현재 PropertyName이 지정된 열의 현재 또는 원래 버전을 참조하는지 여부입니다. 업데이트 재정의 중에만 적용됩니다. 현재 또는 원본일 수 있습니다.

TableFunctionReturn

이 요소는 CUD 함수의 반환 형식을 나타냅니다. 실제로 CUD 함수의 결과에 매핑되는 열 이름만 포함됩니다. 반환의 형식 정보는 열에서 가져올 수 있습니다.

TableFunctionReturn 특성

Attrobite Type 기본값 Description
문자열 없음 반환이 매핑되는 열 이름입니다.

연결

이 요소는 기본 데이터베이스 연결 매개 변수를 나타냅니다. 이렇게 하면 데이터베이스에 연결하는 방법을 이미 알고 있는 DataContext 형식에 대한 기본 생성자를 만들 수 있습니다.

가능한 기본 연결에는 두 가지 유형이 있습니다. 하나는 직접 ConnectionString이고 다른 하나는 App.Settings에서 읽습니다.

연결 특성

attribute Type 기본값 Description
UseApplicationSettings 부울 아니요 직접 ConnectionString에서 App.Settings 파일을 사용할지 또는 애플리케이션설정을 가져올지를 결정합니다.
ConnectionString 문자열 없음 SQL 데이터 공급자에 보낼 연결 문자열입니다.
SettingsObjectName 문자열 설정 속성을 검색할 App.Settings 개체 입니다.
SettingsPropertyName 문자열 ConnectionString ConnectionString을 포함하는 App.Settings 속성입니다.

다중 계층 엔터티

2계층 애플리케이션에서 단일 DataContext 는 쿼리 및 업데이트를 처리합니다. 그러나 추가 계층이 있는 애플리케이션의 경우 쿼리 및 업데이트에 별도의 DataContext 인스턴스를 사용해야 하는 경우가 많습니다. 예를 들어 ASP.NET 애플리케이션의 경우 웹 서버에 대한 별도의 요청에 대해 쿼리 및 업데이트가 수행됩니다. 따라서 여러 요청에서 동일한 DataContext instance 사용하는 것은 비실용적입니다. 이러한 경우 DataContext instance 검색하지 않은 개체를 업데이트할 수 있어야 합니다. LINQ to SQL 다중 계층 엔터티 지원은 Attach() 메서드를 통해 이러한 기능을 제공합니다.

다음은 다른 DataContext instance 사용하여 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 엔터티와 다른 웹 서비스에 대한 데이터 계약을 정의할 수 있습니다. 마찬가지로 웹 페이지에는 Employee 엔터티 멤버의 하위 집합만 표시될 수 있습니다. 따라서 다중 계층 지원은 이러한 경우를 수용하도록 설계되었습니다. 다음 범주 중 하나 이상에 속하는 멤버만 Attach()를 호출하기 전에 계층 간에 전송하고 설정해야 합니다.

  1. 엔터티 ID의 일부인 멤버입니다.
  2. 변경된 멤버입니다.
  3. 낙관적 동시성에 참여하는 멤버는 검사.

낙관적 동시성 검사 타임스탬프 또는 버전 번호 열을 사용하는 경우 Attach()를 호출하기 전에 해당 멤버를 설정해야 합니다. Attach()를 호출하기 전에 다른 멤버에 대한 값을 설정할 필요가 없습니다. LINQ to SQL 낙관적 동시성 검사를 통해 최소한의 업데이트를 사용합니다. 즉, 낙관적 동시성을 설정하거나 확인하지 않은 멤버는 무시됩니다.

낙관적 동시성 검사에 필요한 원래 값은 LINQ to SQL API의 scope 외부의 다양한 메커니즘을 사용하여 보존할 수 있습니다. ASP.NET 애플리케이션은 뷰 상태(또는 뷰 상태를 사용하는 컨트롤)를 사용할 수 있습니다. 웹 서비스는 업데이트 메서드에 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 파일입니다. 파일 매핑을 사용하면 코드에서 매핑을 분리하는 것이 바람직한 추가 시나리오를 사용할 수 있습니다.

DataContextMappingSource를 제공하기 위한 추가 생성자를 제공합니다. MappingSource의 한 가지 형태는 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

  • 산술 및 비교 연산자
  • Shift 연산자: << 및 >>
  • char와 numeric 간의 변환은 UNICODE/NCHAR에서 수행됩니다. 그렇지 않으면 SQL의 CONVERT 가 사용됩니다.

구현되지 않음

  • <형식>입니다. 구문 분석
  • 열거형을 사용하고 테이블의 정수 및 문자열에 매핑할 수 있습니다. 후자의 경우 ParseToString() 메서드가 사용됩니다.

.NET과 차이점

  • Double용 ToString의 출력은 SQL에서 CONVERT(NVARCHAR(30), @x, 2)를 사용하며 항상 16자리 및 "공학 표기법"을 사용합니다. 예를 들어 0의 경우 "0.00000000000000000e+000"이므로 와 동일한 문자열을 제공하지 않습니다. NET의 Convert.ToString().

System.String

Implemented

  • 비정적 메서드:

    • Length, Substring, Contains, StartsWith, EndsWith, IndexOf, Insert, Remove, Replace, Trim, ToLower, ToUpper, LastIndexOf, PadRight, PadLeft, Equals, CompareTo. 아래에 설명된 대로 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 인스턴스, 데이터베이스, 테이블 열 또는 식에 지정할 수 있습니다.

지금까지 구현된 함수의 변환은 데이터 정렬을 변경하거나 번역된 식에 다른 데이터 정렬을 지정하지 않습니다. 따라서 기본 데이터 정렬이 대/소문자를 구분하지 않는 경우 CompareTo 또는 IndexOf 와 같은 함수는 (대/소문자 구분) .NET 함수가 제공하는 것과 다른 결과를 제공할 수 있습니다.

StartsWith(str)/EndsWith(str) 메서드는 인수 str가 클라이언트에서 평가되는 상수 또는 식이라고 가정합니다. 즉, 현재 str에 열을 사용할 수 없습니다.

System.Math

구현된 정적 메서드

  • 모든 서명:
    • Abs, Acos, Asin, Atan, Atan2, BigMul, Ceiling, Cos, Cos, Cosh, Exp, Floor, Log, Log10, Max, Min, Pow, Sign, Sinh, Sqrt, Tan, Tanh 또는 Truncate.

구현되지 않음

  • IEEERemainder.
  • DivRem 에는 out 매개 변수가 있으므로 식에서 사용할 수 없습니다. Math.PIMath.E 상수는 클라이언트에서 평가되므로 번역이 필요하지 않습니다.

.NET과 차이점

.NET 함수 Math.Round 의 변환은 SQL 함수 ROUND입니다. 변환은 MidpointRounding 열거형 값을 나타내는 오버로드가 지정된 경우에만 지원됩니다. MidpointRounding.AwayFromZero는 SQL 동작이고 MidpointRounding.ToEven은 CLR 동작을 나타냅니다.

System.Convert

Implemented

  • 형식의 메서드 To<Type1>(<Type2> x) 여기서 Type1, Type2는 다음 중 하나입니다.
    • bool, byte, char, DateTime, decimal, double, float, Int16, Int32, Int64 또는 string입니다.
  • 동작은 캐스트와 동일합니다.
    • ToString(Double)의 경우 전체 정밀도를 얻기 위한 특수 코드가 있습니다.
    • Int32/Char 변환의 경우 LINQ to SQL SQL의 UNICODE/NCHAR 함수를 사용합니다.
    • 그렇지 않으면 변환이 CONVERT입니다.

구현되지 않음

  • ToSByte, UInt16, 32, 64: 이러한 형식은 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에서 공유됨) 메서드:

       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)
    
  • 정적(공유) 메서드:

       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 날짜/시간 범위는 1753년 1월 1일에 시작됩니다.

SQL에는 TimeSpan에 대한 기본 제공 형식이 없습니다. 32비트 정수는 서로 다른 DATEDIFF 메서드를 사용합니다. 하나는 일 수를 제공하는 DATEDIFF(DAY,...)입니다. 다른 하나는 DATEDIFF(MILLISECOND,...)로, 밀리초 수를 제공합니다. DateTimes가 24일 이상 떨어져 있으면 오류가 발생합니다. 반면 .NET은 64비트 정수 를 사용하고 TimeSpans를 틱 단위로 측정합니다.

SQL의 .NET 의미 체계에 최대한 근접하기 위해 LINQ to SQL TimeSpans를 64비트 정수로 변환하고 위에서 언급한 두 DATEDIFF 메서드를 사용하여 두 날짜 사이의 틱 수를 계산합니다.

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 기능을 이해하고 특정 문제를 디버깅하는 데 유용할 수 있습니다.

DataContext 메서드를 사용하여 생성된 SQL 가져오기

멤버 용도
로그 SQL이 실행되기 전에 인쇄합니다. 쿼리, 삽입, 업데이트, 삭제 명령을 다룹니다. Usage:

C#

db.Log = Console.Out;

Visual Basic

db.Log = Console.Out

GetQueryText(query) 쿼리를 실행하지 않고 쿼리의 쿼리 텍스트를 반환합니다. Usage:

C#

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

Visual Basic

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

GetChangeText() 삽입/업데이트/삭제를 실행하지 않고 SQL 명령의 텍스트를 반환합니다. Usage:

C#

Console.WriteLine(db.GetChangeText());

Visual Basic

Console.WriteLine(db.GetChangeText())