using System;
using System.ComponentModel;

namespace DGRowDetails
{
    public class Task : System.ComponentModel.INotifyPropertyChanged
    {
        // The Task class implements INotifyPropertyChanged so that 
        // the datagrid row will be notified of changes to the data
        // that are made in the row details section.

        // Private task data.
        private string m_Name;
        private DateTime m_DueDate;
        private bool m_Complete;
        private string m_Notes;

        // Define the public properties.
        public string Name
        {
            get { return this.m_Name; }
            set
            {
                if (value != this.m_Name)
                {
                    this.m_Name = value;
                    NotifyPropertyChanged("Name");
                }
            }
        }

        public DateTime DueDate
        {
            get { return this.m_DueDate; }
            set
            {
                if (value != this.m_DueDate)
                {
                    this.m_DueDate = value;
                    NotifyPropertyChanged("DueDate");
                }
            }
        }

        public bool Complete
        {
            get { return this.m_Complete; }
            set
            {
                if (value != this.m_Complete)
                {
                    this.m_Complete = value;
                    NotifyPropertyChanged("Complete");
                }
            }
        }

        public string Notes
        {
            get { return this.m_Notes; }
            set
            {
                if (value != this.m_Notes)
                {
                    this.m_Notes = value;
                    NotifyPropertyChanged("Notes");
                }
            }
        }

        // Implement INotifyPropertyChanged interface.
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}


Silverlight 2를 사용하여 데이터 중심 웹 응용 프로그램 만들기

출처 : http://msdn.microsoft.com/ko-kr/magazine/cc794279.aspx

Data Services

Silverlight 2를 사용하여 데이터 중심 웹 응용 프로그램 만들기

Shawn Wildermuth

코드 다운로드 위치: SLDataServices2008_09a.exe (234KB)
온라인으로 코드 찾아보기

이 기사는 Silverlight 2 및 ADO.NET 데이터 서비스 시험판 버전을 기준으로 합니다. 여기에 포함된 모든 정보는 변경될 수 있습니다.

이 기사에서는 다음 내용에 대해 설명합니다.

  • 데이터 원본에 Silverlight 연결
  • ADO.NET 데이터 서비스 사용
  • LINQ to SQL 사용
  • 서비스 디버깅

이 기사에서 사용하는 기술:
Silverlight 2 베타 2, LINQ, ADO.NET 데이터 서비스

목차

ADO.NET 데이터 서비스
서비스 만들기
데이터 쿼리 및 업데이트
Silverlight 2.0 클라이언트 라이브러리
관련 엔터티 로드
변경 관리
서비스를 통해 업데이트
서비스 디버깅
정리

Silverlight™에서 기간 업무(LOB) 및 기타 데이터 중심 응용 프로그램을 작성할 수 있지만 Silverlight에서의 데이터 작업이 항상 간단하지만은 않습니다. Silverlight에는 데이터를 소비하고 웹 서비스 및 XML을 지원하는 데 필요한 많은 도구가 포함되어 있지만 이러한 도구는 방화벽을 통한 데이터 액세스의 기본 요소일 뿐입니다.

일반적인 데이터 액세스 전략에서는 웹 서비스와 클라이언트 쪽 LINQ를 섞어 사용하는데, 이는 기존 웹 서비스 끝점을 활용하여 Silverlight 응용 프로그램을 구동할 경우에 가장 권장되는 방법입니다. 그러나 특별히 Silverlight에서 작동하도록 새로운 웹 서비스를 구축하는 경우에는 불필요한 비용이 수반됩니다.

일반적인 웹 서비스 계층의 경우 서버에 기존의 데이터 액세스 전략을 구현하고(사용자 지정 비즈니스 개체, LINQ to SQL, Entity Framework, NHibernate 등) 웹 서비스를 통해 데이터 개체를 노출합니다. 이 경우 웹 서비스는 기본 데이터 액세스 전략에 대한 게이트웨이일 뿐입니다.

그러나 완전한 데이터 연결을 활성화하려면 4개의 데이터 작업(Create, Read, Update 및 Delete)을 웹 서비스 메서드에 매핑해야 합니다. 예를 들어 Product 클래스를 지원하기 위한 간단한 서비스 계약은 그림 1과 비슷할 것입니다. 이 기사에서는 C#을 사용하지만 코드 다운로드에는 Visual Basic® 코드도 포함되어 있습니다.

그림 1 간단한 Product 웹 서비스에 대한 서비스 계약

코드 복사

[ServiceContract]
public interface ICustomerService
{
  [OperationContract]
  List<Product> GetAllProducts();

  [OperationContract]
  Product GetProduct(int productID);

  [OperationContract]
  List<Product> GetAllProductsWithCategories();

  [OperationContract]
  Product SaveProduct(Product productToSave);

