이 기사는 Silverlight 2 및 ADO.NET 데이터 서비스 시험판 버전을 기준으로 합니다. 여기에 포함된 모든 정보는 변경될 수 있습니다.
이 기사에서는 다음 내용에 대해 설명합니다.
- 데이터 원본에 Silverlight 연결
- ADO.NET 데이터 서비스 사용
- LINQ to SQL 사용
- 서비스 디버깅
|
이 기사에서 사용하는 기술:
Silverlight 2 베타 2, LINQ, ADO.NET 데이터 서비스
|
목차
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 기술의 모든 이점을 갖춘 강력한 데이터 기반 웹 응용 프로그램을 작성할 수 있습니다. 이러한 기술에 대한 자세한 내용은 "추가 정보" 보충 기사를 참조하십시오.