  [OperationContract]
  void DeleteProduct(Product productToDelete);
}
한 
응용 프로그램의 전체 데이터 모델과 함께 작동하는 서비스 집합을 만들려면 상당한 시간이 걸릴 수 있습니다. 그리고 이 예에서 볼 수 있듯이 기능별 작업으로 인해 웹 서비스가 불필요하게 커질 수 있습니다. 즉, 시간이 지남에 따라 웹 서비스에는 새로운 요구 사항과 작업이 추가되고, 여기에는 핵심 비즈니스 영역에 속하지 않는 작업도 포함됩니다. 

예를 들어 그림 1에서는 기본적으로 Product를 가져오고 범주를 포함하는 데 사용되는 GetAllProductsWithCategories 작업을 볼 수 있습니다. 이 간단한 예에도 정렬, 필터링 및 페이징 메커니즘이 추가된다는 것쯤은 쉽게 짐작할 수 있습니다. 이러한 모든 메커니즘을 계속 수동으로 작성하지 않고도 쿼리, 정렬, 필터링과 같은 데이터 작업을 지원하는 간단한 방법이 있다면 좋을 것입니다. 바로 이 부분에서 ADO.NET 데이터 서비스가 진가를 발휘합니다.

ADO.NET 데이터 서비스

ADO.NET 데이터 서비스의 목표는 데이터 모델에 웹 액세스가 가능한 끝점을 제공하는 것입니다. 이 끝점을 사용하면 개발자는 이러한 작업을 위한 기능을 사용자 지정하여 구축하지 않고도 서버의 데이터를 필터링하고, 정렬하고, 표현하고, 페이징할 수 있습니다. 기본적으로 각 끝점은 LINQ 쿼리의 시작 지점입니다. 끝점에서 여러분이 실제로 찾는 데이터에 대한 쿼리를 수행할 수 있습니다.

그러나 ADO.NET 데이터 서비스를 많은 데이터 액세스 전략 중의 하나로 치부하지는 마십시오. ADO.NET 데이터 서비스는 데이터 액세스를 직접 수행하지 않습니다. 사실 ADO.NET 데이터 서비스는 직렬화, 쿼리 및 업데이트를 지원하는 데이터 액세스 기술 위에 위치합니다. 데이터 쿼리 및 업데이트 작업은 이 기본 데이터 액세스 계층에서 담당합니다. 그림 2는 일반적인 응용 프로그램 아키텍처에서 ADO.NET 데이터 서비스의 위치를 보여 줍니다.

그림 2 ADO.NET 데이터 서비스 계층 (더 크게 보려면 이미지를 클릭하십시오.)

ADO.NET 데이터 서비스는 데이터 액세스 기능에 의존하여 실제 데이터 액세스 작업을 수행하므로 이러한 작업의 형태를 지정할 방법이 필요합니다. ADO.NET 데이터 서비스에서 각 서비스는 LINQ 지원 공급자의 지원을 받아야 합니다. 사실 각 끝점은 하나의 IQueryable 끝점에 불과합니다. 따라서 ADO.NET 데이터 서비스는 IQueryable을 지원하는 모든 개체를 지원할 수 있습니다.

서비스 만들기

ADO.NET 데이터 서비스를 프로젝트에 추가하면 서비스를 나타내는 클래스와 함께 새로운 .svc 파일이 만들어집니다. 웹 서비스와는 달리 서비스 작업을 직접 구현하지 않고 DataService 클래스가 대부분의 주요 작업을 처리하도록 합니다. 서비스를 실행하려면 몇 가지 간단한 작업이 필요합니다. 첫째, DataService 클래스에 context 개체라고 하는 형식 매개 변수가 필요합니다. context 개체는 서비스로 노출될 데이터를 설명하는 클래스입니다. 서비스가 관계형 데이터베이스의 데이터를 노출하는 경우 이 클래스는 일반적으로 EntityFramework의 ObjectContext 또는 LINQ to SQL의 DataContext에서 파생됩니다.

코드 복사

// Use my NorthwindEntities context object 
// as the source of the Service's Data
public class Products : DataService<NorthwindEntities>

context 개체에 대한 기본 클래스 요구 사항은 없습니다. 사실 속성이 IQueryable 인터페이스를 구현한다는 조건만 충족하면 직접 만들 수 있습니다. ADO.NET 데이터 서비스는 이러한 속성을 끝점으로 노출합니다.

코드 복사

public class StateContext
{
  StateList _states = new StateList();
  public IQueryable<State> States 
  {
    get { return _states.AsQueryable<State>(); } 
  }
}

InitializeService 호출에서는 서비스에서 허용할 사용 권한 유형을 지정하는 데 사용할 수 있는 IDataServiceConfiguration 개체가 제공됩니다. ADO.NET 데이터 서비스는 그림 3에서 볼 수 있듯이 명사와 동사를 추상화하여 사용 권한을 지정합니다.

그림 3 액세스 규칙 설정


// This method is called only once to initialize service-wide policies.
public static void InitializeService(IDataServiceConfiguration config)
{
  // Only allow us to read or update Products Entities
  // not Delete or Create
  config.SetEntitySetAccessRule("Products", 
                                EntitySetRights.AllRead | 
                                EntitySetRights.WriteUpdate);

  // Only Allow Reading of Category and Supplier Entities
  config.SetEntitySetAccessRule("Categories", EntitySetRights.AllRead);
  config.SetEntitySetAccessRule("Suppliers", EntitySetRights.AllRead);

}

이 작업이 완료되면 서비스로 직접 이동할 수 있고, 이렇게 하면 각 끝점에 대한 Atom 피드 정보가 표시됩니다. ADO.NET 데이터 서비스를 디버깅하려면 Internet Explorer®에서 RSS 피드 보기를 비활성화하거나 다른 브라우저를 사용하여 XML 형식의 서비스를 보는 것이 좋습니다.

데이터 쿼리 및 업데이트

ADO.NET 데이터 서비스는 서비스를 SOAP 기반 서비스가 아닌 REST(Representational State Transfer) 기반 서비스로 노출합니다. 이는 SOAP 봉투 대신 서비스의 응답 페이로드에 요청 메타데이터가 아닌 데이터만 포함됨을 의미합니다. 모든 요청은 HTTP 동사(GET, PUT, POST 등)와 요청 URI를 조합하여 기술됩니다. 그림 4와 같이 Product, Category 및 Supplier를 기술하는 모델이 있다고 가정해 보겠습니다. ADO.NET 데이터 서비스에는 모델의 각 엔터티 집합당 하나씩 3개의 끝점이 있을 수 있습니다. 모델에 있는 엔터티 집합의 주소를 지정하는 URI는 서비스의 주소와 끝점의 이름입니다(http://localhost/{서비스 이름}/{끝점 이름} 또는 http://localhost/Product.svc/Products).

그림 4 샘플 데이터 모델 (더 크게 보려면 이미지를 클릭하십시오.)

이 URI 구문은 특정 엔터티 검색, 결과의 정렬, 필터링, 페이징, 표현 등 다양한 기능을 지원합니다.

ADO.NET 데이터 서비스는 이러한 URL 스타일 쿼리를 사용하여 서비스 소비자에게 데이터를 반환합니다. 현재는 JSON(JavaScript Object Notation)과 Atom 기반 XML의 두 가지 직렬화 형식만 지원되지만 향후 버전에서 확장될 가능성이 높습니다. JSON은 클라이언트 쪽 웹 코드에서 사용하기에 편리한 형식이고, Atom은 나중에 XML 파서의 지원이 필요한 XML 기반 형식입니다.

쿼리에 직렬화 형식을 지정할 필요가 없는 대신 ADO.NET 데이터 서비스는 표준 HTTP Accept 헤더를 사용하여 클라이언트로 반환할 형식을 결정합니다. XML을 사용할 수 있는 클라이언트(예: 브라우저)에서 요청을 실행하는 경우, 그리고 Accept 헤더를 통해 기본 형식 유형을 지정하지 않는 경우 반환되는 데이터의 기본 형식은 Atom입니다.

데이터 쿼리는 솔루션의 일부일 뿐입니다. 궁극적으로 솔루션은 쿼리와 업데이트를 모두 지원해야 합니다. 이러한 요구 사항을 모두 지원하기 위해 ADO.NET 데이터 서비스는 4개의 기본 데이터 액세스 작업을 4개의 기본 HTTP 동사에 매핑합니다(그림 5 참조).

그림 5 데이터 액세스 동사와 HTTP 동사

데이터 액세스 동사
HTTP 동사

Create
POST

Read
GET

Update
PUT

Delete
DELETE

ADO.NET 데이터 서비스는 이러한 동사를 사용하여 서비스 소비자가 다양한 유형에 대한 특수한 끝점을 만들지 않고도 모든 유형의 데이터 작업을 활용할 수 있도록 합니다. ADO.NET 데이터 서비스 내에서 데이터 업데이트가 작동하도록 하기 위한 유일한 요구 사항은 IUpdatable 인터페이스를 지원하는 기본 데이터 액세스 기술에 대한 것입니다. 이 인터페이스는 업데이트가 ADO.NET 데이터 서비스에서 데이터 원본으로 전달되는 방식을 정의하는 계약입니다.

현재는 Entity Framework가 이 인터페이스를 지원하는 유일한 데이터 액세스 기술이지만 앞으로는 대부분의 계층에서 이에 대한 지원이 통합될 것입니다(LINQ to SQL, LLBGenPro 및 NHibernate 포함). ADO.NET 데이터 서비스에 대한 실무적인 지식을 익혔으니 이제 Silverlight 2에서 사용할 수 있습니다.

Silverlight 2.0 클라이언트 라이브러리

ADO.NET 데이터 서비스를 사용하여 URI 구문을 통해 데이터를 업데이트하고 쿼리를 실행하고 XML을 직접 조작하려는 경우 원하는 기능을 얻을 수는 있지만 상당한 양의 코딩 작업이 필요합니다. 바로 이 부분에서 Silverlight용 ADO.NET 데이터 서비스 클라이언트 라이브러리를 활용할 수 있습니다. 명령줄 도구와 함께 이 라이브러리를 사용하면 Silverlight 응용 프로그램에서 직접 LINQ 쿼리를 실행할 수 있습니다. 그러면 클라이언트 라이브러리에서 이러한 쿼리를 HTTP 쿼리 또는 데이터 서비스에 대한 업데이트 요청으로 변환합니다.

시작하려면 먼저 몇 가지 코드를 생성해야 합니다. 이 코드 생성에서는 ADO.NET 데이터 서비스 서비스의 메타데이터를 읽고 서비스의 엔터티에 대한 데이터 전용 클래스와 서비스 자체를 나타내는 클래스를 생성합니다.

이 코드를 생성하려면 Microsoft® .NET Framework 3.5 폴더(일반적으로 c:\windows\Microsoft.NET\Framework\v3.5\)에 있는 DataSvcUtil.exe 도구를 사용합니다. 이 도구를 실행하고 서비스의 URI, 출력 코드 파일, 코드를 작성하는 데 사용할 언어 등을 지정할 수 있습니다.

 

DataSvcUtil.exe –uri:http://localhost/Product.svc –out:data.cs  
  –lang:CSharp

이렇게 하면 각 끝점에 대한 데이터 계약 클래스와 DataServiceContext 파생 클래스가 포함된 새 파일이 작성됩니다. DataServiceContext 클래스는 서비스 진입점으로 사용되며 쿼리 가능한 서비스 끝점을 노출합니다. 이 클래스를 Silverlight 프로젝트에 추가하고 System.Data.Services.Client.dll(Silverlight 2 베타 2 SDK에 속함)에 대한 참조를 추가하면 ADO.NET 데이터 서비스로 작업하는 데 필요한 모든 코드를 얻을 수 있습니다.

Silverlight 클라이언트 코드는 .NET 대상 코드에서 작성할 수 있는 다른 LINQ 기반 쿼리와 유사합니다. DataServiceContext 파생 클래스를 만들고 이에 대한 LINQ 쿼리를 실행합니다. 코드는 다음과 같습니다.


// Create the Service class specifying the 
// location of the ADO.NET Data Services 
NorthwindEntities ctx = 
  new NorthwindEntities(new Uri("Products.svc", UriKind.Relative));

// Create a LINQ Query to be issued to the service
var qry = from p in ctx.Products
               orderby p.ProductName
                select p;

이 쿼리를 실행하면 원하는 데이터를 가져오기 위한 웹 요청이 실행됩니다. 그러나 Silverlight는 동기 웹 요청을 허용하지 않는다는 점에서 여기에 나온 Silverlight 코드는 표준 LINQ 쿼리와 크게 다릅니다. 따라서 비동기적으로 실행하기 위해 다음과 같이 먼저 쿼리를 DataServiceQuery<T> 개체로 캐스팅한 다음 BeginExecute를 명시적으로 호출하여 비동기 실행을 시작해야 합니다.

 

// Cast to a DataServiceQuery<Product> 
// (since the query is returning Products)
DataServiceQuery<Product> productQuery =
  (DataServiceQuery<Product>)qry;

  // Execute the Query Asynchronously specifying 
  // a callback method
  productQuery.BeginExecute(new 
    AsyncCallback(OnLoadComplete),
    productQuery);

쿼리가 실행되면 작업의 성공 여부에 관계없이 AsyncCallback에 지정된 메서드가 실행됩니다. 이때 LINQ 쿼리를 열거하여 실제 결과를 처리할 수 있습니다. 일반적으로 AsyncCallback에 원래 쿼리를 포함하면 콜백 메서드에서 이를 검색하거나 클래스의 일부로 저장할 수 있습니다(그림 6 참조).

그림 6 컬렉션에 결과 추가

코드 복사

void OnLoadComplete(IAsyncResult result)
{
  // Get a reference to the Query
  DataServiceQuery<Product> productQuery =
    (DataServiceQuery<Product>)result.AsyncState;

  try
  {
    // Get the results and add them to the collection
    List<Product> products = productQuery.EndExecute(result).ToList();

  }
  catch (Exception ex)
  {
    if (HtmlPage.IsEnabled)
    {
      HtmlPage.Window.Alert("Failed to retrieve data: " + ex.ToString());
    }
  }

}

LINQ를 다루어 본 경험이 없다면 이 패턴이 상당히 생소할 것입니다. 현재로서는 비동기 패키지에서 LINQ를 실행하는 것(예: ThreadPool 및 BackgroundWorker) 외에는 비동기 LINQ에 대한 좋은 패턴이 없습니다. Silverlight에서는 모든 요청이 비동기적이어야 하므로 ADO.NET 데이터 서비스 클라이언트 라이브러리를 사용할 때 이 패턴이 필요합니다.

관련 엔터티 로드

ADO.NET 데이터 서비스는 관련 엔터티를 로드하는 방식을 선택할 수 있도록 합니다. 이전 예에서는 서버에서 제품을 로드했습니다. 각 제품은 해당 제품의 공급업체 및 범주와 관련되어 있습니다.

이전 LINQ 쿼리를 사용할 때는 제품만 검색했습니다. 공급업체 또는 범주 정보를 보여 주려고 했다면 이러한 정보에 즉시 액세스할 수 없었을 것입니다. 필요할 때 이러한 정보를 로드하거나 서버에 대한 원래 쿼리에서 명시적으로 검색해야 했을 것입니다. 두 방법은 각기 장점이 있지만 일반적으로 모든 개체에 대한 정보가 필요한 경우에는 명시적 로딩이 훨씬 더 효율적입니다. 몇 가지 엔터티에 대한 데이터만 로드하려는 경우에는 필요 시 검색이 더 좋습니다.

기본적으로 관계 속성(예: Product.Supplier)이 있을 때 속성을 명시적으로 로드하지 않으면 해당 속성은 Null이 됩니다. 필요 시 로딩을 지원하기 위해 DataServiceContext 클래스에는 동일한 비동기 모델을 따르는 BeginLoadProperty 메서드가 있습니다. 이 메서드에서 원본 엔터티, 속성 이름 및 콜백을 지정할 수 있습니다.

 

public void LoadSupplierAsync(Product theProduct)
{
  TheContext.BeginLoadProperty(theProduct, 
                               "Supplier", 
                               new AsyncCallback(SupplierLoadComplete), 
                               null);
  }

  public void SupplierLoadComplete(IAsyncResult result)
  {
    TheContext.EndLoadProperty(result);
  }
EndLoadProperty
가 호출되면 속성에 관련 엔터티가 적절히 로드된 것입니다. 원래 쿼리에서 이를 명시적으로 로드해야 하는 경우가 많습니다. 이를 용이하게 수행할 수 있도록 LINQ 공급자는 Expand 확장 메서드를 지원합니다. 이 메서드를 사용하면 쿼리가 실행될 때 로드할 속성 경로의 이름을 지정할 수 있습니다. Expand 확장 메서드는 LINQ 쿼리의 From 절에서 공급자에게 관련 엔터티를 로드하도록 지시하는 데 사용됩니다. 예를 들어 Category와 Supplier 모두에 대해 Expand 메서드를 사용하도록 원래 쿼리를 변경하는 경우 원래 쿼리가 실행되는 동안 개체가 이러한 관련 엔터티를 로드하게 됩니다.

코드 복사

var qry = 
  from p in TheContext.Products.Expand("Supplier").Expand("Category")
          orderby p.ProductName
          select p; 

ADO.NET 데이터 서비스에서 데이터를 읽기만 한다면 더 이상 수행할 작업이 없습니다. 쿼리를 만들고, 실행하고, 관련 엔터티를 로드하는 방법만 알면 됩니다. 그러나 데이터를 수정하려는 경우에는 작업이 더 남아 있습니다. 새로운 데이터를 Silverlight 컨트롤에 바인딩하고 원하는 대로 수정하십시오.

변경 관리

ADO.NET 데이터 서비스 클라이언트 라이브러리는 개체에 대한 변경 자동 모니터링을 지원하지 않습니다. 따라서 개체, 컬렉션 및 관계가 변경되면 개발자가 직접 DataServiceContext 개체에 이러한 변경에 대해 알려야 합니다. DataServiceContext 개체에 알리기 위한 API는 상당히 간단하며 그림 7에 설명되어 있습니다.

그림 7 DataServiceContext 변경 API

메서드
설명

AddObject
새로 만든 개체를 추가합니다.

UpdateObject
개체가 변경되었음을 표시합니다.

DeleteObject
개체를 삭제하도록 표시합니다.

AddLink
두 개체 간 링크를 추가합니다.

UpdateLink
두 개체 간 링크를 업데이트합니다.

DeleteLink
두 개체 간 링크를 삭제합니다.

따라서 개발자는 개체에 대한 변경을 모니터링하고 직접 코드를 작성하여 DataServiceContext 개체에 이를 알려야 합니다. 겉보기에는 자동 변경 관리 기능이 없다는 점이 실망스러울 수 있지만 이를 통해 라이브러리는 효율성과 작은 크기를 확보할 수 있습니다.

개체에 대한 변경을 모니터링하는 방법이 궁금할 것입니다. 해답은 생성된 코드에 있습니다. 생성된 각 데이터 계약 클래스에는 클래스 변경에서 데이터로 호출되는 부분 메서드가 있습니다. 부분 메서드에 대한 자세한 내용은 go.microsoft.com/fwlink/?LinkId=122979를 참조하십시오. 이러한 메서드가 구현되지 않는다면 클래스가 이러한 변경 알림에서 오버헤드를 유발하지 않습니다. 부분 메서드 메커니즘은 변경에 따른 변경 알림 구현을 지원하는 모든 데이터 계약에서 사용할 수 있습니다. DataServiceContract 클래스를 결합할 필요 없이 부분 메서드에서 DataServiceContract를 호출하기만 하면 됩니다.

다행히 Silverlight는 이미 변경 알림을 위한 인터페이스(INotifyPropertyChange)를 지원합니다. 데이터 변경의 영향을 받는 사용자에게 변경을 알리기 위한 구현에서 이 인터페이스를 활용하십시오. 예를 들어 데이터 계약 클래스(예에서는 Product 클래스)에서 INotifyPropertyChange를 구현하여 개체가 변경될 때 실행할 이벤트를 정의할 수 있습니다. 코드는 다음과 같습니다.

 

public partial class Product : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;
}

이를 구현하면 속성이 변경될 때마다 이벤트가 발생되도록 할 수 있습니다. 생성된 데이터 계약 클래스가 호출하는 부분 메서드를 구현하여 이러한 이벤트가 발생하는 시기를 결정할 수 있습니다. 예를 들어 ProductName 속성이 변경될 때 구독자에게 알리려면 OnProductNameChanged 부분 메서드를 구현한 다음 PropertyChanged 이벤트를 발생시켜 이벤트 구독자가 변경된 속성을 식별할 수 있도록 ProductName을 전달하기만 하면 됩니다. 코드는 다음과 같습니다.

 

partial void OnProductNameChanged()
{
  if (PropertyChanged != null)
  {
    PropertyChanged(this, new PropertyChangedEventArgs("ProductName"));
  }  
}

모든 쓰기 가능 속성에 대해 이러한 부분 메서드를 구현하면 개체에 대한 변경을 간단하게 모니터링할 수 있습니다. 그런 다음 PropertyChanged 이벤트에 등록하고 개체가 변경될 때 DataServiceContext 개체에 알릴 수 있습니다.

코드 복사

// In the OnLoadComplete method
// Get the results and add them to the collection
List<Product> products = productQuery.EndExecute(result).ToList();

foreach (Product product in products)
{
  // Wireup Change Notification
  product.PropertyChanged += 
    new PropertyChangedEventHandler(product_PropertyChanged);
}

마지막으로 product_PropertyChanged 메서드를 구현하여 DataServiceContext 개체에 알릴 수 있습니다.

 

void product_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
  Product product = (Product)sender;
  TheContext.UpdateObject(product);
}

마찬가지로 개체가 만들어지거나 삭제될 때도 그림 8에서와 같이 DataServiceContext에 알려야 합니다.

그림 8 알림 만들기 및 삭제

코드 복사

void addNewButton_Click(object sender, RoutedEventArgs e)
{
  Product theProduct = new Product();
  // ...
  TheContext.AddObject(theProduct);
}

void deleteButton_Click(object sender, RoutedEventArgs e)
{
  Product theProduct = (Product)theList.SelectItem;
  TheContext.DeleteObject(theProduct);
  theCollection.Remove(theProduct);
}

이러한 코드를 모두 작성하면 Silverlight UI에서 개체를 변경하고 데이터 바인딩과 변경 알림 코드를 사용하여 발생할 변경을 DataServiceContex에 알릴 수 있습니다. 그런데 서비스에 대한 실제 업데이트는 어떻게 수행할까요?

서비스를 통해 업데이트

DataServiceContext 개체가 데이터 변경에 대해 알게 되었으므로 이제 이러한 변경 내용을 서버에 전달할 방법이 필요합니다. 이를 지원하기 위해 DataServiceContext 클래스에는 앞서 설명한 쿼리와 동일한 비동기 모델을 따르는 BeginSaveChanges 메서드가 있습니다. BeginSaveChanges 메서드는 다음과 같이 DataServiceContext의 모든 변경 내용을 받아 서버로 전송합니다.

코드 복사

TheContext.BeginSaveChanges(SaveChangesOptions.None, 
                            new AsyncCallback(OnSaveAllComplete), 
                            null);

BeginSaveChanges를 실행할 때는 SaveChangesOptions라는 플래그가 지정된 열거형이 있습니다. 이 열거형을 사용하여 일괄 처리를 사용할지, 일부 개체가 저장에 실패하는 경우에도 계속할지를 지정할 수 있습니다. 일반적으로 항상 일괄 처리를 사용하는 것이 좋습니다. 사실 서버에 특정 참조 무결성 제약이 있는 경우 올바른 업데이트를 위해서는 일부 부모/자식 관계에 일괄 처리가 필요합니다.

저장이 완료되면 콜백이 실행됩니다. 오류 정보를 전달하는 메커니즘은 두 가지입니다. 첫째, 저장 실행 중에 발생한 예외가 있으면 콜백에서 "EndSaveChanges"를 호출할 때 이 예외가 발생합니다. 따라서 try/catch 블록을 사용하여 심각한 오류를 잡아야 합니다. 또한 EndSaveChanges의 반환 형식은 DataServiceResponse 개체입니다. DataServiceResponse에는 HasErrors 속성(그림 9 참조)이 있지만 Silverlight 2 베타 2 버전의 라이브러리에서는 불안정합니다.

그림 9 BeginSaveChanges 콜백

코드 복사

void OnSaveAllComplete(IAsyncResult result)
{
  bool succeeded = true;
  try
  {
    DataServiceResponse response = 
      (DataServiceResponse)TheContext.EndSaveChanges(result);

    foreach (OperationResponse opResponse in response)
    {
      if (opResponse.HasErrors)
      {
        succeeded = false;
      }
    }

  }
  catch (Exception ex)
  {
    succeeded = false;
  }

  // Alert the User
}

추가 정보

여기에 의존하는 대신 실제 OperationResponse 개체를 반복하여(DataServiceResponse는 OperationResponse 개체의 컬렉션임) 서버에서 반환된 각 응답에 오류가 있는지 확인할 수 있습니다. 이후 버전에서는 DataServiceResponse 클래스 자체의 HasErrors 속성을 사용할 수 있게 될 것입니다.

서비스 디버깅

서비스를 디버깅하는 동안 수행해야 할 중요한 작업 세 가지는 DataServiceContext 개체의 데이터 상태 보기, ADO.NET 데이터 서비스의 요청 보기, 그리고 마지막으로 서버 오류 잡기입니다.

먼저 DataServiceContext 개체의 엔터티 상태부터 살펴보겠습니다. DataServiceContext 클래스는 Entities와 Links라는 두 가지 유용한 컬렉션을 노출합니다. 이러한 컬렉션은 엔터티와 DataServiceContext에서 추적하는 엔터티 간 링크가 포함된 읽기 전용 컬렉션입니다. 디버깅할 때는 개체의 변경 표시 여부에 관계없이 디버거에서 이러한 컬렉션을 관찰하여 변경 추적 코드가 올바르게 작동하고 있는지 확인하는 것이 중요합니다.

또한 Silverlight 2 응용 프로그램의 실제 서버 요청도 확인해야 합니다. 가장 좋은 방법은 일종의 네트워크 프록시를 사용하는 것입니다. 필자는 이러한 프록시로 Fiddler2 (fiddler2.com)를 사용합니다. Fiddler2에 익숙하지 않은 독자를 위해 설명하자면, 이 프로그램은 전송 시 발생하는 일을 확인하기 위한 웹 트래픽 관찰 도구입니다.

ADO.NET 데이터 서비스의 경우 실제로 수행되는 통신을 확인하여 Silverlight 응용 프로그램에서 송수신되는 트래픽을 살펴보아야 합니다. 자세한 내용은 필자의 블로그(wildermuth.com/2008/06/07/Debugging_ADO_NET_Data_Services_with_Fiddler2)를 참조하십시오.

마지막으로 최신 .NET Framework 3.5 SP1에서 서버 쪽 오류는 클라이언트로 잘 전달되지 않습니다. 사실 서버에서 발생하는 대부분의 오류는 서버 내에서만 처리됩니다. 서버 오류를 디버깅하기 위한 가장 좋은 방법은 디버그 메뉴의 예외 옵션(디버그 -> 예외…)을 사용하여 모든 .NET 예외를 확인하고 넘어가도록 디버거를 구성하는 것입니다. 이 옵션을 선택하면 서비스에서 일으키는 예외를 볼 수 있습니다. 다만 처음 발생하는 다른 모든 예외에 대해 "계속"을 클릭해야 합니다.

정리

이 기사에서 필자는 ADO.NET 데이터 서비스가 Silverlight 2와 서버 기반 모델 사이에서 어떻게 게이트웨이 역할을 하는지 살펴보았습니다. 이제 수동으로 디자인된 웹 서비스에 의지하지 않고 ADO.NET 데이터 서비스를 사용하여 서버의 데이터를 읽고 쓰는 방법을 알게 되었을 것입니다. 이 기사에서 볼 수 있듯이 Silverlight, ADO.NET 데이터 서비스 및 LINQ를 조합하여 사용하면 Web 2.0 기술의 모든 이점을 갖춘 강력한 데이터 기반 웹 응용 프로그램을 작성할 수 있습니다. 이러한 기술에 대한 자세한 내용은 "추가 정보" 보충 기사를 참조하십시오.

Shawn Wildermuth는 Microsoft MVP(C#)이며 Wildermuth Consulting Services의 설립자이고, 여러 권의 책과 수많은 기사를 집필했습니다. 또한 Shawn은 현재 미국 전역에서 Silverlight 투어를 진행하면서 Silverlight 2를 강의하고 있습니다. 문의 사항이 있으면 shawn@wildermuthconsulting.com으로 연락하십시오.

ObservableCollection<(Of <(T>)>) Class

    [SerializableAttribute]
    public class ObservableCollection<T> : Collection<T>,
    INotifyCollectionChanged, INotifyPropertyChanged

    In many cases the data that you work with is a collection of objects. For example, a common scenario in data binding is to use an ItemsControl such as a ListBox, ListView, or TreeView to display a collection of records.

    You can enumerate over any collection that implements the IEnumerable interface. However, to set up dynamic bindings so that insertions or deletions in the collection update the UI automatically, the collection must implement the INotifyCollectionChanged interface. This interface exposes the CollectionChanged event, an event that should be raised whenever the underlying collection changes.

    WPF provides the ObservableCollection<(Of <(T>)>) class, which is a built-in implementation of a data collection that implements the INotifyCollectionChanged interface.

    Before implementing your own collection, consider using ObservableCollection<(Of <(T>)>) or one of the existing collection classes, such as List<(Of <(T>)>), Collection<(Of <(T>)>), and BindingList<(Of <(T>)>), among many others. If you have an advanced scenario and want to implement your own collection, consider using IList, which provides a non-generic collection of objects that can be individually accessed by index. Implementing IList provides the best performance with the data binding engine.

    Note:

    To fully support transferring data values from binding source objects to binding targets, each object in your collection that supports bindable properties must implement an appropriate property changed notification mechanism such as the INotifyPropertyChanged interface.

    For more information, see "Binding to Collections" in Data Binding Overview.

    Notes on XAML Usage

    ObservableCollection<(Of <(T>)>) can be used as a XAML object element in Windows Presentation Foundation (WPF), in versions 3.0 and 3.5. However, the usage has substantial limitations.

    • ObservableCollection<(Of <(T>)>) must be the root element, because the x:TypeArguments attribute that must be used to specify the constrained type of the generic ObservableCollection<(Of <(T>)>) is only supported on the object element for the root element.
    • You must declare an x:Class attribute (which entails that the build action for this XAML file must be Page or some other build action that compiles the XAML).
    • ObservableCollection<(Of <(T>)>) is in a namespace and assembly that are not initially mapped to the default XML namespace. You must map a prefix for the namespace and assembly, and then use that prefix on the object element tag for ObservableCollection<(Of <(T>)>).

    A more straightforward way to use ObservableCollection<(Of <(T>)>) capabilities from XAML in an application is to declare your own non-generic custom collection class that derives from ObservableCollection<(Of <(T>)>), and constrains it to a specific type. Then map the assembly that contains this class, and reference it as an object element in your XAML.

    This example

    This example shows how to create and bind to a collection that derives from the ObservableCollection<(Of <(T>)>) class, which is a collection class that provides notifications when items get added or removed.

    The following example shows the implementation of a NameList collection:

    public class PersonName
    
    {
    
    private string firstName;
    
    private string lastName;
    
    public PersonName(string first, string last)
    
    {
    
    	this.firstName = first;
    
    	this.lastName = last;
    
    }
    
    public string FirstName
    
    {
    
    	get { return firstName; }
    
    	set { firstName = value; }
    
    }
    
    public string LastName
    
    {
    
    	get { return lastName; }
    
    	set { lastName = value; }
    
    }
    
    }
    
    public class NameList : ObservableCollection<PersonName>
    
    {
    
    public NameList() : base()
    
    {
    
    	Add(new PersonName("Willa", "Cather"));
    
    	Add(new PersonName("Isak", "Dinesen"));
    
    	Add(new PersonName("Victor", "Hugo"));
    
    	Add(new PersonName("Jules", "Verne"));
    
    }
    
    }
    

    데이터 삽입 방법(ADO.NET 데이터 서비스/Silverlight)

    System.Data.Services.Client 라이브러리를 사용하는 Silverlight 응용 프로그램에서는 ADO.NET 데이터 서비스에 데이터를 삽입할 수 있습니다. 이 단원의 코드 시나리오에서는 데이터 모델 및 데이터 서비스 구현(ADO.NET 데이터 서비스/Silverlight)에 설명되어 있는 데이터 서비스를 사용합니다. 이 항목에서는 클라이언트 응용 프로그램이 사용자 지정 항목을 System.Data.Services.Client..::.DataServiceContext의 로컬 인스턴스에 추가하여 Northwind 데이터 서비스의 저장소에 유지하는 방법을 보여 줍니다.

    이 예제의 쿼리는 LINQ 구문을 사용하며 미국 지역 고객을 식별하는 where 절을 포함합니다. 비동기 메서드는 서비스에서 데이터를 가져와서 Customer 형식의 ObservableCollection<T>으로 읽어 옵니다. 이 컬렉션은 데이터 표의 DataServiceContext에 할당됩니다.

    Silverlight 응용 프로그램

    Silverlight 응용 프로그램을 구현하려면

    1. 데이터 모델 및 데이터 서비스 구현(ADO.NET 데이터 서비스/Silverlight)에 설명되어 있는 ASP.NET 웹 응용 프로그램을 만들거나 다시 사용합니다.

    2. Visual Studio 솔루션 탐색기에서 솔루션을 마우스 오른쪽 단추로 클릭하고 추가를 가리킨 다음 새 항목을 선택합니다.

    3. 새 항목 추가 대화 상자에서 Silverlight 범주의 새 프로젝트를 추가합니다. 프로젝트 이름을 SilverlightClientApp2로 지정합니다.

    4. Silverlight 응용 프로그램 추가 대화 상자에서 Silverlight 컨트롤을 기존 웹 사이트에 연결, 응용 프로그램을 참조하는 테스트 페이지 추가 및 시작 페이지로 만들기를 선택합니다.

    5. ASP.NET 웹 응용 프로그램 속성 페이지에서 웹/시작 작업 섹션에 특정 페이지가 선택되어 있는지 확인합니다. 새 프로젝트의 *.html 테스트 페이지를 사용합니다. 그러면 텍스트 상자에 Silverlight 응용 프로그램 HTML 테스트 페이지가 시작 페이지로 표시됩니다.

    중요:

    ADO.NET 데이터 서비스의 Silverlight 클라이언트는 데이터 서비스 호스트와 동일한 도메인에 있어야 합니다. 예를 들어 클라이언트 응용 프로그램이 http://domain-x/SilverlightApp.html에 있는 경우 http://domain-x/dataservice.svc로 데이터 서비스 요청을 보내면 클라이언트와 데이터 서비스가 같은 도메인에 있으므로 요청이 성공합니다. 도메인 x에 있는 클라이언트 응용 프로그램에서 http://domain-z/dataservice.svc로 요청을 보내면 대상 데이터 서비스가 다른 도메인에 있으므로 요청이 실패합니다.

    Silverlight 사용자 인터페이스

    이 예제의 사용자 인터페이스에는 System.Windows.Controls.Data 어셈블리의 DataGrid 컨트롤과 StackPanel이 포함됩니다. 데이터 표에서는 Silverlight용 클라이언트 응용 프로그램(ADO.NET 데이터 서비스/Silverlight)에 구현된 것과 유사한 데이터 바인딩 시나리오를 사용하여 데이터를 표시합니다. 이전 예제의 목록 상자와는 달리 데이터 표에 바인딩할 때 페이지 스키마에 데이터 템플릿을 정의하지 않아도 됩니다.

    사용자 인터페이스를 구현하려면

    1. 프로젝트에 System.Windows.Controls.Data에 대한 참조를 추가합니다.

    2. 클라이언트 응용 프로그램의 Page.xaml 파일에 xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" xmlns 참조를 추가합니다.

    3. UserControl 블록은 다음과 같습니다.

      <UserControl x:Class="SilverlightClientApp2.Page"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
          Width="800" Height="400">
    4. 다음과 같이 Page.xaml 파일의 Grid 요소에 RowDefinitions를 추가합니다.

      <Grid x:Name="LayoutRoot" Background="White">
              <Grid.RowDefinitions>
                  <RowDefinition Height="*"/>
                  <RowDefinition Height="4*"/>
              </Grid.RowDefinitions>
          </Grid>
    5. Grid에 StackPanel을 추가합니다.

              <StackPanel Orientation="Horizontal">
              </StackPanel>
    6. 다음 XML 예제에 표시된 것과 같이 StackPanel에 단추와 텍스트 상자를 추가합니다. 텍스트 상자에는 새 Customer에 대한 데이터를 입력할 수 있습니다. Get Data 단추와 Insert 단추에 Click을 할당하면 이 사용자 인터페이스의 코드에서 처리기 메서드가 해당 단추에 할당됩니다.

              <StackPanel Orientation="Horizontal">
                   <Button Content="Get Data" 
                      Width="75"
                      Height="20"
                      Margin="10" 
                      Click="OnGetData"/>
                  <Button Content="Insert" 
                      Width="75"
                      Height="20"
                      Margin="10"  
                      Click="OnInserted"/>
                  <TextBox Text="IDXXX" 
                      Width="75"
                      Height="25"
                      Margin="10" 
                      Name="textBoxCustomerID"/>
                  <TextBox Text="CompanyName" 
                      Width="75"
                      Height="25"
                      Margin="10" 
                      Name="textBoxCompanyName"/>
              </StackPanel>
    7. System.Windows.Controls.Data 어셈블리에서 DataGrid 컨트롤을 추가합니다. Northwind 데이터를 표시할 수 있도록 ItemsSource 특성을 {Binding}으로 설정합니다.

              <data:DataGrid 
                  Name="dataGrid1"
                  Grid.Row="1" 
                  Margin="20"
                  AutoGenerateColumns="True" 
                  ItemsSource="{Binding}" >
              </data:DataGrid>

    다음은 완성된 Page.xaml 파일입니다.

    <UserControl x:Class="SilverlightClientApp2.Page"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
        Width="800" Height="400">
        <Grid x:Name="LayoutRoot" Background="White">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="4*" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <StackPanel Orientation="Horizontal">
                <Button Content="Get Data" 
                    Width="75"
                    Height="20"
                    Margin="10" 
                    Click="OnGetData"/>
                <Button Content="Insert" 
                    Width="75"
                    Height="20"
                    Margin="10"  
                    Click="OnInserted"/>
                <TextBox Text="IDXXX" 
                    Width="75"
                    Height="25"
                    Margin="10" 
                    Name="textBoxCustomerID"/>
                <TextBox Text="CompanyName" 
                    Width="75"
                    Height="25"
                    Margin="10" 
                    Name="textBoxCompanyName"/>
            </StackPanel>
            
            <TextBlock x:Name="CustomerBlock" Grid.Row="1" 
               Text="Customers:" Grid.Column="0" FontFamily="Calibri"/>
    
            <data:DataGrid 
                Name="dataGrid1"
                Grid.Row="2" 
                Margin="10"
                AutoGenerateColumns="True" 
                ItemsSource="{Binding}" >
            </data:DataGrid>
            
            <TextBlock x:Name="textBlock" Grid.Row="3" Text=""
                       Grid.Column="0" FontFamily="Calibri"/>
            
        </Grid>
    </UserControl>

    데이터 표시 및 삽입을 위한 클라이언트 코드

    사용자 인터페이스에서 dataGrid1의 ItemsSource는 데이터 바인딩을 지정합니다. ADO.NET 데이터 서비스에 대한 데이터 바인딩을 사용하려면 클라이언트 메서드에서 데이터 서비스의 응답을 기다려야 하므로 비동기 메서드를 사용해야 합니다.

    데이터 바인딩 및 표시

    Northwind 데이터 서비스에서 Customer 데이터를 가져오고 표시하려면

    1. 참조를 추가하거나, Silverlight 프로젝트에서 다음 어셈블리를 참조하는지 확인한 후 이러한 어셈블리에 대한 using 지시문을 Page.xaml.cs 파일에 추가합니다.

      using System.Data.Services.Client;
      using System.Collections.ObjectModel;
    2. 데이터 서비스 클래스에 대한 using 지시문을 Page.xaml.cs 파일에 추가합니다.

      using SilverlightClientApp2.NorthwindSvc;
    3. 다음 코드 예제와 같이 페이지 초기화 코드를 구현합니다. NorthwindEntities 클래스는 이 응용 프로그램에서 사용하는 DataServiceContext 클래스를 구현한 것입니다. 다음 코드를 사용하면 Silverlight 페이지가 로드될 때 DataServiceContext가 초기화됩니다. 6단계에 설명된 것처럼 데이터 바인딩에는 Customer 형식의 ObservableCollection<T>이 사용됩니다.

      namespace SilverlightClientApp2
      {
          public partial class Page : UserControl
          {
              NorthwindEntities svcContext;
              ObservableCollection<Customer> obsvCollCustomers;
      
              public Page()
              {
                  InitializeComponent();
                  this.Loaded += OnLoaded;
              }
      
              void OnLoaded(object sender, EventArgs args)
              {
                  svcContext = new NorthwindEntities(
                          new Uri("Northwind.svc", UriKind.Relative));
                  obsvCollCustomers = new
                                     ObservableCollection<Customer>();
              }
    4. 다음 코드와 같이 Get Data 단추의 Click 메서드에 지정된 메서드를 구현합니다. Customer 형식의 DataServiceQuery<(Of <(TElement>)>)을 만듭니다. 이 예제에서는 where 절과 select 문이 포함된 LINQ 구문을 사용합니다. 쿼리를 정의한 후에는 비동기 메서드 BeginExecute를 호출합니다. 메서드의 첫 번째 매개 변수는 데이터 서비스에서 결과를 반환할 때 쿼리를 완료할 콜백 함수를 식별합니다.

              void OnGetData(object sender, EventArgs args)
              {
                  DataServiceQuery<Customer> query =
                      (DataServiceQuery<Customer>)
                      (from c in svcContext.Customers 
                       where c.Country == "USA"
                       select c);
      
                  query.BeginExecute(GetDataCallback, query);
      
              }
    5. 이전 단계에서 시작된 쿼리의 결과를 처리하는 콜백 함수를 구현합니다. 비동기 작업의 결과는 결과 매개 변수에 포함된 IAsyncResult.AsyncState 개체에 들어 있습니다. AsyncState를 Customer 형식의 DataServiceQuery<(Of <(TElement>)>)로 캐스팅하여 쿼리 결과를 열거합니다.

    6. Customer 형식의 ObservableCollection<T>을 초기화합니다. 이 클래스는 변경 사항이 발생했을 때 알림을 보내기 때문에 데이터 바인딩에 사용됩니다. 이 방법은 데이터 변경 사항을 데이터 서비스에 저장한 후에 비동기 메서드에서 반환하는 경우 유용합니다. 쿼리 결과를 처리하는 스레드에서 사용자 인터페이스 스레드에 대해 프로시저를 호출하려면 Dispatcher 개체가 필요합니다. Dispatcher 대괄호 안에서 호출되는 코드는 고객의 ObservableCollection을 dataGrid1의 DataContext 속성에 할당합니다.

              void GetDataCallback(IAsyncResult result)
              {
                  try
                  {
                      DataServiceQuery<Customer> queryResult =
                                     (DataServiceQuery<Customer>) result.AsyncState;
      
                      IEnumerable<Customer> results =
                                    queryResult.EndExecute(result);
      
                      foreach (var item in results)
                              obsvCollCustomers.Add(item);
      
                      Dispatcher.BeginInvoke(() =>
                      {
                          dataGrid1.DataContext = obsvCollCustomers;
                      });
                  }
                  catch (DataServiceRequestException ex)
                  {
                      textBlock.Text = "Error: " + ex.Response.ToString();
                  }
              }
      
    7. 응용 프로그램을 빌드하고 실행합니다. Get Data 단추를 클릭하면 페이지의 데이터 표가 Northwind 데이터 서비스에서 가져온 고객으로 채워집니다.

    데이터 삽입

    Silverlight 사용자 인터페이스에서 Customer 데이터 형식의 새 인스턴스를 만들어 데이터 서비스에 삽입하려면

    1. Insert 단추의 Click 메서드에 지정된 OnInserted 메서드를 구현합니다. OnInserted 메서드는 Customer 데이터 형식의 새 인스턴스를 만든 다음 페이지의 텍스트 상자에 있는 데이터로부터 null을 허용하지 않는 CustomerID 및 CompanyName 속성을 할당합니다. OnGetData에서 DataServiceQuery<(Of <(TElement>)>)의 where 절은 Country == USA인 결과를 지정하므로 Country 속성을 할당합니다. 그런 다음에는 컨트롤에 표시되는 데이터에 새 항목을 표시하기 원할 수 있습니다. 모든 할당 작업이 완료되면 Northwind 서비스의 DataServiceContext를 사용하여 새 Customer를 추가합니다. 클라이언트 클래스에서 제공하는 AddToCustomers 메서드를 호출하여 Customer를 추가합니다. 그런 다음 콜백 함수와 새 Customer 인스턴스를 식별하는 매개 변수를 사용하여 System.Data.Services.Client 라이브러리에서 BeginSaveChanges 메서드를 호출합니다.

              void OnInserted(object sender, EventArgs args)
              {
                  Customer newCustomer = new Customer();
                  newCustomer.CustomerID = textBoxCustomerID.Text;
                  newCustomer.CompanyName = textBoxCompanyName.Text;
                  newCustomer.Country = "USA";
      
                  svcContext.AddToCustomers(newCustomer);
                  svcContext.BeginSaveChanges(OnSaveChangesCompleted, newCustomer);
      
              }
      
    2. OnSaveChangesCompleted를 구현하여 이전 단계에서 호출한 BeginSaveChanges의 비동기 결과를 처리합니다. 콜백 함수는 DataServiceContextEndSaveChanges 멤버를 사용하여 Northwind svcContext를 통해 데이터 서비스의 데이터를 업데이트합니다. IAsyncResultObservableCollection 개체를 Customer 형식으로 캐스팅하여 ObservableCollection ObsvCollCustomers에 추가할 수 있습니다. 이렇게 하면 데이터 표가 자동으로 업데이트됩니다.

              void OnSaveChangesCompleted(IAsyncResult result)
              {
                  try
                  {
                      proxy.EndSaveChanges(result);
                      // Add the new customer to the grid.
                      obsvCollCustomers.Add(result.AsyncState as Customer);
                      textBlock.Text = "Insert complete: " +
                                     (result.AsyncState as Customer).CustomerID;
                  }
                  catch (DataServiceRequestException ex)
                  {
                      textBlock.Text = "OnSaveChangesCompleted Error: " + ex.Response.ToString();
                  }
              }
      

    응용 프로그램을 실행합니다. IDXXX 텍스트 상자에 다섯 자로 된 고객 ID를 입력하고 CompanyName 텍스트 상자에 회사 이름을 입력합니다. Insert 단추를 클릭하여 새 고객을 추가하고, 새 항목이 포함된 결과로 데이터 표를 업데이트합니다.

    다음 코드에서는 이 항목에서 사용된 Page.xaml.cs 파일의 전체 콘텐츠를 보여 줍니다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    
    using System.Data.Services.Client;
    using System.Collections.ObjectModel;
    using SilverlightClientApp2.NorthwindSvc;
    
    
    namespace SilverlightClientApp2
    {
        public partial class Page : UserControl
        {
            NorthwindEntities proxy;
            ObservableCollection<Customer> obsvCollCustomers;
    
            public Page()
            {
                InitializeComponent();
                this.Loaded += OnLoaded;
            }
    
            void OnLoaded(object sender, EventArgs args)
            {
                proxy = new NorthwindEntities(
                        new Uri("Northwind.svc", UriKind.Relative));
                obsvCollCustomers = new
                                   ObservableCollection<Customer>();
            }
    
            void OnGetData(object sender, EventArgs args)
            {
                DataServiceQuery<Customer> query =
                    (DataServiceQuery<Customer>)
                    (from c in proxy.Customers
                     where c.Country == "USA"
                     select c);
    
                query.BeginExecute(GetDataCallback, query);
    
            }
    
            void GetDataCallback(IAsyncResult result)
            {
                try
                {
                    DataServiceQuery<Customer> queryResult =
                                   (DataServiceQuery<Customer>)result.AsyncState;
    
                    IEnumerable<Customer> results =
                                  queryResult.EndExecute(result);
    
                    foreach (var item in results)
                        obsvCollCustomers.Add(item);
    
                    Dispatcher.BeginInvoke(() =>
                    {
                        dataGrid1.DataContext = obsvCollCustomers;
                    });
                }
                catch(DataServiceRequestException ex)
                {
                    ex.Response.ToString();
                }
            }
    
            void OnInserted(object sender, EventArgs args)
            {
                Customer newCustomer = new Customer();
                newCustomer.CustomerID = textBoxCustomerID.Text;
                newCustomer.CompanyName = textBoxCompanyName.Text;
                newCustomer.Country = "USA";
    
                proxy.AddToCustomers(newCustomer);
                proxy.BeginSaveChanges(OnSaveChangesCompleted, newCustomer);
    
            }
    
            void OnSaveChangesCompleted(IAsyncResult result)
            {
                try
                {
                    proxy.EndSaveChanges(result);
    
                    obsvCollCustomers.Add(result.AsyncState as Customer);
                    textBlock.Text = "Insert complete: " +
                                       (result.AsyncState as Customer).CustomerID;
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "OnSaveChangesCompleted Error: " + ex.Response.ToString();
                }
            }
        }
    }
    

    System.Data.Services.Client 라이브러리를 사용하는 Silverlight용 응용 프로그램에서는 ADO.NET 데이터 서비스에서 배포되는 데이터를 업데이트 및 삭제할 수 있습니다. 이 항목의 코드는 데이터 삽입 방법(ADO.NET 데이터 서비스/Silverlight) 항목에서 설명한 시나리오를 확장하고 이전 예제에 구현된 ASP.NET 웹 응용 프로그램을 사용합니다.

    데이터를 업데이트 및 삭제하는 클라이언트 코드

    데이터를 업데이트할 때는 이전 예제에 표시된 것과 비슷한 동기화 메서드를 사용합니다. 다음 예제에서는 Category 데이터 형식을 사용하여 업데이트 및 삭제 절차를 설명합니다. Category 데이터 형식은 Product 데이터에만 연결됩니다.

    참고:

    데이터를 업데이트할 때는 데이터 모델을 분석하여 연결이 제대로 업데이트되는지 확인해야 합니다. Northwind 데이터 서비스에서 Customer 항목은 여러 Order 데이터 인스턴스와 연결할 수 있으며 각 Order는 여러 Order_Detail 항목과 연결할 수 있습니다. 데이터 서비스에서 액세스할 수 있는 관계형 모델에 제한이 있기 때문에 Customer 또는 Order를 삭제하기 위해서는 Customer, Order 및 Order_Detail 항목이 연관된 모든 연결을 삭제해야 할 수 있습니다. 연결에 대한 자세한 내용은 엔터티 및 관계 또는 엔터티 데이터 모델 관계를 참조하십시오.

    Silverlight 사용자 인터페이스

    이 Silverlight용 응용 프로그램의 사용자 인터페이스는 다음 XML 예제에서 보는 것처럼 Page.xaml 스키마에서 초기화됩니다. 이 페이지에는 세 행으로 구성된 표가 있습니다. 0행은 데이터 수정 및 삭제를 실행하는 단추가 있는 가로 스택 패널이고, 1행에는 데이터 표가 있습니다. 2행은 데이터가 로드되거나 오류가 발생할 때 메시지가 표시되는 텍스트 블록입니다.

    <UserControl x:Class="SilverlightClientApp3.Page"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
        Width="800" Height="800">
        <Grid x:Name="LayoutRoot" Background="White">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="3*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition MaxWidth="650"/>
            </Grid.ColumnDefinitions>
    
            <StackPanel Orientation="Horizontal"
                            Grid.Column="0" Grid.Row="0">
                <Button Content="Delete" 
                    Width="75"
                    Height="20"
                    Margin="10" 
                    Click="OnDelete"/>
                <Button Content="Commit"
                    Width="75"
                    Height="20"
                    Margin="10"  
                    Click="OnUpdate"/>
            </StackPanel>
    
            <data:DataGrid 
                Name="dataGrid1"
                Grid.Row="1" 
                Grid.Column="0"
                AutoGenerateColumns="True"
                ItemsSource="{Binding}" >
            </data:DataGrid>
    
            <TextBlock x:Name="textBlock" Grid.Row="2"
                   Grid.Column="0" FontFamily="Calibri"/>
        </Grid>
    </UserControl>
    데이터 업데이트

    ADO.NET 데이터 서비스에서 배포하는 데이터를 업데이트하려면 Silverlight용 응용 프로그램에서 다음 비동기 절차를 구현해야 합니다.

    1. UpdateObject 메서드를 호출하여 클라이언트 응용 프로그램에서 DataServiceContext의 데이터를 수정합니다. 이 경우 서버의 데이터는 수정되지 않지만 BeginSaveChanges 메서드가 호출될 때 업데이트를 위해 데이터가 변경된 것으로 표시됩니다.

    2. 첫 번째 매개 변수에서 지정한 콜백 함수로 BeginSaveChanges 메서드를 호출합니다.

    3. 콜백 함수에서 EndSaveChanges 메서드를 호출하여 BeginSaveChanges 메서드의 결과를 처리합니다.

    다음 코드 세그먼트는 업데이트 시나리오의 각 단계를 보여 줍니다.

    다음 예제의 OnUpdate 메서드는 사용자가 데이터 표의 데이터를 수정하고 커밋 단추를 클릭하여 선택한 행의 변경 사항을 커밋할 때 실행됩니다. 이 예제에서는 한 번에 한 행만 업데이트합니다. Category 항목은 DataGridEndingEditEventArgs 개체의 SelectedItem을 Category 형식에 캐스팅하여 업데이트하도록 지정됩니다. 이 Category 항목을 변경하면 UpdateObject 메서드를 호출하여 클라이언트 응용 프로그램의 DataServiceContext에서 업데이트됩니다. BeginSaveChanges 메서드는 데이터 서비스 업데이트를 위한 비동기화 프로세스를 시작합니다.

            void OnUpdate(object sender, EventArgs args)
            {
                if (dataGrid1.SelectedItem == null || dataGrid1.SelectedItems.Count > 1)
                {
                    textBlock.Text = "Select a single row for update.";
                    return;
                }
    
                Category selectedCategory = (Category)dataGrid1.SelectedItem;
    
                try
                {
                    svcContext.UpdateObject(selectedCategory);
                    textBlock.Text = "Saving Changes...";
                    svcContext.BeginSaveChanges(OnSaveChangesCompleted, selectedCategory);
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "OnUpdate Error: " + ex.Response.ToString();
                }
            }

    데이터 서비스에서 OnUpdate 메서드의 결과가 반환되면 콜백 메서드 OnSaveChangesCompleted가 EndSaveChanges를 호출하여 업데이트를 완료합니다.

            void OnSaveChangesCompleted(IAsyncResult result)
            {
                try
                {
                    svcContext.EndSaveChanges(result);
                    textBlock.Text = "Completed";
    
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "OnSaveChangesCompleted Error: " + ex.Response.ToString();
                }
            }
    
    데이터 삭제

    Northwind 데이터 서비스에서 배포하는 Category 데이터를 삭제하려면 Silverlight용 응용 프로그램에서 다음 비동기 절차를 구현해야 합니다.

    1. 콜백 함수로 BeginLoadProperty 메서드를 호출하여 비동기 메서드의 결과를 처리함으로써 삭제할 항목과 연관된 항목을 모두 가져옵니다. 데이터 서비스는 외부 키 제약 조건이 있는 데이터베이스를 기반으로 하기 때문에 관련된 항목을 모두 삭제해야 합니다.

    2. EndLoadProperty 메서드가 단계 1의 결과를 가져오는 콜백 메서드를 구현합니다.

    3. 각 항목에서 DeleteLink 메서드를 호출하여 관련 항목의 바인딩을 해제합니다.

    4. DeleteObject 메서드를 호출하여 항목을 삭제합니다.

    5. 첫 번째 매개 변수에서 지정한 콜백 함수로 BeginSaveChanges 메서드를 호출합니다.

    6. 콜백 함수에서 EndSaveChanges 메서드를 호출하여 BeginSaveChanges 메서드의 결과를 처리합니다.

    사용자가 삭제 단추를 클릭하면 OnDelete 메서드가 트리거되어 Northwind 서비스에서 Category 항목을 삭제하는 일련의 비동기 메서드가 시작됩니다. OnDelete 메서드는 삭제할 Category를 가져온 다음 비동기 메서드 BeginUnBindDeleteCategory로 전달합니다.

            void OnDelete(object sender, EventArgs args)
            {
                if (dataGrid1.SelectedItem != null)
                {
                    try
                    {
                        Category Category = (Category)dataGrid1.SelectedItem;
    
                        BeginUnBindDeleteCategory(Category);
                        textBlock.Text = "Saving Changes...";
    
                    }
                    catch (DataServiceRequestException ex)
                    {
                        textBlock.Text = "OnDelete Error: " + ex.Response.ToString();
                    }
                }
            }
    

    BeginUnBindDeleteCategory 메서드는 이전 단계에서 지정한 Category 항목과 연관된 Product 항목을 로드하기 시작합니다. BeginLoadProperty 메서드의 두 번째 매개 변수는 결과를 처리할 콜백 함수, 이 경우에는 EndUnBindDeleteCategory를 지정합니다.

            void BeginUnBindDeleteCategory(Category categoryObject)
            {
                try
                {//기본적으로 관계 속성(예: Product.Supplier)이 있을 때 속성을 명시적으로 로드하지 않으면 해당 속성은 Null이 됩니다. //필요 시 로딩을 지원하기 위해 DataServiceContext 클래스에는 동일한 비동기 모델을 따르는 BeginLoadProperty 메서드가 있습니다. //이 메서드에서 원본 엔터티, 속성 이름 및 콜백을 지정할 수 있습니다. (테이블의 고유키라고 보면됩니다.)                                svcContext.BeginLoadProperty(categoryObject, "Products",
                                   EndUnBindDeleteCategory, categoryObject);
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "BeginUnBindDeleteCategory Error: " + ex.Response.ToString();
                }
            }
    



     

    EndUnBindDeleteCategory 메서드는 BeginLoadProperty 메서드의 결과를 처리할 EndLoadProperty 메서드를 호출합니다. 결과는 각 항목에서 DeleteLink 메서드를 호출하는 foreach 루프에 열거되는 연관된 Product 항목입니다.

    연관된 Product 항목에 대한 모든 링크가 Category 항목에서 삭제되면 DeleteObject 메서드를 호출하여 Category 항목이 마지막으로 삭제됩니다. 업데이트 예제에 사용된 콜백 함수인 OnSaveChangesCompleted에서 첫 번째 매개 변수로 BeginSaveChanges 메서드를 호출하여 변경 사항을 데이터 서비스로 전달합니다.

            void EndUnBindDeleteCategory(IAsyncResult asyncResult)
            {
                Category categoryObject = asyncResult.AsyncState as Category;
    
                try
                {
                    svcContext.EndLoadProperty(asyncResult);
    
                    //Unbind all the Products that are bound to this instance 
                    //of Category using the DeleteLink API
                    foreach (Product product in categoryObject.Products)
                    {
                        svcContext.DeleteLink(categoryObject, "Products", product);
                    }
    
                    svcContext.DeleteObject(categoryObject);
                    svcContext.BeginSaveChanges(OnSaveChangesCompleted, null);
                    // Update the grid.
                    obsvCollCategories.Remove(categoryObject);
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "EndUnbindDelete Error: " + ex.Response.ToString();
                }
            }

    OnSaveChangesCompleted 메서드는 EndSaveChanges 메서드를 호출하여 Category 및 Product 항목에 대한 링크의 삭제를 완료합니다.

            void OnSaveChangesCompleted(IAsyncResult result)
            {
                try
                {
                    svcContext.EndSaveChanges(result);
                    textBlock.Text = "Completed";
    
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "OnSaveChangesCompleted Error: " + ex.Response.ToString();
                }
            }
    

    다음 코드에서는 이 항목의 샘플에서 사용하는 Page.xaml.cs 파일의 전체 콘텐츠를 보여 줍니다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    
    using SilverlightClientApp3.NorthwindSvc;
    using System.Data.Services.Client;
    using System.Collections.ObjectModel;
    
    namespace SilverlightClientApp3
    {
        public partial class Page : UserControl
        {
            NorthwindEntities svcContext;
            ObservableCollection<Category> obsvCollCategories;
    
            public Page()
            {
                InitializeComponent();
                this.Loaded += OnLoaded;
    
            }
    
            void OnLoaded(object sender, EventArgs args)
            {
                dataGrid1.AutoGeneratingColumn += OnColumnCreated;
    
                svcContext = new NorthwindEntities(
                            new Uri("Northwind.svc", UriKind.Relative));
    
                obsvCollCategories = new ObservableCollection<Category>();
    
                OnGetData(null, null);
            }
    
            void OnColumnCreated(object sender, DataGridAutoGeneratingColumnEventArgs e)
            {
                if (e.PropertyName == "Products" || e.PropertyName == "Picture")
                {
                    e.Cancel = true;
                }
            }
    
            void OnGetData(object sender, EventArgs args)
            {
                DataServiceQuery<Category> query = svcContext.Categories;
    
                try
                {
                    textBlock.Text = "Loading Data...";
    
                    query.BeginExecute(GetDataCallback, query);
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "OnGetData Error: " + ex.Response.ToString();
                }
    
            }
    
            void GetDataCallback(IAsyncResult result)
            {
                try
                {
                    DataServiceQuery<Category> queryResult =
                        (DataServiceQuery<Category>)result.AsyncState;
    
                    IEnumerable<Category> results =
                                       queryResult.EndExecute(result);
    
                        foreach (Category item in results)
                            obsvCollCategories.Add(item);
    
                    Dispatcher.BeginInvoke(() =>
                    {
                        dataGrid1.DataContext = obsvCollCategories;
                    });
    
                    textBlock.Text = "Data Loaded.";
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "GetDataCallback Error: " + ex.Response.ToString();
                }
            }
    
            void BeginUnBindDeleteCategory(Category categoryObject)
            {
                try
                {
                    svcContext.BeginLoadProperty(categoryObject, "Products",
                                   EndUnBindDeleteCategory, categoryObject);
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "BeginUnbindDelete Error: " + ex.Response.ToString();
                }
            }
    
            void EndUnBindDeleteCategory(IAsyncResult asyncResult)
            {
                Category categoryObject = asyncResult.AsyncState as Category;
    
                try
                {
                    svcContext.EndLoadProperty(asyncResult);
    
                    //Unbind all the Products that are bound to this instance 
                    //of Category using the DeleteLink API
                    foreach (Product product in categoryObject.Products)
                    {
                        svcContext.DeleteLink(categoryObject, "Products", product);
                    }
    
                    svcContext.DeleteObject(categoryObject);
                    svcContext.BeginSaveChanges(OnSaveChangesCompleted, null);
                    obsvCollCategories.Remove(categoryObject);
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "EndUnbindDeleteCategory Error: " + ex.Response.ToString();
                }
            }
    
            void OnUpdate(object sender, EventArgs args)
            {
                if (dataGrid1.SelectedItem == null || dataGrid1.SelectedItems.Count > 1)
                {
                    textBlock.Text = "Select a single row for update.";
                    return;
                }
    
                Category selectedCategory = (Category)dataGrid1.SelectedItem;
    
                try
                {
                    svcContext.UpdateObject(selectedCategory);
                    textBlock.Text = "Saving Changes...";
                    svcContext.BeginSaveChanges(OnSaveChangesCompleted, selectedCategory);
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "OnUpdate Error: " + ex.Response.ToString();
                }
            }
    
            void OnDelete(object sender, EventArgs args)
            {
                if (dataGrid1.SelectedItem != null)
                {
                    try
                    {
                        Category Category = (Category)dataGrid1.SelectedItem;
                        BeginUnBindDeleteCategory(Category);
                        textBlock.Text = "Saving Changes...";
    
                    }
                    catch (DataServiceRequestException ex)
                    {
                        textBlock.Text = "OnDelete Error: " + ex.Response.ToString();
                    }
                }
            }
    
            void OnSaveChangesCompleted(IAsyncResult result)
            {
                try
                {
                    svcContext.EndSaveChanges(result);
                    textBlock.Text = "Completed";
                }
                catch (DataServiceRequestException ex)
                {
                    textBlock.Text = "OnSaveChangesCompleted Error: " + ex.Response.ToString();
                }
            }
    
        }
    }

    패턴의 논리적인 뷰

    Cc707885.d7533449 - 4a8a - 31b2 - b258 - 316be0fdaf3f (엉 - 우리, MSDN.10)의 PNG.

    종합 WPF 응용 프로그램에서 프레 젠 테이션 모델 패턴을 사용함으로써,

    개발자들은 데이터 - 바인딩 기능을 윈도우 프리젠 테이션 파운데이션 (WPF)에 의해 제공을 사용할 수있습니다.

    애플 리케이션 데이터에 대한 데이터를 자동으로 그 가치를 변경하면 변경 사항이 반영 묶여있다 요소 바인딩.

    또한, 데이터 바인딩 요소를 변경하는 경우에 데이터의 외부 표현, 기본 데이터를 자동으로 업데이트 될 수있는 변경 사항을 반영하기 위해 의미할 수있습니다. 예를 들어, 사용자가 기본 데이터 값을 자동으로 업데이트 됩니다 그 변경 사항을 반영에 TextBox 요소에서 값을 편집합니다.

     

    보통의 개발단계

    The typical implementation process of the Model-View-Presenter pattern includes the following tasks:

    -Implementing a presenter class . (presenter class  구현)

    -Implementing a view interface. The view interface should expose the view's state elements.( 뷰 인터페이스 구현)

    -Implementing a view class . ( 뷰클래스 구현 )

    Cc707895.8b48bd07 - 0f4f - 4ffc - 8cfd - f2c14a3d57aa (엉 - 우리, MSDN.10)의 PNG.

    일반적인 프로젝트  모습

     

    구현방법

    ViewName Presenter.cs

    class EmployeesPresenter
    {
      private IEmployeesView view;
    
      public EmployeesPresenter (IEmployeesView view)
      {
        this.view = view;
      }
    }
    
    

    view interface는 일반적으로 설정하거나 보기의 상태를 쿼리는 발표자에 대한 하나 이상의 속성이 포함되어있습니다.

    For example, a view could expose an Employee property that allows the presenter to set the employee that the view should display.

    예를 들어, 직원의 견해 발표자보기를 표시해야 그 속성을 설정할 수있는 직원에 노출될 수 있다.

    Another approach consists of using the Presentation Model pattern.

    또 다른 접근 패턴을 사용하여 프레 젠 테이션 모델로 구성되어있습니다.

    In this case, the view interface contains a single property for the presenter to set the presentation model.

    이 경우에는 view interface는 Presentation 모델을 설정할 수있는 presenter  를위한 단일 속성이 포함되어있습니다.

    A different approach consists of exposing methods on the view interface.

    다른 접근 방식을 볼 interface에 노출로 구성되어있습니다.

    For example, the presenter could invoke a method named ShowEmployee ( Employee ) that indicates to the view that it has to display the employee passed by parameter.

    예를 들어, 발표자), 그 직원을 표시하려면이 매개 변수에 의해 통과를 볼 수있는 방법을 나타냅니다 ShowEmployee (Employee)라는 호출할 수있습니다.

    public class EmployeesDetailsPresenter : IEmployeesDetailsPresenter
    {
        public EmployeesDetailsPresenter(IEmployeesDetailsView view)
        {
            this.View = view;
        }
    
        public IEmployeesDetailsView View { get; set; }
    
        public void SetSelectedEmployee(BusinessEntities.Employee employee)
        {
            EmployeesDetailsPresentationModel model = new EmployeesDetailsPresentationModel();
            model.SelectedEmployee = employee;
            this.View.Model = model;
        }
    }
    
    public interface IEmployeesDetailsView
    {
        EmployeesDetailsPresentationModel Model { get; set; }
    }

     

    http://proxy4free.com/page1.html

    proxy4free.com 에 접속해서 프록시 서버를 입력해주면 끗~

     

     

    customErrors 요소(ASP.NET 설정 스키마)

    ASP.NET 응용 프로그램의 사용자 지정 오류 메시지에 대한 정보를 제공합니다. customErrors 요소는 응용 프로그램 파일 계층 구조의 모든 수준에 정의할 수 있습니다.

    <customErrors  
       mode="Off|On|RemoteOnly" 
    />

    완전한 예외 정보에는 정확한 서버 예외와 서버 스택 추적 내용이 포함됩니다. 필터링된 정보에는 표준 원격 예외가 포함되지만 서버 스택 추적 내용은 포함되지 않습니다.

    다음 표에서는 예외 정보를 받는 호출자와 받게 되는 정보 유형을 지정하는 세 가지 서버 채널 속성 값에 대해 설명합니다.

    Off 모든 호출자가 완전한 예외 정보를 받습니다

    On 모든 호출자가 필터링된 예외 정보를 받습니다.

    RemoteOnly

    로컬 호출자는 완전한 예외 정보를 받고 원격 호출자는 필터링된 예외 정보를 받습니다

    예제

    <configuration>
      <system.web>
        <customErrors defaultRedirect="GenericError.htm"
                      mode="RemoteOnly">
          <error statusCode="500"
                 redirect="InternalError.htm"/>
        </customErrors>
      </system.web>
    </configuration>
    

    [SqlDataSource] 웹 서버 컨트롤 개요

    SqlDataSource 웹 서버 컨트롤 개요

    SqlDataSource 컨트롤을 사용하면 웹 컨트롤을 사용하여 OLE DB와 ODBC 데이터 소스는 물론 Microsoft SQL Server와 Oracle 데이터베이스 같은 관계형 데이터베이스에 있는 데이터에 액세스할 수 있습니다. GridView, FormView 및 DetailsView 컨트롤처럼 데이터를 표시하는 다른 컨트롤과 SqlDataSource 컨트롤을 함께 사용하여 코드를 거의 사용하지 않고 ASP.NET 웹 페이지에서 데이터를 표시하거나 조작할 수 있습니다.

    SqlDataSource 컨트롤을 데이터 소스에 연결

    <HTML>
      <BODY>
        <FORM runat="server">
          <asp:SqlDataSource
              id="SqlDataSource1"
              runat="server"
              DataSourceMode="DataReader"
              ConnectionString="<%$ ConnectionStrings:MyNorthwind%>"
              SelectCommand="SELECT LastName FROM Employees">
          </asp:SqlDataSource>
          <asp:ListBox
              id="ListBox1"
              runat="server"
              DataTextField="LastName"
              DataSourceID="SqlDataSource1">
          </asp:ListBox>
        </FORM>
      </BODY>
    </HTML>

    SqlDataSource 컨트롤을 사용하여 데이터 명령 실행

    SelectCommand, UpdateCommand, DeleteCommand 및 InsertCommand라는 최대 네 개의 명령(SQL 쿼리)을 SqlDataSource 명령에 지정할 수 있습니다. 각 명령은 데이터 소스 컨트롤의 개별 속성입니다. 각 명령 속성에 대해 데이터 소스 컨트롤에서 실행할 SQL 문을 지정합니다. 저장 프로시저를 지원하는 데이터베이스에 데이터 소스 컨트롤이 연결되면 SQL 문 대신 저장 프로시저의 이름을 지정할 수 있습니다.

    런타임에 제공할 값의 자리 표시자를 포함하여 매개 변수가 있는 명령을 만들 수 있습니다. 다음 예제에서는 매개 변수가 있는 SQL Select 명령을 보여 줍니다.

    Select CustomerID, CompanyName From Customers Where City = @city

    런타임에 명령이 매개 변수 값을 가져오는 위치(예: 다른 컨트롤, 쿼리 문자열 등)를 지정하는 매개 변수 개체를 만들 수 있습니다. 또는 프로그래밍 방식으로 매개 변수 값을 지정할 수도 있습니다. 자세한 내용은 SqlDataSource 컨트롤에 매개 변수 사용을 참조하십시오.

    데이터 소스 컨트롤은 해당하는 Select, Update, Delete 또는 Insert 메서드가 호출되면 명령을 실행합니다. Select 메서드는 페이지 또는 데이터 소스 컨트롤에 바인딩된 컨트롤의 DataBind 메서드를 호출하면 자동으로 호출됩니다. 또한 데이터 소스 컨트롤에서 명령을 실행하려는 경우 네 가지 메서드 중 하나를 명시적으로 호출할 수 있습니다.

    GridView와 같은 일부 컨트롤에서는 이러한 네 개의 메서드를 호출하거나 DataBind 메서드를 명시적으로 호출할 필요 없이 자동으로 메서드를 호출할 수 있습니다.

    자세한 내용은 SqlDataSource 컨트롤을 사용하여 데이터 선택SqlDataSource 컨트롤을 사용하여 데이터 수정을 참조하십시오.

    DataSet 또는 DataReader 개체 반환

    SqlDataSource 컨트롤은 DataSet 개체 또는 ADO.NET 데이터 판독기라는 두 가지 형식으로 데이터를 반환할 수 있습니다. 데이터 소스 컨트롤의 DataSourceMode 속성을 설정하여 반환 형식을 지정할 수 있습니다. DataSet 개체에는 서버 메모리의 모든 데이터가 포함되어 있으며 데이터를 가져온 후 다양한 방법으로 조작할 수 있습니다.

    데이터 판독기는 개별 레코드를 페치할 수 있는 읽기 전용 커서를 제공합니다. 일반적으로 데이터를 가져온 후 데이터를 필터링, 정렬 또는 페이징하려는 경우나 캐시를 유지 관리하려는 경우에 데이터 집합을 반환하도록 선택합니다.

    이와 달리 단순히 데이터를 반환하기만 하고 페이지의 컨트롤을 사용하여 이 데이터를 표시하려는 경우에는 데이터 판독기를 사용합니다.

    예를 들어 결과 목록이 읽기 전용 형식으로 표시되는 ListBox, DropDownList 또는 GridView 컨트롤에 표시할 데이터를 반환하려면 데이터 판독기를 사용하는 것이 좋습니다.

    SqlDataSource.SelectCommand 속성

    <HTML>
        <BODY>
            <FORM runat="server">
                <asp:DropDownList
                    id="DropDownList1"
                    runat="server"
                    DataTextField="LastName"
                    DataSourceID="SqlDataSource1" />
                <asp:SqlDataSource
                    id="SqlDataSource1"
                    runat="server"
                    ConnectionString="<%$ ConnectionStrings:MyNorthwind%>"
                    SelectCommandType="StoredProcedure"               
                    SelectCommand="sp_lastnames">
                </asp:SqlDataSource>
                <!--
                    The sp_lastnames stored procedure is
                    CREATE PROCEDURE sp_lastnames AS
                       SELECT LastName FROM Employees
                    GO
                -->
            </FORM>
        </BODY>
    </HTML>

    SqlDataSource 컨트롤에 매개 변수 사용

    SELECT * FROM Employees WHERE LastName = @LastName AND FirstName = @FirstName

    라는 쿼리를 실행시키고 싶을때....

    명명된 매개 변수를 사용하는 경우 명령에 대한 매개 변수 컬렉션에 지정된 매개 변수의 순서는 별로 중요하지 않습니다. 그러나 SQL 명령에 사용하는 매개 변수의 이름이 연결된 컬렉션에 있는 매개 변수의 이름과 일치하도록 해야 합니다.

    다음 예제에서는 System.Data.SqlClient 공급자를 사용하는 SqlDataSource 컨트롤에 대한 SQL 명령에 명명된 매개 변수를 사용하는 방법을 보여 줍니다.

    <asp:sqlDataSource ID="EmployeeDetailsSqlDataSource"

    SelectCommand="SELECT EmployeeID, LastName, FirstName FROM Employees WHERE EmployeeID = @EmpID"

    InsertCommand="INSERT INTO Employees(LastName, FirstName) VALUES (@LastName, @FirstName);

    SELECT @EmpID = SCOPE_IDENTITY()"

    UpdateCommand="UPDATE Employees SET LastName=@LastName, FirstName=@FirstName WHERE EmployeeID=@EmployeeID"

    DeleteCommand="DELETE Employees WHERE EmployeeID=@EmployeeID" ConnectionString="<%$ ConnectionStrings:NorthwindConnection %>" OnInserted="EmployeeDetailsSqlDataSource_OnInserted"

    RunAt="server">

    <SelectParameters>

    <asp:Parameter Name="EmpID" Type="Int32" DefaultValue="0" /> </SelectParameters>

    <InsertParameters>

    <asp:Parameter Name="EmpID" Direction="Output" Type="Int32" DefaultValue="0" />

    </InsertParameters>

    </asp:sqlDataSource>

    SqlDataSource 컨트롤의 필터링을 사용하도록 설정

    SqlDataSource 컨트롤을 사용하면 쿼리를 다시 실행하지 않고도 쿼리 결과를 필터링(정렬 또는 선택)할 수 있습니다. SqlDataSource 컨트롤에 필터링을 추가하면 쿼리가 실행된 후 데이터베이스로 돌아가지 않고도 SqlDataSource를 통해 사용할 수 있는 데이터를 변경할 수 있습니다.

    SqlDataSource 컨트롤의 필터링을 사용하도록 설정하려면

    1. 유효한 연결 문자열과 Select 문을 사용하여 SqlDataSource 컨트롤을 만듭니다. 자세한 내용은 방법: SqlDataSource 컨트롤을 사용하여 SQL Server 데이터베이스에 연결을 참조하십시오.

    2. SqlDataSource 컨트롤의 DataSourceMode 속성을 DataSet로 설정합니다.

    3. SqlDataSource 컨트롤의 EnableCaching 속성을 true로 설정합니다.

      필터링을 지원하려면 쿼리에서 반환된 데이터를 캐싱해야 합니다.

    4. SqlDataSource 컨트롤의 CacheDuration 속성을 데이터를 캐싱할 시간(초)으로 설정합니다. 응용 프로그램에 따라 적절한 값을 선택합니다.

    5. 다음 예제처럼 컨트롤의 FilterExpression 속성을 반환할 데이터를 지정하는 식으로 설정합니다.

    7번 설명 country = 'Germany'

    필터 식 구문에 대한 자세한 내용은 RowFilter를 참조하십시오.

    필터링을 사용하도록 설정된 SqlDataSource 컨트롤은 다음과 같은 형태가 됩니다.

    <asp:SqlDataSource

    ID="SqlDataSource1"

    DataSourceMode="DataSet"

    EnableCaching="true"

    Runat="server"

    SelectCommand="Select * From Customers" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"

    FilterExpression="

    country = 'Germany'">

    </asp:SqlDataSource>

    위에는 별로 쓸모가 없네 ... 밑으로

    매개 변수를 사용하여 SqlDataSource 컨트롤을 필터링하려면

    1. SqlDataSource 컨트롤의 FilterExpression 속성을 필터 매개 변수 값의 자리 표시자가 포함된 식으로 설정합니다. 자리 표시자에는 {n} 구문을 사용합니다. 여기서 n은 매개 변수의 순서를 나타냅니다.

      다음 예제에서는 매개 변수가 있는 필터 식을 보여 줍니다. 두 번째 필터 식에는 매개 변수 자리 표시자가 여러 개 포함되어 있습니다.

    FilterExpression="category = '{0}'" FilterExpression="country = '{0}' AND city = '{1}'"

    FilterParameters 요소를 SqlDataSource 요소의 자식으로 만듭니다. 각 필터 매개 변수 자리 표시자에 대해 다음 중 한 형식의 요소를 추가합니다.

    • ControlParameter

    • CookieParameter

    • FormParameter

    • ProfileParameter

    • SessionParameter

    • Parameter

    다음 예제에서는 DropDownList 컨트롤에서 값을 가져오는 필터 매개 변수를 만드는

    방법을 보여 줍니다.

    <FilterParameters>

    <asp:ControlParameter Name="CategoryList" ControlID="DropDownList1" PropertyValue="SelectedValue" />

    </FilterParameters>

    매개 변수의 Name 속성은 필수 속성입니다.

    그러나 매개 변수는 이름이 아니라 순서에 따라 자리 표시자와 대응합니다.

    <asp:SqlDataSource

    ID="SqlDataSource1"

    EnableCaching="true"

    DataSourceMode="DataSet"

    Runat="server"

    SelectCommand="Select * from Customers" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString1 %>"

    FilterExpression="

    country = '{0}'">

    <FilterParameters>

    <asp:ControlParameter Name="countryparam" ControlID="DropDownList1" PropertyName="SelectedValue" /> <

    /FilterParameters>

    </asp:SqlDataSource>

    사용자가 컨트롤 하기

    사용자가 임의로 컨트롤 할 수 있따 아래는 삭제 예제 그럼 인설트 등등도 응용하면 되겠네

    ~~~

        protected void DataList1_DeleteCommand(object source, DataListCommandEventArgs e)
        {
    
    
    
            /* 예전에 는 이런방식으로.
            SqlConnection conn = new SqlConnection(AspxUtil.GetDBStr());
            conn.Open();
            SqlCommand comm = new SqlCommand("DELETE FROM COMMENT_BOARD WHERE IDX=@IDX", conn);
            comm.Parameters.Add("@IDX", SqlDbType.Int).Value = e.CommandArgument;
            comm.ExecuteNonQuery();        
            conn.Close();
            this.DataList1.DataBind();
            * */
    
    간단하게 구현된거~ 열라간단하다
    
            SqlDataSource1.DeleteParameters["idx"].DefaultValue =      e.CommandArgument.ToString();
            SqlDataSource1.Delete();
    
        }
    

    당연한 얘기지만 SqlDataSource에는 삭제 커멘드와

    <asp:SqlDataSource ID="SqlDataSource1" runat="server" ...

    DeleteCommand="DELETE FROM [COMMENT_BOARD] WHERE [idx] = @idx"

    />

        <DeleteParameters>
            <asp:Parameter Name="idx" Type="Int32" />
        </DeleteParameters>

    파라메터 값이 있어야 한다

    사용자 임의 삭제 참고 MSDN

    ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.ko/dv_aspnetcon/html/44c76175-34b4-4dec-95d7-f9f6fd1fe00d.htm

    런타임에 DataList 항목 사용자 지정

        protected void DataList1_ItemDataBound(object sender, DataListItemEventArgs e)
        {
    
      //항목구분해줘야 하네... e.Item.ItemType == ListItemType.Item 만하니깐 한다리건너 나오고 나오고 한다 ㅋㅋㅋ
    
            if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
            {
                System.Data.DataRowView drv = (System.Data.DataRowView)(e.Item.DataItem);
                Response.Write(drv.Row["member_id"].ToString() + "<br>");
            }
    
        }
    

    간단하게 설명해서 게시판 리스트가 쭈욱 나오는데 아이디를 비교해서 삭제 버튼을 보여줄 필요가 있따  그때 각행을 돌때 아이디를 비교 해서 작업을 처리할수 있따

     

     

    페이지를 처리하는 동안 DataList 컨트롤은 페이지에 렌더링할 개별 항목을 만듭니다. 항목이 만들어질 때 이를 사용자 지정할 수 있도록 DataList 컨트롤은 다음과 같은 두 개의 이벤트를 발생시킵니다.

    • ItemCreated 이벤트는 DataList 컨트롤이 각 항목, 교대로 반복되는 항목, 머리글, 바닥글 등을 만들 때 발생됩니다.

    • ItemDataBound 이벤트는 항목의 컨트롤이 데이터 바인딩된 다음에 발생됩니다. 이벤트 매개 변수를 사용하여 데이터 바인딩에 사용되는 데이터에 액세스할 수 있습니다. 데이터에 따라 사용자 지정하는 경우 이 이벤트를 사용합니다.

    이러한 이벤트에 대한 응답으로 항목을 변경할 수 있습니다. 흔한 예로, 항목에 표시되는 데이터에 따라 항목의 모양을 변경할 수 있습니다. 예를 들어 데이터베이스에서 읽은 숫자 값이 음수이면 항목의 배경색을 빨강으로 설정합니다.

    ItemCreated 이벤트를 사용하여 런타임에 항목을 사용자 지정하려면

    1. DataList 컨트롤의 ItemCreated 이벤트에 대한 이벤트 처리기를 만듭니다.

    2. 이벤트 처리기에서 Item 개체를 사용하여 e 이벤트 인수 개체에서 현재 항목을 가져옵니다. 현재 항목의 인덱스는 Item 개체의 ItemIndex 속성을 통해 사용할 수 있으며 해당 형식은 ItemType 속성(ListItemType 열거형을 사용하여 정의됨)을 통해 사용할 수 있습니다.

      참고

      ItemIndex 속성은 Item, AlternatingItem 및 SelectedItem 개체에 대해서만 양수 값을 반환합니다. 머리글, 바닥글, 구분 기호 항목의 인덱스 값은 -1입니다.

      다음 예제에서는 항목의 배경색을 조건에 따라 수정할 수 있는 방법을 보여 줍니다. 이 예제에서는 ItemType 속성과 ListItemType 열거형을 사용하여 항목의 배경색을 설정합니다. 항목은 LightGoldenrodYellow로, 교대로 반복되는 항목은 DarkGoldenrod로, 선택한 항목은 Yellow로 각각 설정됩니다.

    protected void DataList1_ItemCreated(object sender, 
        EventArgs e)
    {
       switch (e.Item.ItemType)
       {
          case ListItemType.Item :
             e.Item.BackColor = 
                 System.Drawing.Color.LightGoldenrodYellow;
             break;
          case ListItemType.AlternatingItem :
             e.Item.BackColor = System.Drawing.Color.DarkGoldenrod;
             break;
          case ListItemType.SelectedItem :
             e.Item.BackColor = System.Drawing.Color.Yellow;
             break;
          default :
             // Add code here to handle the header, footer, and 
             // separator templates.
             break;
          }
    }

    ItemCreated 이벤트가 발생된 당시에는 템플릿의 개별 컨트롤이 아직 데이터에 바인딩되어 있지 않지만 ItemDataBound 이벤트에서 데이터를 사용할 수 있습니다.

    ItemDataBound 이벤트를 사용하여 런타임에 항목을 사용자 지정하려면

    DataList 컨트롤의 ItemDataBound 이벤트에 대한 이벤트 처리기를 만듭니다.

    이벤트 처리기에서 머리글 또는 바닥글이 아닌 Item, AlternatingItem 또는 EditItem으로 작업 중인지 테스트하십시오.

    DataItem 속성을 사용하여 컨트롤에 바인딩될 데이터를 테스트하고 이를 비교 기준으로 사용합니다.

    다음 예제에서는 데이터를 바탕으로 하는 조건적 테스트 방법을 보여 줍니다. 예제에서는 이벤트 인수의 DataItem 속성을 DataRowView 개체로 캐스팅합니다. 그런 다음 현재 DataItem에서 Quantity라는 필드를 추출합니다. Quantity가 10보다 작으면 DataList 컨트롤의 항목이 빨강으로 설정됩니다.

    protected void DataList1_ItemDataBound(object sender, 
            DataListItemEventArgs e)
    {
       if(e.Item.ItemType == ListItemType.Item || 
              e.Item.ItemType == ListItemType.AlternatingItem)
       {
           System.Data.DataRowView drv = 
               (System.Data.DataRowView)(e.Item.DataItem);
           int quantity = int.Parse(drv.Row["Quantity"].ToString());
           if (quantity < 10)
           {
              e.Item.BackColor = Color.Red;
           }
        }
    }

    또 다른 예제) 컨트롤에 맞게 dataList 안에있는 컨트롤에 값을 전달하기

    protected void DataListLoveletter_ItemDataBound(object sender, DataListItemEventArgs e)
        {
    
            System.Data.DataRowView drv = (System.Data.DataRowView)(e.Item.DataItem);
            //하이퍼링크 컨트롤에 제목넣기
            Control c = e.Item.FindControl("HyperLinkTitle");
            if (c != null) {
                ((HyperLink)c).Text = drv["title"].ToString()         
    
             }
    
    }
    

    http://msdn.microsoft.com/ko-kr/library/system.transactions.transactionscope.aspx

    System.Transactions 인프라는 Transaction 클래스 기반의 명시적 프로그래밍 모델과 TransactionScope 클래스를 사용하는 암시적 프로그래밍 모델을 둘 다 제공합니다. 후자의 경우 인프라에서 자동으로 트랜잭션을 관리합니다.

    new 문으로 TransactionScope를 인스턴스화하면 트랜잭션 관리자가 참여할 트랜잭션을 결정합니다. 결정 후에는 이 범위가 항상 해당 트랜잭션에 참여합니다. 앰비언트 트랜잭션이 있는지 여부와 생성자에 있는 TransactionScopeOption 매개 변수의 값에 따라 참여할 트랜잭션이 결정됩니다. 앰비언트 트랜잭션이란 해당 코드가 실행되는 트랜잭션입니다. Transaction 클래스의 정적 Current 속성을 호출하면 앰비언트 트랜잭션에 대한 참조를 가져올 수 있습니다. 이 매개 변수의 사용 방법에 대한 자세한 내용은 트랜잭션 범위를 사용하여 암시적 트랜잭션 구현 항목의 "트랜잭션 흐름 관리" 단원을 참조하십시오.

    트랜잭션 범위(즉, TransactionScope 개체 초기화와 Dispose 메서드 호출 사이)에서 예외가 발생하지 않으면 범위가 참여하는 트랜잭션을 계속할 수 있습니다. 트랜잭션 범위에 예외가 발생하면 범위가 참여하는 트랜잭션이 롤백됩니다.

    응용 프로그램이 트랜잭션에서 수행할 작업을 모두 완료하면 Complete 메서드를 한 번만 호출하여 트랜잭션 커밋이 허용됨을 트랜잭션 관리자에게 알려야 합니다. 이 메서드를 호출하지 못하면 트랜잭션이 중단됩니다.

    Dispose 메서드를 호출하면 트랜잭션 범위의 끝이 표시됩니다. 이 메서드를 호출한 후에 발생하는 예외는 트랜잭션에 영향을 주지 않습니다.

    범위 안의 Current 값을 수정하면 Dispose를 호출할 경우 예외가 throw됩니다. 그러나 범위 끝에서 이전 값이 복원됩니다. 또한 트랜잭션을 만든 트랜잭션 범위 안의 Current에서 Dispose를 호출하면 범위 끝에서 트랜잭션이 중단됩니다.

    // This function takes arguments for 2 connection strings and commands to create a transaction 
    // involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the 
    // transaction is rolled back. To test this code, you can connect to two different databases 
    // on the same server by altering the connection string, or to another 3rd party RDBMS by 
    // altering the code in the connection2 code block.
    static public int CreateTransactionScope(
        string connectString1, string connectString2,
        string commandText1, string commandText2)
    {
        // Initialize the return value to zero and create a StringWriter to display results.
        int returnValue = 0;
        System.IO.StringWriter writer = new System.IO.StringWriter();
    
        // Create the TransactionScope to execute the commands, guaranteeing
        // that both commands can commit or roll back as a single unit of work.
        using (TransactionScope scope = new TransactionScope())
        {
            using (SqlConnection connection1 = new SqlConnection(connectString1))
            {
                try
                {
                    // Opening the connection automatically enlists it in the 
                    // TransactionScope as a lightweight transaction.
                    connection1.Open();
    
                    // Create the SqlCommand object and execute the first command.
                    SqlCommand command1 = new SqlCommand(commandText1, connection1);
                    returnValue = command1.ExecuteNonQuery();
                    writer.WriteLine("Rows to be affected by command1: {0}", returnValue);
    
                    // If you get here, this means that command1 succeeded. By nesting
                    // the using block for connection2 inside that of connection1, you
                    // conserve server and network resources as connection2 is opened
                    // only when there is a chance that the transaction can commit.   
                    using (SqlConnection connection2 = new SqlConnection(connectString2))
                        try
                        {
                            // The transaction is escalated to a full distributed
                            // transaction when connection2 is opened.
                            connection2.Open();
    
                            // Execute the second command in the second database.
                            returnValue = 0;
                            SqlCommand command2 = new SqlCommand(commandText2, connection2);
                            returnValue = command2.ExecuteNonQuery();
                            writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
                        }
                        catch (Exception ex)
                        {
                            // Display information that command2 failed.
                            writer.WriteLine("returnValue for command2: {0}", returnValue);
                            writer.WriteLine("Exception Message2: {0}", ex.Message);
                        }
                }
                catch (Exception ex)
                {
                    // Display information that command1 failed.
                    writer.WriteLine("returnValue for command1: {0}", returnValue);
                    writer.WriteLine("Exception Message1: {0}", ex.Message);
                }
            }
    
            // The Complete method commits the transaction. If an exception has been thrown,
            // Complete is not  called and the transaction is rolled back.
            scope.Complete();
        }
    
        // The returnValue is greater than 0 if the transaction committed.
        if (returnValue > 0)
        {
            writer.WriteLine("Transaction was committed.");
        }
        else
        {
            // You could write additional business logic here, for example, you can notify the caller 
            // by throwing a TransactionAbortedException, or logging the failure.
            writer.WriteLine("Transaction rolled back.");
        }
    
        // Display messages.
        Console.WriteLine(writer.ToString());
    
        return returnValue;
    } 
    

    + Recent posts