관련 링크patterns & practices Index (영문) .NET: 응용 프로그램 및 서비스 디자인을 위한 응용 프로그램 아키텍처 Alex Mackman, Chris Brooks, Steve Busby, Ed Jezierski, Jason Hogg, Roberta Leibovitz(Modeled Computation) 및 Colin Campbell(Modeled Computation) Microsoft Corporation 2001년 10월 2003년 6월 업데이트 요약: 이 문서에서는 다중 계층 .NET 기반 응용 프로그램에서 ADO.NET 기반 데이터 액세스 계층을 구현하기 위한 지침을 제공합니다. 이 문서에서는 일반적인 데이터 액세스 작업 및 시나리오에 대해 집중적으로 설명하고 가장 적합한 접근 방식 및 기술 선택에 도움이 되는 지침을 제공합니다. 소개.NET 기반 응용 프로그램을 위한 데이터 액세스를 디자인하는 경우 Microsoft ADO.NET을 데이터 액세스 모델로 사용해야 합니다. ADO.NET은 다양한 기능을 제공하며 느슨하게 연결된 다중 계층 웹 응용 프로그램 및 웹 서비스에 필요한 데이터 액세스 요구 사항을 지원합니다. 다른 다양한 기능의 개체 모델들과 같이 ADO.NET은 특정 문제를 해결할 수 있는 여러 방법을 제공합니다. .NET 데이터 액세스 아키텍처 가이드는 가장 적합한 데이터 액세스 접근 방식을 선택하는 데 도움이 되는 정보를 제공합니다. 이 설명서에서는 폭 넓은 일반적 데이터 액세스 시나리오에 대해 설명하고 성능 팁을 제공하고 최상의 실행 방법을 처방합니다. 이 가이드에서는 또한 데이터베이스 연결 문자열을 저장하기에 가장 적합한 장소는 어디입니까? 연결 풀링을 어떻게 구현합니까? 트랜잭션 작업을 어떻게 수행합니까? 대규모 레코드에서 사용자 스크롤을 허용하려면 페이지를 어떻게 구현해야 합니까?와 같은 자주 묻는 질문에 대한 해답을 제공합니다. 이 가이드에서는 ADO.NET에 제공된 두 공급자 중 하나인 SQL Server .NET 데이터 공급자를 사용하여 Microsoft SQL Server에 액세스하기 위해 ADO.NET을 사용하는 것과 관련된 내용을 중점적으로 다룹니다. 필요에 따라 이 가이드에서는 다른 OLE DB 인식 데이터 원본에 액세스하기 위해 OLE DB .NET 데이터 공급자를 사용할 때 알고 있어야 하는 차이점들에 대해서도 자세히 다룹니다. 이 문서에 설명된 지침 및 최상의 실행 방법을 사용하여 개발된 데이터 액세스 구성 요소에 대한 확실한 구현을 위해서는 데이터 액세스 응용 프로그램 블록을 참조하십시오. 데이터 액세스 응용 프로그램 블록에는 구현에 대한 원본 코드가 포함되어 있으며 이 코드를 자신의 .NET 기반 응용 프로그램에 직접 사용할 수 있습니다. .NET 데이터 액세스 아키텍처 가이드에는 다음 섹션이 포함됩니다.
이 문서의 대상 독자이 문서는 .NET 기반 응용 프로그램을 작성하려는 응용 프로그램 설계자 및 엔터프라이즈 개발자를 위한 지침을 제공합니다. 자신의 업무가 다중 계층 .NET 기반 응용 프로그램의 데이터 계층을 디자인 및 개발하는 것이라면 이 문서를 읽어 보십시오. 기본 필수 지식이 가이드를 통해 .NET 기반 응용 프로그램을 작성하려면 ActiveX Data Objects(ADO) 및/또는 OLE DB를 사용한 데이터 액세스 코드 개발 경험 뿐만 아니라 SQL Server 사용 경험이 필요합니다. 또한 .NET 플랫폼에 대한 관리 코드를 개발하는 방법을 이해해야 하며 ADO.NET 데이터 액세스 모델이 제공하는 기본적인 변경 사항에 대해서도 알고 있어야 합니다. .NET 개발에 대한 자세한 내용은 http://www.microsoft.com/korea/msdn/netframework/을 참조하십시오. 새로운 기능이 문서 업데이트에는 데이터베이스 업데이트 수행, 형식있는 DataSets 사용 및 Null 데이터 필드 사용에 대한 섹션이 포함됩니다. 텍스트에 표시된 대로 이 가이드의 내용 중 일부는 특히 Microsoft Visual Studio 2003 개발 시스템 및 .NET Framework SDK 버전 1.1에 적용됩니다. .NET 데이터 액세스 아키텍처 가이드 다운로드.NET 데이터 액세스 아키텍처 가이드를 다운로드하려면 MS.com 다운로드 센터 (영문)를 클릭하십시오. ADO.NET 소개ADO.NET은 .NET 기반 응용 프로그램을 위한 데이터 액세스 모델입니다. ADO.NET을 사용하면 OLE DB 또는 ODBC 공급자가 있는 SQL Server 2000, Oracle 및 기타 데이터 원본과 같은 관계형 데이터베이스 시스템에 액세스할 수 있습니다. ADO.NET은 어느 정도 최신 ADO 기술의 발전을 나타냅니다. 하지만 ADO.NET은 느슨하게 연결된(그리고 내재적으로 연결이 해제된) 웹 응용 프로그램 특성을 위한 주요 변경 내용과 혁신 기술을 제공합니다. ADO와 ADO.NET을 비교하려면 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/adonetprogmsdn.asp (영문)에서 MSDN 문서 "ADO 프로그래머를 위한 ADO.NET"을 참조하십시오 ADO.NET에서 제공되는 주요 변경 내용 중 하나는 ADO Recordset 개체가 DataTable, DataSet, DataAdapter 및 DataReader 개체의 조합으로 바뀌었다는 점입니다. DataTable은 단일 테이블의 행 컬렉션을 나타내며 이러한 관점에서 볼 때 Recordset과 비슷합니다. DataSet은 여러 테이블을 하나로 바인딩하는 관계 및 제약 조건이 포함된 DataTable 개체 컬렉션을 나타냅니다. 실제로 DataSet은 XML 지원이 기본 제공되는 인-메모리 관계형 구조에 있습니다. DataSet의 주요 특성 중 하나는 이를 채우는 데 사용될 수 있는 기본 데이터 원본에 대한 인식이 없다는 점입니다. DataSet은 데이터 컬렉션을 나타내기 위해 사용되는 연결이 끊긴 독립 실행형 엔터티이며 다중 계층 응용 프로그램의 여러 계층을 통해 구성 요소 간에 전달될 수 있습니다. 또한 XML 데이터 스트림으로 직렬화될 수 있기 때문에 유형이 다른 플랫폼 간의 데이터 전송에 이상적입니다. ADO.NET은 DataAdapter 개체를 사용하여 DataSet 및 기본 데이터 원본에 데이터를 연결합니다. DataAdapter 개체는 또한 이전에 Recordset과 연결된 향상된 일괄 처리 업데이트 기능을 제공합니다. 그림 1은 완전한 DataSet 개체 모델을 보여 줍니다. 그림 1.1 DataSet 개체 모델 .NET 데이터 공급자ADO.NET은 .NET 데이터 공급자의 서비스에 의존합니다. 이러한 서비스는 기본 데이터 원본에 대한 액세스를 제공하며 네 개의 핵심 개체(Connection, Command, DataReader 및 DataAdapter)를 구성합니다. 현재까지 ADO.NET에는 브리지 공급자와 네이티브 공급자의 두 가지 종류의 공급자가 제공됩니다. OLE DB 및 ODBC에 대해 제공되는 것과 같이 브리지 공급자를 사용하면 이전의 데이터 액세스 기술을 위해 디자인된 데이터 라이브러리를 사용할 수 있습니다. 네이티브 공급자는 SQL Server 및 Oracle 공급자와 같이 부분적으로 추상 계층이 하나 적다는 이유로 인해 일반적으로 향상된 성능을 제공합니다.
SQL Server 7.0 또는 SQL Server 2000에 연결할 때는 항상 이 공급자를 사용하십시오.
현재 베타 테스트에 포함된 다른 .NET 데이터 공급자에는 다음이 포함됩니다.
다른 데이터 공급자에 대한 자세한 개요를 보려면 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconADONETProviders.asp (영문)의 .NET Framework 개발자 가이드에서 ".NET Framework 데이터 공급자"를 참조하십시오. 이름 공간 구성각 .NET 데이터 공급자와 연결된 유형(클래스, 구성, 열거 등)은 해당 고유 이름 공간에 있습니다.
해당 관련 이름 공간 내에서 각 공급자는 Connection, Command, DataReader 및 DataAdapter 개체의 구현을 제공합니다. SqlClient 구현의 접두사는 "Sql"이고 OleDb 구현의 접두사는 "OleDb"입니다. 예를 들어 Connection 개체의 SqlClient 구현 이름은 SqlConnection이고 OleDb에 해당하는 이름은 OleDbConnection입니다. 이와 비슷하게 DataAdapter 개체의 두 가지 구체적인 구현 이름은 각각 SqlDataAdapter 및 OleDbDataAdapter입니다. 이 설명서에서 제공되는 예는 SQL Server 개체 모델로부터 가져온 것입니다. 여기에 설명되지는 않았지만 Oracle/OLEDB 및 ODBC에서도 비슷한 기능이 제공됩니다. 일반 프로그래밍다른 데이터 원본을 대상화할 가능성이 있고 데이터 원본 간에 코드를 이동하려는 경우에는 System.Data 이름 공간 내에 있는 IDbConnection, IDbCommand, IDataReader 및 IDbDataAdapter 인터페이스에 대한 프로그래밍을 고려하십시오. Connection, Command, DataReader 및 DataAdapter 개체의 모든 구현에서는 이러한 인터페이스가 지원되어야 합니다. .NET 데이터 공급자에 대한 자세한 내용은 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconImplementingNETDataProvider.asp (영문)를 참조하십시오. 또한 응용 프로그램에 여러 데이터베이스에 액세스하기 위해 단일 개체 모델이 사용되는 경우 OLE DB 및 ODBC 연결 공급자가 대신 사용됩니다. 이 경우 응용 프로그램에 필요한 유연성, 데이터베이스 관련 기능이 필요한 정도를 응용 프로그램의 성능 요구 사항에 비추어 고려해야 합니다. 그림 2에서는 데이터 액세스 스택과 ADO.NET이 ADO 및 OLE DB를 비롯한 다른 데이터 액세스 기술과 관련된 방식을 보여 줍니다. 또한 두 개의 관리 공급자와 ADO.NET 모델 내의 주요 개체를 보여 줍니다. 그림 1.2 데이터 액세스 스택 ADO에서 ADO.NET으로 발전된 자세한 내용을 보려면 http://msdn.microsoft.com/msdnmag/issues/1100/adoplus/default.aspx (영문)에서 2000년 11월 MSDN Magazine에 있는 문서"ADO+ 소개: Microsoft .NET Framework를 위한 데이터 액세스 서비스"를 참조하십시오. 저장 프로시저와 직접적인 SQL이 문서에 표시된 대부분의 코드 단편에서는 SqlCommand 개체를 사용하여 데이터베이스 조작을 수행하기 위한 저장 프로시저를 호출합니다. 일부 경우에는 저장 프로시저 이름이 SqlDataAdapter 개체에 직접 전달되기 때문에 SqlCommand 개체가 표시되지 않습니다. 이 경우에도 내부적으로는 SqlCommand 개체가 만들어집니다. 포함된 SQL 문 대신 저장 프로시저를 사용해야 하는 이유는 다음과 같이 여러 가지입니다.
SQL Server 온라인 설명서에서는 생성한 저장 프로시저의 이름 접두사에 "sp_"가 포함되지 않도록 권장합니다. 이 이름 접두사는 시스템 저장 프로시저 이름 접두사로 지정되어 있습니다. SQL Server는 항상 다음과 같은 순서로 sp_로 시작하는 저장 프로시저를 조회합니다.
속성과 생성자 인수생성자 인수를 통하거나 속성을 직접 설정하여 ADO.NET 개체의 특정 속성 값을 설정할 수 있습니다. 예를 들어 다음 코드 단편은 기능적으로 동일합니다. // Use constructor arguments to configure command objectSqlCommand cmd = new SqlCommand( "SELECT * FROM PRODUCTS", conn );// The above line is functionally equivalent to the following// three lines which set properties explicitlysqlCommand cmd = new SqlCommand();cmd.Connection = conn;cmd.CommandText = "SELECT * FROM PRODUCTS"; 성능적인 관점에서 볼 때 .NET 개체에 대해 속성을 설정하고 가져오는 것이 COM 개체에 대해 비슷한 작업을 수행하는 것보다 효율적이기 때문에 두 접근 방식 간의 차이점은 무시할 수 있습니다. 두 방식은 개인적인 선호나 코딩 스타일에 따라 선택될 수 있습니다. 하지만 속성을 명시적으로 설정하면 특히 사용자가 ADO.NET 개체 모델에 익숙하지 않은 경우 코드를 더욱 쉽게 이해하고 디버깅할 수 있습니다. 참고과거에는 Microsoft Visual Basic 개발 시스템 개발자가 "Dim x As New..." 구성으로 개체를 만들지 않는 것이 일반적이었습니다. COM 기반에서는 이러한 코드를 사용할 경우 COM 개체의 생성 프로세스가 짧게 순환되어 미묘하거나 일부 확실한 버그가 발생할 수 있었습니다. 하지만 .NET 기반에서는 이러한 코드가 더 이상 문제가 되지 않습니다. 데이터베이스 연결 관리데이터베이스 연결은 특히 다중 계층 웹 응용 프로그램에서 핵심적이고 비용이 높은 제한적 리소스를 나타냅니다. 접근 방식은 응용 프로그램의 전반적인 확장성에 중요한 영향을 줄 수 있기 때문에 연결을 올바르게 관리해야 합니다. 또한 연결 문자열을 저장할 위치에 대해서도 신중히 고려해야 합니다. 이를 위해서는 구성 가능한 보안 위치가 필요합니다. 데이터베이스 연결 및 연결 문자열을 관리할 때는 다음과 같은 문제를 해결해야 합니다.
이 섹션에서는 연결 풀링에 대해 설명하고 적합한 연결 풀링 전략을 선택할 수 있도록 도와줍니다. 이 섹션에서는 또한 데이터베이스 연결 문자열을 관리 및 저장하는 방법에 대해서도 고려합니다. 마지막으로 이 섹션에서는 연결이 제대로 닫히고 연결 풀로 돌아갈 수 있도록 보장하기 위해 사용할 수 있는 두 가지 코딩 패턴을 제공합니다. 연결 풀링 사용데이터베이스 연결 풀링을 사용하면 응용 프로그램에서 데이터베이스에 새로운 연결을 반복적으로 설정하는 대신 풀에서 기존 연결을 다시 사용할 수 있습니다. 이 기술은 제한된 개수의 데이터베이스 연결이 매우 많은 수의 클라이언트를 지원하기 때문에 응용 프로그램의 확장성을 향상시켜 줍니다. 이 기술은 또한 새 연결을 설정하는 데 필요한 상당한 시간을 없애주기 때문에 성능을 향상시켜 줍니다. ODBC와 OLE DB와 같은 데이터 액세스 기술은 여러 수준으로 구성할 수 있는 연결 풀링 형식을 제공합니다. 두 접근 방식 모두 데이터베이스 클라이언트 응용 프로그램에 대부분 투명하게 이뤄집니다. OLE DB 연결 풀링은 세션 또는 리소스 풀링이라고도 자주 부릅니다. MDAC(Microsoft Data Access Components) 내의 풀링에 대한 일반적인 내용은 http://msdn.microsoft.com/SQL/sqlreldata/ADO.NET/default.aspx?pull=/library/en-us/dnmdac/html/pooling2.asp (영문)에서 "Microsoft Data Access Components 풀링"을 참조하십시오. ADO.NET 데이터 공급자는 각 공급자에 따라 다른 정확한 방식인 투명한 연결 풀링을 제공합니다. 이 섹션에서는 다음과 관련된 연결 풀링에 대해 설명합니다.
SQL Server .NET Data Provider 풀링SQL Server .NET Data Provider를 사용하는 경우 공급자에서 제공되는 연결 풀링 지원을 사용하십시오. 이러한 지원은 관리 코드 내에서 공급자에 의해 내부적으로 구현되어 트랜잭션을 지원하는 효율적인 메커니즘입니다. 풀은 응용 프로그램 도메인별 기준으로 생성되며 응용 프로그램이 언로드되기 전에는 삭제되지 않습니다. 이러한 형식의 연결 풀링을 투명하게 사용할 수 있지만 사용자는 풀의 관리 방법과 연결 풀링을 세부적으로 조정하는 데 사용할 수 있는 다양한 구성 옵션에 대해 알아야 합니다. 대부분의 경우 SQL Server .NET 데이터 공급자에 대한 기본 연결 풀링 설정 만으로도 응용 프로그램에 충분할 수 있습니다. .NET 기반 응용 프로그램의 개발 및 테스트 중에는 연결 풀 크기를 수정해야 할지 여부를 확인하기 위해 임시 트래픽 패턴을 시뮬레이션하는 것이 좋습니다. 확장 가능한 고성능 응용 프로그램을 개발 중인 개발자는 연결 사용 시간을 최소화하고 데이터를 검색하거나 업데이트하는 데 걸리는 시간 동안만 연결이 열려 있도록 유지해야 합니다. 연결이 닫히면 연결 풀로 돌아가서 다시 사용할 수 있습니다. 이 경우 데이터베이스에 대한 실제 연결은 제공되지 않지만 연결 풀링이 해제된 경우 데이터베이스에 대한 실제 연결이 닫힙니다. 개발자는 참조 범위가 벗어날 때 연결이 반드시 닫힐 필요가 없기 때문에 연결을 해제하기 위해 가비지 수집기에 의존하지 않도록 주의해야 합니다. 이는 연결 누출의 일반적인 원인이며 새로운 연결이 요청될 때 연결 예외를 일으킵니다. SQL Server .NET Data Provider 연결 풀링 구성연결 문자열을 통해 제공된 일련의 이름-값 쌍을 사용하여 연결 풀링을 구성할 수 있습니다. 예를 들어 풀링을 설정할지 여부(기본값은 설정), 최대 및 최소 풀 크기, 지연된 연결 열기 요청이 차단할 수 있는 시간 등을 구성할 수 있습니다. 다음은 최대 및 최소 풀 크기를 구성하는 연결 문자열 예입니다. "Server=(local); Integrated Security=SSPI; Database=Northwind; Max Pool Size=75; Min Pool Size=5" 연결이 열리고 풀이 생성되면 여러 연결이 풀에 추가되어 연결 개수를 구성된 최소 수준으로 만듭니다. 이후에는 구성된 최대 풀 개수까지 풀에 연결을 추가할 수 있습니다. 최대 개수에 도달하면 새로운 연결 열기 요청이 구성 가능한 기간 동안 큐에 지정됩니다. 풀 크기 선택수 천의 클라이언트에 대한 동시 연결을 관리하는 대규모 시스템의 경우에는 반드시 최대 임계값을 설정할 수 있어야 합니다. 시스템에 최적의 풀 크기를 확인하려면 연결 풀링과 응용 프로그램의 성능을 모니터링해야 합니다. 최적 크기는 또한 SQL Server를 실행 중인 하드웨어에 따라서도 달라집니다. 개발 중에는 연결 누출을 찾기 위해 기본 최대 풀 크기(현재 100)를 줄이는 것이 좋습니다. 최소 풀 크기를 설정하는 경우 처음 연결하는 몇 개의 클라이언트에는 성능이 높아지지만 이 수준을 만들기 위해 풀을 처음으로 채울 때 약간의 성능 오버헤드가 발생할 수 있습니다. 새 연결을 만드는 프로세스는 직렬화되어 있어서 처음 풀을 채울 때 동시 요청으로 서버가 넘쳐나지 않습니다. 연결 풀링 모니터링에 대한 자세한 내용은 이 문서에서 연결 풀링 모니터링 섹션을 참조하십시오. 연결 풀링 연결 문자열 키워드에 대한 전체 목록을 보려면http://www.microsoft.com/korea/msdn/library/ko-kr/cpguide/html/cpconConnectionPoolingForSQLServerNETDataProvider.aspx의 .NET Framework 개발자 가이드에서 ".NET Framework Data Provider for SQL Server를 위한 연결 풀링"을 참조하십시오. 추가 정보SQL Server .NET Data Provider 연결 풀링을 사용할 때는 다음에 주의하십시오.
OLE DB .NET Data Provider 풀링OLE DB .NET Data Provider는 OLE DB 리소스 풀링의 기본 서비스를 사용하여 연결을 풀링합니다. 리소스 풀링 구성을 위해서는 여러 옵션이 지원됩니다.
레지스트리 관련 배포 문제를 방지하려면 레지스트리를 사용하여 OLE DB 리소스 풀링을 구성하지 마십시오. OLE DB 리소스 풀링에 대한 자세한 내용은 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/oledb/htm/oledbabout_the_ole_db_documentation.asp (영문)에서 OLE DB Programmer's Reference의 19장 "OLE DB Services"에 있는 "Resource Pooling"을 참조하십시오. 풀링된 개체로 연결 풀링 관리Windows DNA 개발자의 경우 OLE DB 리소스 풀링 및/또는 ODBC 연결 풀링을 해제하고 데이터베이스 연결 풀링을 위한 기술로 COM+ 개체 풀링을 사용하는 것이 좋습니다. 이에 대한 두 가지 주요 이유는 다음과 같습니다.
하지만 SQL Server .NET Data Provider는 풀링을 내부적으로 사용하기 때문에 이 공급자를 사용할 경우 자체 개체 풀링 메커니즘을 개발할 필요가 없습니다. 따라서 수동 트랜잭션 등록과 관련된 복잡한 문제들을 피할 수 있습니다. 뛰어난 구성 및 향상된 성능을 활용하기 위해 OLE DB .NET Data Provider를 사용할 경우에는 COM+ 개체 풀링을 고려할 수 있습니다. 이러한 목적으로 풀링된 개체를 개발할 경우에는 OLE DB 리소스 풀링 및 자동 트랜잭션 등록을 해제해야 합니다(예: 연결 문자열에 "OLE DB Services=-4" 포함). 풀링된 개체 구현 내에서 트랜잭션 등록을 처리해야 합니다. 연결 풀링 모니터링응용 프로그램에서 사용되는 연결 풀링을 모니터링할 경우 SQL Server에 제공되는 프로파일러 도구를 사용하거나 Microsoft Windows 2000 운영 체제에 제공되는 성능 모니터 도구를 사용할 수 있습니다. SQL Server 프로파일러를 사용하여 연결 풀링을 모니터링하려면
성능 모니터를 사용하여 연결 풀링을 모니터링하려면
보안 관리데이터베이스 연결 풀링을 사용하면 응용 프로그램의 전반적 성능이 향상되지만 데이터베이스에서 더 이상 보안을 관리할 수 없습니다. 이러한 이유는 연결 풀링을 지원하기 위해 연결 문자열이 동일해야 하기 때문입니다. 사용자별 기반으로 데이터베이스 작업을 추적해야 하는 경우 사용자 ID를 전달하고 데이터베이스에서 사용자 작업을 수동으로 로그할 수 있는 매개 변수를 추가하도록 고려하십시오. 각 작업에 이 매개 변수를 추가해야 합니다. Windows 인증 사용SQL Server에 연결할 때는 다양한 장점을 제공하는 Windows 인증을 사용하는 것이 좋습니다.
Windows 인증을 사용하여 SQL Server에 액세스할 때는 다음 지침을 따르십시오.
보안 ASP.NET 및 웹 응용 프로그램에 대한 일반 개발 지침을 보려면 다음 Microsoft patterns & practices 지침을 참조하십시오.
연결 문자열 저장데이터베이스 연결 문자열을 저장하기 위해서는 다양한 유연성 및 보안성을 제공하는 옵션 중에서 선택할 수 있습니다. 원본 코드 내에서 연결 문자열을 하드 코딩하면 최상의 성능을 제공할 수 있지만 파일 시스템 캐싱을 사용하면 파일 시스템에서 외부적으로 문자열을 저장하는 것과 관련된 성능 감소를 무시할 수 있는 수준으로 만들어 줍니다. 관리자 구성을 지원하는 외부 연결 문자열에 의해 제공되는 추가 유연성은 거의 모든 경우에 선호됩니다. 연결 문자열 저장소를 위한 접근 방식을 선택할 경우 가장 중요한 두 가지 고려 사항은 성능과 밀접하게 연관된 보안성 및 구성 용이성입니다. 데이터베이스 연결 문자열 저장을 위해서는 다음 위치 중에서 선택할 수 있습니다.
SQL Server 액세스를 위해 Windows 인증을 사용할 경우 연결 문자열에서 강력한 사용자 이름 및 암호를 피할 수 있습니다. 보안 요구 사항에 따라 더욱 엄격한 방식이 필요한 경우 연결 문자열을 암호화된 형식으로 저장하십시오. ASP.NET 웹 응용 프로그램의 경우 연결 문자열을 Web.config 파일 내에 암호화된 형식으로 저장하면 보안적이고 구성 가능한 솔루션이 제공됩니다. 참고연결 문자열에서 Persist Security Info라는 명명된 값을 False로 설정하여 암호와 같은 보안상 중요한 세부 정보가 SqlConnection 또는 OleDbConnection 개체의 ConnectionString 속성으로 인해 반환되지 않도록 방지합니다. 다음 하위 섹션에서는 연결 문자열 저장을 위해 여러 옵션을 사용하는 방법에 대해 설명하고 각 접근 방식의 장점과 단점에 대해 설명합니다. 이렇게 하면 특정 응용 프로그램 시나리오에 따라 정확한 결정을 내릴 수 있습니다. 참고구성 응용 프로그램 관리 블록을 사용하면 데이터베이스 연결에서 복잡한 계층적 데이터까지 구성 설정을 관리할 수 있습니다. 자세한 내용은http://msdn.microsoft.com/practices (영문)를 참조하십시오. XML 응용 프로그램 구성 파일 사용<appSettings> 요소를 사용하여 응용 프로그램 구성 파일의 사용자 지정 설정 섹션에 데이터베이스 연결 문자열을 저장할 수 있습니다. 이 요소는 다음 조각에 표시된 것과 같이 임의의 키-값 쌍을 지원합니다. <configuration> <appSettings> <add key="DBConnStr" value="server=(local);Integrated Security=SSPI;database=northwind"/> </appSettings></configuration> 참고<appSettings> 요소는 <configuration> 요소 아래에 표시되며 <system.web> 아래에 직접 표시되지 않습니다. 장점
단점
추가 정보
UDL 파일 사용OLE DB .NET 데이터 공급자는 해당 연결 문자열에서 UDL(Universal Data Link) 파일 이름을 지원합니다. OleDbConnection 개체에 대해 생성 인수를 사용하여 연결 문자열을 전달하거나 개체의 ConnectionString 속성을 사용하여 연결 문자열을 설정할 수 있습니다. 참고SQL Server .NET Data Provider는 해당 연결 문자열에서 UDL 파일을 지원하지 않습니다. 따라서 이러한 접근 방식은 OLE DB .NET Data Provider를 사용하는 경우에만 사용할 수 있습니다. OLE DB 공급자에 대해 연결 문자열에서 UDL 파일을 참조하려면 "File Name=name.udl"을 사용합니다. 장점
단점
추가 정보
Windows 레지스트리 사용또한 Windows 레지스트리의 사용자 지정 키를 사용하여 연결 문자열을 저장할 수도 있지만 배포 문제로 인해 이 방법은 권장되지 않습니다. 장점
단점
사용자 지정 파일 사용사용자 지정 파일을 사용하여 연결 문자열을 저장할 수 있습니다. 하지만 이 기술은 장점이 없으며 권장되지 않습니다. 장점
단점
생성 인수 및 COM+ 카탈로그 사용데이터베이스 연결 문자열을 COM+ 카탈로그에 저장하고 개체 생성 문자열을 사용하여 개체에 자동으로 전달할 수 있습니다. COM+는 개체를 인스턴스화한 다음 개체의 Construct 메서드를 즉시 호출하여 구성된 생성 문자열을 제공합니다. 참고이 방법은 서비스되는 구성 요소에서만 작동됩니다. 관리 구성 요소에 분산 트랜잭션 지원이나 개체 풀링과 같은 기타 서비스가 사용되는 경우에만 고려하십시오. 장점
단점
중요연결 문자열의 보안은 반드시 필요합니다. SQL 인증의 경우 연결에는 사용자 이름과 암호가 포함됩니다. 공격자가 웹 서버에서 보안 코드의 취약성을 악용하거나 구성 정보에 대한 액세스를 얻는 경우 데이터베이스가 취약해집니다. 이를 방지하기 위해 연결 문자열을 암호화해야 합니다. 일반 텍스트의 연결 문자열을 암호화하기 위해 사용할 수 있는 여러 방법에 대한 설명을 보려면 http://www.microsoft.com/practices (영문)에서 제공되는 웹 응용 프로그램 보안 향상: 위협과 대처 방안을 참조하십시오. 추가 정보
연결 사용 패턴사용 중인 .NET 데이터 공급자와는 관계 없이 다음을 항상 수행해야 합니다.
메서드 반환 전에 연결이 닫혀 있도록 보장하려면 아래의 두 코드 예제에 설명된 접근 방식 중 하나를 고려하십시오. 첫 번째 예제에서는 finally 블록을 사용합니다. 두 번째 코드에서는 C# using 문을 사용하여 개체의 Dispose 메서드가 호출되도록 보장합니다. 다음 코드는 finally 블록이 연결을 닫도록 보장합니다. Visual Basic .NET에서는 구조화된 예외 처리를 지원하기 때문에 Visual Basic .NET 및 C# 모두에서 이 방식이 작동됩니다. public void DoSomeWork(){ SqlConnection conn = new SqlConnection(connectionString); SqlCommand cmd = new SqlCommand("CommandProc", conn ); cmd.CommandType = CommandType.StoredProcedure; try { conn.Open(); cmd.ExecuteNonQuery(); } catch (Exception e) { // Handle and log error } finally { conn.Close(); }} 다음 코드는 C# using 문을 사용하는 다른 방식을 보여 줍니다. Visual Basic .NET에서는 using 문이나 기타 비슷한 기능을 제공하지 않습니다. public void DoSomeWork(){ // using guarantees that Dispose is called on conn, which will // close the connection. using (SqlConnection conn = new SqlConnection(connectionString)) { SqlCommand cmd = new SqlCommand("CommandProc", conn); fcmd.CommandType = CommandType.StoredProcedure; conn.Open(); cmd.ExecuteQuery(); }} 현재 연결에서 다른 작업이 수행되기 전에 닫혀야 하는 SqlDataReader 또는 OleDbDataReader와 같은 다른 개체에 이 방식을 적용할 수도 있습니다. 오류 처리생성된 ADO.NET 오류는 .NET Framework에서 고유하게 제공되는 기본 구조화된 예외 처리 지원을 통해서 처리됩니다. 따라서 응용 프로그램의 다른 곳에서 오류를 처리하는 것과 동일한 방식으로 데이터 액세스 코드 내에서 오류를 처리합니다. 표준 .NET 예외 처리 구문 및 기술을 통해 예외를 발견하고 처리할 수 있습니다. 이 섹션에서는 강력한 데이터 액세스 코드를 개발하는 방법을 보여 주고 데이터 액세스 오류를 처리하는 방법에 대해 설명합니다. 또한 SQL Server .NET 데이터 공급자와 관련된 특정 예외 처리 지침을 제공합니다. .NET 예외.NET 데이터 공급자는 데이터베이스 관련 오류 조건을 데이터 액세스 코드에서 처리해야 하는 표준 예외 유형으로 변환합니다. 데이터베이스 관련 오류 세부 정보는 관련 예외 개체의 속성을 통해 제공됩니다. 모든 .NET 예외 유형은 궁극적으로 System 이름 공간의 기본 Exception 클래스로부터 파생됩니다. .NET 데이터 공급자에서 공급자 특성 예외 유형이 발생합니다. 예를 들어 SQL Server .NET 데이터 공급자에서 SQL Server가 오류 조건을 반환할 때마다 SqlException 개체가 발생하게 됩니다. 이와 비슷하게 OLE DB .NET 데이터 공급자에서는 기본 OLE DB 공급자에 의해 제공된 세부 사항이 포함되는 OleDbException 유형의 예외가 발생합니다. 그림 3은 .NET 데이터 공급자 예외 계층을 보여 줍니다. OleDbException 클래스는 모든 COM Interop 예외의 기본 클래스인 ExternalException으로부터 파생됩니다. 이 개체의 ErrorCode 속성에는 OLE DB에서 보장되는 COM HRESULT가 저장됩니다. 그림 1.3 .NET 데이터 공급자 예외 계층 .NET 예외 발견 및 처리데이터 액세스 예외 조건을 처리하려면 데이터 액세스 코드를 try 블록 내에 두고 적합한 필터와 함께 catch 블록을 사용하여 생성된 예외를 발견합니다. 예를 들어 SQL Server .NET 데이터 공급자를 사용하여 데이터 액세스 코드를 작성할 경우 다음 코드에 표시된 것과 같이 SqlException 유형의 예외를 발견합니다. try{ // Data access code}catch (SqlException sqlex) // more specific{}catch (Exception ex) // less specific{} 다른 필터 조건이 포함된 하나 이상의 catch 문을 제공하는 경우 가장 구체적인 유형부터 가장 추상적인 유형의 순서로 문을 정렬하십시오. 이렇게 하면 모든 특정 예외 유형에 대해 가장 구체적인 유형의 catch 블록이 실행됩니다. SqlException 클래스는 세부적인 예외 조건이 포함된 속성을 제공합니다. 여기에는 다음이 포함됩니다.
다음 코드 단편에서는 SQL Server .NET 데이터 공급자를 사용하여 SQL server 오류 조건을 처리하는 방법을 보여 줍니다. using System.Data;using System.Data.SqlClient;using System.Diagnostics;// Method exposed by a Data Access Layer (DAL) Componentpublic string GetProductName( int ProductID ){ SqlConnection conn = null; // Enclose all data access code within a try block try { conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=northwind"); conn.Open(); SqlCommand cmd = new SqlCommand("LookupProductName", conn ); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add("@ProductID", ProductID ); SqlParameter paramPN = cmd.Parameters.Add("@ProductName", SqlDbType.VarChar, 40 ); paramPN.Direction = ParameterDirection.Output; cmd.ExecuteNonQuery(); // The finally code is executed before the method returns return paramPN.Value.ToString(); } catch (SqlException sqlex) { // Handle data access exception condition // Log specific exception details LogException(sqlex); // Wrap the current exception in a more relevant // outer exception and re-throw the new exception throw new DALException( "Unknown ProductID: " + ProductID.ToString(), sqlex ); } catch (Exception ex) { // Handle generic exception condition . . . throw ex; } finally { if(conn != null) conn.Close(); // Ensures connection is closed }}// Helper routine that logs SqlException details to the // Application event logprivate void LogException( SqlException sqlex ){ EventLog el = new EventLog(); el.Source = "CustomAppLog"; string strMessage; strMessage = "Exception Number : " + sqlex.Number + "(" + sqlex.Message + ") has occurred"; el.WriteEntry( strMessage ); foreach (SqlError sqle in sqlex.Errors) { strMessage = "Message: " + sqle.Message + " Number: " + sqle.Number + " Procedure: " + sqle.Procedure + " Server: " + sqle.Server + " Source: " + sqle.Source + " State: " + sqle.State + " Severity: " + sqle.Class + " LineNumber: " + sqle.LineNumber; el.WriteEntry( strMessage ); }} SqlException catch 블록 내에서 코드는 LogException 지원 함수를 사용하여 예외 세부 정보를 처음부터 기록합니다. 이 함수는 foreach 문을 사용하여 Errors 컬렉션 내의 공급자 관련 세부 정보를 열거하고 오류 세부 정보를 오류 로그에 기록합니다. 그런 다음 catch 블록 내의 코드는 GetProductName 호출자에게 더욱 유용한 DALException 유형의 예외 내에서 SQL Server 관련 예외를 래핑합니다. 예외 처리기는 throw 키워드를 사용하여 이 예외를 호출자에게 전파합니다. 추가 정보
저장 프로시저로부터 오류 생성Transact-SQL(T-SQL)은 사용자 지정 오류를 발생시키고 이를 클라이언트로 반환하는 데 사용할 수 있는 RAISERROR(스펠링 주의) 함수를 제공합니다. ADO.NET 클라이언트의 경우 SQL Server .NET Data Provider는 이러한 데이터베이스 오류를 가로채어 SqlError 개체로 변환합니다. RAISERROR 함수를 사용하는 가장 간단한 방법은 메시지 텍스트를 첫 번째 매개 변수로 포함시킨 다음 아래 코드 단편에 표시된 것과 같이 심각도 및 상태 매개 변수를 지정하는 것입니다. RAISERROR( 'Unknown Product ID: %s', 16, 1, @ProductID ) 이 예에서 대체 매개 변수는 현재 제품 ID를 오류 메시지 텍스트의 일부로 반환하기 위해 사용됩니다. 매개 변수 two는 메시지 심각도를 나타내며 매개 변수 three는 메시지 상태입니다. 추가 정보
적합한 심각도 수준 사용오류 심각도 수준을 신중하게 선택하고 각 수준의 영향을 확인하십시오. SQL Server 2000에서 발생하는 문제 유형을 식별하는 데에는 0부터 25 사이의 오류 심각도 수준 범위가 사용됩니다. 클라이언트 코드에서 사용자는 SqlException 클래스의 Errors 컬렉션 내에서 SqlError 개체의 Class 속성을 검사하여 오류의 심각도를 가져올 수 있습니다. 표 1은 여러 심각도 수준의 영향 및 의미를 나타냅니다. 표 1. 오류 심각도 수준 - 영향 및 의미
자동 트랜잭션 제어SQL Server .NET 데이터 공급자에서는 심각도 수준이 10 이상인 오류에 대해 SqlException이 발생합니다. 자동(COM+) 트랜잭션의 일부인 구성 요소에서 SqlException이 발견되면 구성 요소에서 트랜잭션을 취소해야 합니다. 이러한 과정은 자동으로 진행되거나 수동으로 진행될 수도 있으며 메서드가 AutoComplete 특성으로 표시되어 있는지 여부에 따라 달라집니다. 자동 트랜잭션 컨텍스트에서 SqlException을 처리하는 방법에 대한 자세한 내용은 이 문서의 트랜잭션 결과 확인 섹션을 참조하십시오. 정보 메시지 검색10 이하의 심각도 수준은 정보 메시지를 나타내는 데 사용되며 SqlException을 발생시키지 않습니다. 정보 메시지를 검색하려면
메시지 데이터는 이벤트 처리기에 전달된 SqlInfoMessageEventArgs 개체를 통해 제공됩니다. 이 개체는 정보 메시지당 하나의 SqlError 개체 집합을 포함하는 Errors 속성을 제공합니다. 다음 코드 단편에서는 정보 메시지를 로그하는 데 사용되는 이벤트 처리기를 등록하는 방법을 보여 줍니다. public string GetProductName( int ProductID ){ SqlConnection conn = null; try { conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=northwind"); // Register a message event handler conn.InfoMessage += new SqlInfoMessageEventHandler( MessageEventHandler ); conn.Open(); // Setup command object and execute it . . . } catch (SqlException sqlex) { // log and handle exception . . . } finally { if(conn != null) conn.Close(); }}// message event handlervoid MessageEventHandler( object sender, SqlInfoMessageEventArgs e ){ foreach( SqlError sqle in e.Errors ) { // Log SqlError properties . . . }} 성능이 섹션에서는 다양한 일반 데이터 액세스 시나리오를 소개하고 각 시나리오에 대해 ADO.NET 데이터 액세스 코드 측면에서 최고의 성능과 확장성을 제공하는 솔루션에 대한 세부 정보를 제공합니다. 필요에 따라 성능, 기능 및 개발 효율 등이 비교됩니다. 이 섹션에서는 다음과 같은 기능적 시나리오를 고려합니다.
여러 행 검색이 시나리오에서는 테이블 형식의 데이터 집합을 검색하고 검색된 행을 반복해서 작업을 수행할 수 있습니다. 예를 들어 일련의 데이터 집합을 검색하고 연결이 끊어진 상태로 이를 작업하고 웹 서비스 등을 통해 클라이언트 응용 프로그램에 XML 문서로 전달할 수 있습니다. 또는 데이터를 HTML 테이블 형식으로 표시할 수 있습니다. 가장 적합한 데이터 액세스 방식을 결정하기 위해서는 (연결이 끊어진) DataSet 개체에 대해 추가 유연성이 필요하거나 B2C(Business-to consumer) 웹 응용 프로그램에서 데이터를 표현하는 데 적합한 SqlDataReader 개체로 제공되는 기본 성능이 필요한지 여부를 고려하십시오. 그림 4는 두 가지 기본 시나리오를 보여 줍니다. 참고DataSet을 채우는 데 사용되는 SqlDataAdapter는 내부적으로 SqlDataReader를 사용하여 데이터에 액세스합니다. 그림 1.4. 여러 행 데이터 액세스 시나리오 옵션 비교데이터 원본으로부터 여러 행을 검색할 때는 다음 옵션을 사용할 수 있습니다.
SqlDataReader 및 DataSet/DataTable의 선택은 기본적으로 기능 및 성능 중 어떤 것이 중요한지 여부에 따라 달라집니다. SqlDataReader는 최적의 성능을 제공하며 DataSet은 추가 기능 및 유연성을 제공합니다. 데이터 바인딩이러한 세 가지 개체는 모두 데이터 바인딩 컨트롤의 데이터 원본 역할을 수행할 수 있지만 DataSet 및 DataTable은 SqlDataReader보다 더 다양한 컨트롤에 대한 데이터 원본으로 사용될 수 있습니다. 이러한 이유는 DataSet 및 DataTable이 IListSource(IList 생성)를 구현하는 데 비해 SqlDataReader는 IEnumerable을 구현하기 때문입니다. 데이터 바인딩이 가능한 여러 WinForm 컨트롤에는 IList를 구현하는 데이터 원본이 필요합니다. 이러한 차이는 각 개체 유형이 디자인된 시나리오 유형이 다르기 때문입니다. DataSet(DataTable 포함)은 웹 및 데스크톱(WinForm) 시나리오 모두에 적합한 연결이 끊긴 리치 구조입니다. 반면에 데이터 판독기는 최적화된 전진 전용 데이터 액세스가 필요한 웹 응용 프로그램에 최적화되어 있습니다. 바인딩하려는 특정 컨트롤 유형에 대한 데이터 원본 요구 사항을 확인하십시오. 응용 프로그램 계층 간 데이터 전달DataSet은 선택적으로 XML로 조작될 수 있는 데이터의 관계형 뷰를 제공하며 응용 프로그램 계층과 구성 요소 간에 연결이 끊긴 상태로 캐시된 데이터 복사본을 전달하도록 허용합니다. 하지만 SqlDataReader는 DataSet 생성과 관련된 성능 및 메모리 오버헤드를 방지하기 때문에 최적의 성능을 제공합니다. DataSet 개체를 만들면 DataTable, DataRow 및 DataColumn 개체를 포함한 여러 하위 개체와 이러한 하위 개체의 컨테이너로 사용되는 컬렉션 개체가 만들어진다는 점에 유의하십시오. DataSet 사용다음 경우에 SqlDataAdapter 개체로 채워지는 DataSet을 사용하십시오.
참고자세한 내용은 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/BOAGag.asp (영문)의 MSDN 웹 사이트에서 "데이터 계층 구성 요소 디자인 및 계층 간 데이터 전달"을 참조하십시오. 추가 정보DataSet 또는 DataTable을 생성하기 위해 SqlDataAdapter를 사용하는 경우 다음에 유의하십시오.
SqlDataReader 사용다음 경우에 SqlCommand 개체의 ExecuteReader 메서드를 호출하여 SqlDataReader를 사용합니다.
추가 정보SqlDataReader를 사용하는 경우 다음에 유의하십시오.
XmlReader 사용다음 경우에 SqlCommand 개체의 ExecuteXmlReader 메서드를 호출하여 XmlReader를 사용합니다.
추가 정보XmlReader를 사용하는 경우 다음에 유의하십시오.
단일 행 검색이 시나리오에서는 데이터 원본에서 지정된 열 집합이 포함된 단일 데이터 행을 검색할 수 있습니다. 예를 들어 고객 ID를 사용하여 관련된 고객 세부 정보를 조회하거나 제품 ID를 사용하여 제품 정보를 검색할 수 있습니다. 옵션 비교데이터 원본에서 검색된 단일 행으로 데이터 바인딩을 수행하려는 경우 앞에서 설명한 여러 행 검색 및 반복 시나리오에서 설명된 것과 동일한 방법으로 SqlDataAdapter를 사용하여 DataSet 또는 DataTable을 채울 수 있습니다. 하지만 특별히 DataSet/DataTable 기능이 필요하지 않으면 이러한 개체를 만들지 않아야 합니다. 단일 행을 검색하려는 경우 다음 옵션 중 하나를 사용하십시오. 두 옵션 모두 서버에 결과 집합을 만들고 클라이언트에 DataSet을 만드는 불필요한 오버헤드를 방지합니다. 각 방식의 성능 차이는 스트레스 수준과 데이터베이스 연결 풀링의 설정 여부에 따라 달라집니다. 데이터베이스 연결 풀링이 설정된 경우 성능 테스트에서는 스트레스 조건이 높은 상태에서(동시 연결 200 이상) 저장 프로시저 방식이 SqlDataReader 방식보다 거의 30퍼센트 정도 뛰어난 성능을 보여 줍니다. 저장 프로시저 출력 매개 변수 사용연결 풀링을 설정한 다중 계층 웹 응용 프로그램에서 단일 행을 검색하려는 경우 저장 프로시저 출력 매개 변수를 사용하십시오. 추가 정보저장 프로시저 출력 매개 변수를 사용하는 방법을 보여 주는 코드 예제를 보려면 부록에 있는 저장 프로시저 출력 매개 변수를 사용하여 단일 행을 검색하는 방법을 참조하십시오. SqlDataReader 사용다음 경우에 SqlDataReader를 사용합니다.
추가 정보SqlDataReader를 사용하는 경우 다음에 유의하십시오.
단일 항목 검색이 시나리오에서는 데이터의 단일 항목을 검색할 수 있습니다. 예를 들어 해당 ID에 따른 단일 제품 이름 또는 해당 고객 이름에 따른 단일 고객 신용 등급을 조회할 수 있습니다. 이러한 시나리오에서는 일반적으로 단일 항목을 검색할 때 DataSet 또는 심지어 DataTable의 오버헤드도 발생시키지 않아야 합니다. 또한 단순히 특정 행이 데이터베이스에 존재하는지 여부를 확인할 수도 있습니다. 예를 들어 새로운 사용자가 웹 사이트에 등록할 때 선택한 사용자 이름이 이미 존재하는지 여부를 확인해야 합니다. 이 경우는 단일 항목 조회의 특수한 경우이지만, 이 경우 간단한 부울 반환으로도 충분합니다. 옵션 비교데이터 원본에서 데이터의 단일 항목을 검색할 때는 다음 옵션을 고려하십시오.
ExecuteScalar 메서드는 단일 값만 반환하는 쿼리에 맞게 디자인되어 있으므로 데이터 항목을 직접 반환합니다. 이 메서드는 저장 프로시저 출력 매개 변수나 SqlDataReader 방식에 필요한 것보다 필요한 코드가 적습니다. 성능적인 관점에서 볼 때 테스트에서 저장 프로시저 방식이 높고 낮은 스트레스 조건(동시 브라우저 연결 100개 미만에서 200개 이상까지)에서 일관적인 성능을 제공하는 것으로 밝혀졌기 때문에 저장 프로시저 출력을 사용하거나 매개 변수를 반환해야 합니다. 추가 정보단일 항목을 검색할 때 다음을 고려 하십시오.
방화벽을 통해 연결방화벽을 통해 SQL Server에 연결하도록 인터넷 응용 프로그램을 구성할 수 있습니다. 예를 들어 여러 웹 응용 프로그램 및 해당 방화벽의 핵심 아키텍처 구성 요소는 내부 네트워크로부터 프런트 엔드 웹 서버를 격리시키는 데 사용되는 주변 네트워크(DMZ 또는 비무장지대)입니다. 방화벽을 통해 SQL Server에 연결하려면 방화벽, 클라이언트 및 서버의 특정 구성이 필요합니다. SQL Server는 구성을 지원하는 클라이언트 네트워크 유틸리티 및 서버 네트워크 유틸리티 프로그램을 제공합니다. 네트워크 라이브러리 선택방화벽을 통해 연결할 때 구성을 단순화하려면 SQL Server TCP/IP 네트워크 라이브러리를 사용하십시오. 이 라이브러리는 SQL Server 2000 설치 시 기본으로 설치됩니다. 이전 버전의 SQL Server를 사용하는 경우에는 각각 클라이언트 네트워크 유틸리티 및 서버 네트워크 유틸리티를 사용하여 클라이언트 및 서버 모두에서 기본 네트워크 라이브러리로 TCP/IP를 구성했는지 확인합니다. 구성 장점 외에도 TCP/IP 라이브러리 결과를 사용하면 다음과 같은 장점이 있습니다.
TCP/IP로 클라이언트 및 서버 컴퓨터를 구성해야 합니다. 대부분의 방화벽은 트래픽 흐름을 허용하는 일련의 포트를 제한하기 때문에 SQL Server에서 사용되는 포트 번호에 주의해야 합니다. 서버 구성SQL Server의 기본 인스턴스는 포트 1433으로 수신합니다. 또한 SQL 클라이언트가 네트워크의 다른 SQL Server를 찾을 수 있도록 허용하는 데에는 UDP 포트 1434가 사용됩니다. 하지만 SQL Server 2000의 명명된 인스턴스는 서버가 처음 시작될 때 포트 번호를 동적으로 할당합니다. 네트워크 관리자는 방화벽에서 포트 번호 범위를 열지 않기를 바라기 때문에 방화벽에서 SQL Server의 명명된 인스턴스를 사용할 때는 서버 네트워크 유틸리티를 사용하여 특정 포트 번호에서 수신하도록 인스턴스를 구성하십시오. 그런 다음 관리자는 서버 인스턴스가 수신하는 특정 IP 주소 및 포트 번호에 대한 트래픽을 허용하도록 방화벽을 구성할 수 있습니다. 참고클라이언트 네트워크 라이브러리가 사용하는 원본 포트는 1024-5000 범위 내에 동적으로 할당됩니다. 이러한 동작은 TCP/IP 클라이언트 응용 프로그램에 있어서 표준 방식이지만 이렇게 하려면 이 범위 내의 모든 포트에 대한 트래픽을 허용하도록 방화벽을 구성해야 합니다. SQL Server에서 사용되는 포트에 대한 자세한 내용은 Microsoft 기술 자료 문서 287932, "INF: 방화벽을 통한 SQL Server 연결에 필요한 TCP 포트"를 참조하십시오. 명명된 인스턴스의 동적 검색SQL Server가 수신하는 기본 포트 번호를 변경하려면 클라이언트가 이 포트에 연결하도록 구성하십시오. 자세한 내용은 이 문서의 클라이언트 구성 섹션을 참조하십시오. SQL Server 2000 기본 인스턴스에 대한 포트 번호를 변경할 경우 클라이언트를 수정하지 않으면 연결 오류가 발생합니다. 여러 SQL Server 인스턴스가 있는 경우 최신 버전의 MDAC 데이터 액세스 스택(2.6)은 동적 검색을 사용하고 UDP(사용자 데이터그램 프로토콜) 협상(UDP 포트 1434 사용)를 사용하여 명명된 인스턴스를 찾습니다. 개발 환경에서 이를 사용할 수도 있지만 방화벽은 일반적으로 UDP 협상 트래픽을 차단하기 때문에 실제 환경에서는 작동하지 않을 수 있습니다. 이러한 문제를 방지하려면 항상 구성된 대상 포트 번호로 연결하도록 클라이언트를 구성하십시오. 클라이언트 구성TCP/IP 네트워크 라이브러리를 사용하여 SQL Server에 연결하도록 클라이언트를 구성하고 클라이언트 라이브러리에 올바른 대상 포트 번호가 사용되는지 확인해야 합니다. TCP/IP 네트워크 라이브러리 사용SQL Server 클라이언트 네트워크 유틸리티를 사용하여 클라이언트를 구성할 수 있습니다. 일부 설치(예: 웹 서버)의 경우 클라이언트에 이 유틸리티가 설치되지 않았을 수도 있습니다. 이 경우에는 다음 중 하나를 수행합니다.
포트 지정SQL Server 인스턴스가 기본 1433 포트 이외의 다른 포트에서 수신하도록 구성된 경우 다음과 같은 방법으로 연결할 포트 번호를 지정할 수 있습니다.
분산 트랜잭션COM+ 분산 트랜잭션 및 Microsoft DTC(분산 트랜잭션 코디네이터) 서비스를 사용하는 서비스 구성 요소를 개발한 경우 개별 DTC 인스턴스 및 DTC 및 SQL Server와 같은 리소스 관리자 사이의 DTC 트래픽 흐름을 허용하도록 방화벽을 구성해야 할 수도 있습니다. DTC용 포트 열기에 대한 자세한 내용은 정보: 방화벽을 통해 작동하도록 Microsoft DTC(분산 트랜잭션 코디네이터) 구성을 참조하십시오. BLOB 처리현재 여러 응용 프로그램에서는 기존의 문자 및 숫자 데이터 외에도 그래픽 및 사운드와 같은 데이터 형식이나 비디오와 같은 보다 정교한 데이터 형식을 다루어야 합니다. 그래픽, 사운드 및 비디오 형식은 매우 다양한 종류가 있습니다. 하지만 저장소의 관점에서 볼 때 이러한 데이터는 모두 BLOB(Binary Large Object)라고도 부르는 대량의 이진 데이터로 간주될 수 있습니다. SQL Server는 BLOB를 저장할 수 있는 binary, varbinary 및 image 데이터 형식을 제공합니다. 이름과는 달리 BLOB 데이터는 텍스트 기반 데이터도 참조할 수 있습니다. 예를 들어 특정 행과 연결할 수 있는 임의 길이의 메모 필드를 저장할 수 있습니다. SQL Server는 이러한 목적을 위해 ntext 및 text 데이터 형식을 제공합니다. 일반적으로 8KB 미만의 이진 데이터의 경우에는 varbinary 데이터 형식을 사용하십시오. 이 크기를 초과하는 이진 데이터의 경우에는 image를 사용하십시오. 표 2에서는 각 데이터 형식의 기본 특징에 대해 설명합니다. 표 2. 데이터 형식 특징
참고Microsoft SQL Server 2000은 데이터 행에서 중간 크기의 text, ntext 및 image 값을 저장하는 기능을 지원합니다. 이 기능은 text, ntext 및 image 열의 데이터가 일반적으로 하나의 단위로 읽혀지거나 쓰여지는 테이블과 text, ntext 및 image 데이터를 사용하는 테이블을 참조하는 대부분의 문에 가장 적합합니다. 자세한 내용은 SQL Server 온라인 설명서의 "행 텍스트" 부분을 참조하십시오. BLOB 데이터 저장 위치SQL Server 7.0 이상에서는 데이터베이스에 저장된 BLOB 데이터 작업 성능이 향상되었습니다. 이러한 이유 중 하나는 데이터베이스 페이지 크기가 8KB로 증가했기 때문입니다. 그 결과 8KB 미만의 텍스트 또는 이미지 데이터를 별개의 이진 트리 페이지 구조로 저장할 필요 없이 단일 행에 저장할 수 있습니다. 즉, text, ntext 또는 image 데이터를 읽고 쓰는 작업을 문자 및 이진 문자열을 읽거나 쓰는 속도만큼 빠르게 수행할 수 있습니다. 8KB를 초과하는 경우에는 포인터가 행 내부에 유지되고 데이터 자체는 별개의 데이터 페이지의 이진 트리 구조에 저장되어 성능상의 단점을 피할 수 없습니다. text, ntext 및 image 데이터를 단일 행에 강제로 저장하는 방법은 SQL Server 온라인 설명서에서 "텍스트 및 이미지 데이터 사용" 항목을 참조하십시오. BLOB 처리를 위해 일반적으로 사용되는 다른 방법은 BLOB 데이터를 파일 시스템에 저장하고 포인터(URL(Uniform Resource Locater) 링크)를 데이터베이스 열에 저장하여 해당 파일을 참조합니다. SQL Server 7.0 이전 버전의 경우에는 파일 시스템에 있는 데이터베이스에 BLOB 데이터를 외부적으로 저장하여 성능을 향상시킬 수 있습니다. 하지만 BLOB 데이터 읽기 및 쓰기를 위한 ADO.NET 지원과 함께 SQL Server 2000에서 제공되는 향상된 BLOB 지원을 통해 BLOB 데이터를 데이터베이스에 매우 쉬운 방식으로 저장할 수 있습니다. 데이터베이스에 BLOB 데이터를 저장하는 방법의 장점데이터베이스에 BLOB 데이터를 저장하면 여러 장점을 제공합니다.
데이터베이스에 BLOB 데이터를 저장하는 방법의 단점데이터베이스와 파일 시스템에 특정 리소스를 저장할 경우의 장단점을 신중하게 고려하십시오. 이에 대한 예로는 HTTP HREF로 참조되는 이미지를 들 수 있습니다. 이러한 이유는 다음과 같습니다.
참고메타데이터 전략을 효과적으로 구성할 경우 이미지, 동영상 및 심지어 Microsoft Office 문서와 같은 리소스를 데이터베이스에 저장할 필요를 줄여 줄 수 있습니다. 메타데이터는 인덱싱될 수 있으며 파일 시스템에 저장된 리소스에 대한 포인터를 포함합니다. 데이터베이스에 BLOB 데이터 쓰기다음 코드는 ADO.NET을 사용하여 파일에서 가져온 이진 데이터를 SQL Server의 image 필드에 기록하는 방법을 보여 줍니다. public void StorePicture( string filename ){ // Read the file into a byte array using(FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) { byte[] imageData = new Byte[fs.Length]; fs.Read( imageData, 0, (int)fs.Length ); } using( SqlConnection conn = new SqlConnection(connectionString) ) { SqlCommand cmd = new SqlCommand("StorePicture", conn); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add("@filename", filename ); cmd.Parameters["@filename"].Direction = ParameterDirection.Input; cmd.Parameters.Add("@blobdata", SqlDbType.Image); cmd.Parameters["@blobdata"].Direction = ParameterDirection.Input; // Store the byte array within the image field cmd.Parameters["@blobdata"].Value = imageData; conn.Open(); cmd.ExecuteNonQuery(); }} 데이터베이스에 BLOB 데이터 읽기BLOB 데이터 포함된 행을 읽기 위해 ExecuteReader 메서드를 통해 SqlDataReader 개체를 만드는 경우 CommandBehavior.SequentialAccess 열거 값을 사용하십시오. 이 열거 값이 없으면 판독기가 서버에서 클라이언트로 한 번에 한 행씩 데이터를 끌어옵니다. 행에 BLOB 열이 포함된 경우에는 매우 큰 용량의 메모리가 필요합니다. 열거 값을 사용하면 GetBytes 메서드와 같이 읽혀진 바이트 수를 제어하는 데 사용할 수 있는 방법을 사용하여 참조될 경우에만 BLOB 데이터를 끌어오기 때문에 더욱 세부적으로 제어할 수 있습니다. 이에 대해서는 다음 코드 단편에서 설명합니다. // Assume previously established command and connection// The command SELECTs the IMAGE column from the tableconn.Open();using(SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)){ reader.Read(); // Get size of image data–pass null as the byte array parameter long bytesize = reader.GetBytes(0, 0, null, 0, 0); // Allocate byte array to hold image data byte[] imageData = new byte[bytesize]; long bytesread = 0; int curpos = 0; while (bytesread < bytesize) { // chunkSize is an arbitrary application defined value bytesread += reader.GetBytes(0, curpos, imageData, curpos, chunkSize); curpos += chunkSize; }}// byte array 'imageData' now contains BLOB from database 참고CommandBehavior.SequentialAccess를 사용하려면 엄격한 순차적 순서에 따라 열 데이터를 액세스해야 합니다. 예를 들어 BLOB 데이터가 열 3에 있는 경우 열 1과 열 2의 데이터도 필요하며 3을 읽기 전에 열 1과 2도 읽어야 합니다. DataSet에서 데이터베이스 업데이트 수행데이터베이스 업데이트 수행을 위한 아키텍처는 ADO.NET의 도입으로 크게 바뀌었습니다. ADO.NET은 대형 데이터베이스 크기와 대규모의 클라이언트 수로 확장되는 다중 계층 응용 프로그램을 더욱 쉽게 개발할 수 있도록 디자인되었습니다. 그 결과 특히 다음과 같은 일부 중요한 결과를 가져왔습니다.
업데이트 사용 패턴DataSet에서 데이터를 업데이트하기 위해 ADO.NET을 사용하는 과정은 다음과 같이 요약할 수 있습니다.
업데이트 수행을 위한 다른 한 가지 방법이 있습니다. ExecuteNonQuery 메서드를 사용하여 SQL 업데이트 쿼리를 직접 수행할 수 있습니다. 이 기술은 DataSet 개체를 사용하지 않고 특정 행을 프로그래밍 방식으로 업데이트하려는 경우에 적합합니다. DataAdapters 업데이트 초기화ADO.NET에서는 데이터베이스 업데이트 사항을 전송하기 위한 고유 코드를 DataAdapter 개체에 추가해야 합니다.. 이를 위한 세 가지 방법은 다음과 같습니다.
고유 업데이트 논리를 제공하는 것이 좋습니다. 시간 절약을 위해서는 데이터 어댑터 구성 마법사를 사용할 수 있지만, 그럴 경우에는 논리를 런타임에 생성하지 않도록 하십시오. 성능상의 문제가 있고 개체에서 생성하는 업데이트 논리를 제어할 수 없으므로 필요한 경우가 아니면 CommandBuilder 개체를 사용하지 마십시오. 또한 CommandBuilder는 저장 프로시저를 사용한 업데이트 전송을 지원하지 않습니다. CommandBuilder는 보고 또는 데이터 추출 도구와 같이 데이터 액세스 논리를 동적으로 생성하는 응용 프로그램에서 사용할 수 있습니다. CommandBuilder를 사용하면 이러한 도구에서 고유 코드 생성 모듈을 작성할 필요가 없습니다. 저장 프로시저 사용업데이트에 대해 저장 프로시저를 사용하면 데이터베이스 관리자가 동적 SQL에서 제공되는 보안보다 세부적인 수준의 보안을 구현할 수 있으며 보다 정밀한 데이터 무결성 검사가 가능합니다. 예를 들어 저장 프로시저는 항목을 감사 로그에 삽입할 수 있을 뿐만 아니라 요청된 업데이트를 수행할 수도 있습니다. 저장 프로시저는 또한 저장 프로시저로 데이터베이스 내에 수행된 오프라인 쿼리 최적화로 인해 최상의 성능을 제공할 수 있습니다. 마지막으로 데이터베이스 구조 및 저장 프로시저가 제공하는 응용 프로그램간의 통합으로 인해 더욱 쉽게 유지 관리할 수 있습니다. 저장 프로시저를 사용하는 ADO.NET 응용 프로그램은 여러 장점을 제공하고 데이터베이스를 직접 수정하는 방법보다 구현하기에 어렵지 않기 때문에 거의 모든 경우에 이 방법이 권장됩니다. 이에 대한 예외는 이를 지원하지 않는 Microsoft Access와 같이 여러 백엔드 또는 데이터베이스를 사용해야 하는 경우입니다. 이러한 경우에는 쿼리 기반 업데이트를 사용하십시오. 동시성 관리DataSet 개체는 사용자가 데이터를 원격으로 처리하고 데이터를 사용하는 경우와 같이 장기 실행 활동에 대한 최적의 동시성 사용을 권장하도록 디자인되었습니다. DataSet의 업데이트를 데이터베이스 서버로 전송하는 경우 최적의 동시성을 관리하기 위한 주요 방법은 다음 네 가지입니다.
마지막 세 가지 접근 방식은 데이터 무결성을 유지하며 처음 방식은 유지하지 않습니다. 기본 키 열만 포함이 옵션은 마지막 업데이트가 모든 이전 변경 사항을 무효화하는 경우를 만듭니다. CommandBuilder는 이 옵션을 지원하지 않지만 데이터 어댑터 구성 마법사는 이 옵션을 지원합니다. 이 옵션을 사용하려면 고급 옵션 탭으로 이동하고 동시성 사용 확인란의 선택을 취소합니다. 이 방식은 사용자가 모르는 사이에 다른 사용자의 변경 사항을 덮어쓸 수 있기 때문에 권장되지 않습니다. 다른 사용자의 업데이트에 대한 무결성은 절대로 손상시켜서는 안됩니다. 이 기술은 단일 사용자 데이터베이스에서만 적합합니다. WHERE 절에 모든 열 포함이 옵션은 사용자의 코드가 행을 패치하는 시간과 사용자의 코드가 행의 보류 중인 변경 사항을 전송하는 시간 사이에 다른 사용자가 변경한 사항을 덮어쓰지 않도록 방지합니다. 이 옵션은 데이터 어댑터 구성 마법사 및 SqlCommandBuilder에서 생성한 SQL 코드에 대한 기본 동작입니다. 이 방식은 다음과 같은 이유로 인해 권장되지 않습니다.
고유 키 열 및 타임스탬프 열 포함이 옵션을 사용하면 데이터베이스가 각 행의 업데이트 후에 타임스탬프 열을 고유 값으로 업데이트합니다. 테이블에 타임스탬프 열을 제공해야 합니다. 현재까지는 CommandBuilder 또는 데이터 어댑터 구성 마법사에서 이 옵션이 지원되지 않습니다. 고유 키 열 및 수정된 열 포함일반적으로 응용 프로그램 논리가 오래된 데이터 필드나 업데이트하지 않는 필드에 의존하는 경우 오류가 발생할 수 있기 때문에 이 옵션은 권장되지 않습니다. 예를 들어 사용자 A가 주문 수량을 변경하고 사용자 B가 단가를 변경하는 경우 주문 합계(가격 곱하기 수량)가 잘못 계산될 수 있습니다. Null 필드의 올바른 업데이트데이터베이스의 필드에 데이터 값이 포함되지 않은 경우 이러한 빈 필드에는 특수한 Null 값이 포함된 것으로 생각하는 것이 편리한 경우가 많습니다. 하지만 데이터베이스 표준에 Null 값에 대한 특수 처리가 필요한 경우에는 이러한 추상적 관점으로 인해 프로그래밍 오류가 발생할 가능성이 있습니다. Null 필드와 관련된 핵심 문제는 일반적인 SQL = operator의 경우 두 피연산자 중 하나 또는 모두가 Null 값인 경우 항상 false를 반환한다는 점입니다. 연산자 IS NULL은 SQL 쿼리에서 Null 필드의 존재를 확인하기 위한 유일한 올바른 방법입니다. 응용 프로그램에서 이에 설명된 기술을 사용하여 WHERE 절 지정을 통한 동시성을 관리하는 경우 필드가 Null일 가능성이 있는 모든 경우에 명시적인 IS NULL 식을 포함시켜야 합니다. 예를 들어 다음 쿼리는 OldLastName이 Null인 경우 항상 실패합니다. SET LastName = @NewLastName WHERE StudentID = @StudentID AND LastName = @OldLastName 이 쿼리는 다음과 같이 다시 작성되어야 합니다. SET LastName = @NewLastName WHERE (StudentID = @StudentID) AND ((LastName = @OldLastName) OR (OldLastName IS NULL AND LastName IS NULL)) 위에 표시된 업데이트 논리를 작성하는 방법을 올바르게 이해하기 위해서는 CommandBuilder 도구에서 생성된 출력을 읽어봐야 합니다. 추가 정보데이터베이스 업데이트에 대한 완벽한 취급 설명은 David Sceppa의 Microsoft ADO.NET(Microsoft Press, 2002), 11장 및 12장을 참조하십시오. 강력하게 형식있는 DataSet 개체 사용강력하게 형식있는 DataSet 개체는 데이터베이스 테이블 및 열을 개체 및 속성으로 제공합니다. 액세스는 컬렉션으로의 인덱싱이 아닌 이름별로 수행됩니다. 즉, 강력하게 형식있는 DataSet 개체와 형식없는 DataSet 개체 사이의 차이점은 필드를 액세스하는 방식으로 인식할 수 있습니다. string n1 = myDataSet.Tables["Students"].Rows[0]["StudentName"]; // untypedstring n2 = myDataSet.Students[0].StudentName; // strongly typed 강력하게 형식있는 DataSet 개체 사용에 따른 장점은 다음과 같습니다.
강력하게 형식있는 DataSet을 사용하는 경우강력하게 형식있는 DataSet은 응용 프로그램을 더욱 쉽게 개발하고 오류를 줄일 수 있도록 해주기 때문에 유용합니다. 포커스가 그래픽 사용자 인터페이스 상에 있는 다중 계층 응용 프로그램의 클라이언트측과 여러 필드 액세스 작업이 필요한 데이터 유효성 검사의 경우에 특히 유용합니다. 하지만 강력하게 형식있는 DataSet은 필드 및 테이블 이름이 수정되는 경우와 같이 데이터베이스 구조가 변경되는 경우에는 작업이 번거로워질 수 있습니다. 이 경우 형식있는 DataSet 클래스를 다시 생성하고 모든 종속 클래스를 수정해야 합니다. 강력하게 형식있는 방식과 형식없는 방식을 모두 같은 응용 프로그램에서 사용할 수 있습니다. 예를 들어 일부 개발자는 클라이언트 측에 강력하게 형식있는 DataSet을 사용하고 서버에서는 형식없는 레코드를 사용합니다. 강력하게 형식있는 DataSet의 .Merge 메서드를 사용하면 형식없는 DataSet으로부터 데이터를 가져올 수 있습니다. DataSet 클래스 생성.NET Framework SDK 및 Visual Studio.NET은 모두 필요한 DataSet 하위 클래스 생성에 도움이 되는 유틸리티를 제공합니다. .NET Framework SDK에는 명령줄 도구 사용 및 코드 작성 작업이 포함됩니다. Visual Studio .NET 메서드는 명백하게 Visual Studio .NET 개발 환경에 의존하며 명령 창을 열 필요가 없습니다. DataSet 클래스 생성 방법과는 관계없이 해당 형식있는 DataSet을 참조하는 모든 계층에 새로운 클래스를 배포해야 합니다. 이 경우는 일반적인 시나리오가 아니지만 원격을 사용하여 계층 간에 형식있는 DataSet을 전달할 경우에는 반드시 고려해야 합니다. .NET Framework 유틸리티 사용.NET Framework SDK에는 XML 스키마(.xsd) 파일을 기준으로 클래스 파일을 생성하는 데 도움이 되는 XML 스키마 정의 도구라는 명령줄 유틸리티가 포함됩니다. DataSet 개체의 WriteXmlSchema 메서드와 함께 이 유틸리티를 사용하여 형식없는 DataSet을 강력하게 형식있는 DataSet으로 변환합니다. 다음 명령은 XML 스키마 파일로부터 클래스 파일을 생성하는 방법을 보여 줍니다. 명령 창을 열고 다음을 입력합니다. C:\>xsd MyNewClass.xsd /d 명령에서 첫 번째 매개 변수는 XML 스키마 파일에 대한 경로입니다. 두 번째 매개 변수는 만들려는 클래스가 DataSet 클래스로부터 파생됨을 나타냅니다. 기본적으로 이 도구는 Visual C# .NET 클래스 파일을 생성하지만 적합한 옵션을 추가할 경우 Visual Basic .NET 클래스 파일을 생성할 수도 있습니다. 도구의 사용 가능한 옵션을 확인하려면 다음을 입력합니다. xsd /? 새로운 클래스 파일을 만든 다음에는 이를 프로젝트에 추가합니다. 이제 다음 Visual C# .NET 코드 조각에 표시된 것과 같이 강력하게 형식있는 DataSet 클래스의 인스턴스를 만들 수 있습니다. MyNewClass ds = new MyNewClass(); Visual Studio .NET 사용Visual Studio .NET에서 강력하게 형식있는 DataSet을 생성하려면 폼 디자이너 창에서 마우스 오른쪽 단추를 클릭한 다음 데이터 집합 생성을 클릭합니다. 그러면 클래스 파일과 함께 .xsd(XML 스키마 정의) 파일이 만들어진 다음 프로젝트에 추가됩니다. 이를 수행하기 전에 Windows 폼에 하나 이상의 DataAdapters가 추가되었는지 확인하십시오. 클래스 파일은 숨겨져 있습니다. 이 파일을 보려면 솔루션 탐색기 창의 도구 모음에 있는 모든 파일 표시 단추를 클릭합니다. 클래스 파일은 .xsd 파일과 연결되어 있습니다. 강력하게 형식있는 DataSet에 관계를 추가하려면 솔루션 탐색기 창에서 스키마 파일을 두 번 클릭하여 XML 스키마 디자이너를 연 다음 제약 조건을 추가하려는 테이블을 마우스 오른쪽 단추로 클릭합니다. 바로가기 메뉴에서 새 관계 추가를 클릭합니다. Visual Studio .NET에서 강력하게 형식있는 DataSet을 생성하는 다른 방법은 프로젝트 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭하고 파일 추가를 선택한 다음 데이터집합을 선택하는 것입니다. 그러면 새로운 .xsd 파일이 생성됩니다. 여기에서 서버 탐색기를 사용하여 데이터베이스에 연결하고 테이블을 xsd 파일로 끌어다 놓을 수 있습니다. Null 데이터 필드 작업다음은 .NET 데이터 아키텍처에서 Null 필드 값을 올바르게 사용하기 위한 일부 팁입니다.
트랜잭션데이터 원본을 업데이트하는 거의 모든 상업용 응용 프로그램에는 트랜잭션 지원이 필요합니다. 트랜잭션은 잘 알려진 ACID(원자성, 일관성, 격리성 및 영속성) 두문자어의 네 가지 기본 보장을 제공하여 하나 이상의 데이터 원본 내에 포함된 시스템 상태의 무결성을 보장하기 위해 사용됩니다. 예를 들어 구매 주문을 처리하는 웹 기반 판매 응용 프로그램을 고려해 보십시오. 각 주문에는 세 가지 데이터베이스 업데이트를 포함하는 세 개의 개별 작업이 필요합니다.
이러한 세 가지 고유 작업은 하나의 단위로 원자성 방식으로 수행되어야 합니다. 이 세 작업은 모두 성공해야 하며 아니면 모두 실패해야 합니다. 그렇지 않으면 데이터 무결성이 손상될 수 있습니다. 트랜잭션은 이러한 보장 및 기타 보장을 제공합니다. 트랜잭션 처리의 기본 개념에 대한 추가 배경 지식을 보려면 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconTransactionProcessingFundamentals.asp (영문)를 참조하십시오. 데이터 액세스 코드에 트랜잭션 관리를 포함시키기 위해 채택할 수 있는 방법은 매우 다양합니다. 각 방식은 두 가지 기본 프로그래밍 모델 중 하나에 속합니다.
두 기술 모두 로컬 트랜잭션(즉, SQL Server 2000과 같은 단일 리소스 관리자에 대해 수행되는 트랜잭션) 또는 분산 트랜잭션(즉, 원격 컴퓨터에 있는 여러 리소스 관리자에 대해 수행되는 트랜잭션)을 수행할 수 있지만 자동 트랜잭션 모델은 분산 트랜잭션 처리를 크게 단순화합니다. 보다 쉬운 프로그래밍 모델을 활용할 수 있도록 자동(COM+) 트랜잭션을 사용할 수도 있습니다. 이러한 장점은 특히 데이터베이스 업데이트를 수행하는 여러 구성 요소가 있는 경우에 두드러집니다. 하지만 대부분의 시나리오의 경우에는 이러한 트랜잭션 모델로 인해 비롯되는 추가 오버헤드 및 성능 감소를 방지해야 합니다. 이 섹션에서는 특정 응용 프로그램 시나리오에 따라 가장 적합한 모델을 선택하는 데 도움이 되는 지침을 제공합니다. 트랜잭션 모델 선택트랜잭션 모델을 선택하기 전에 트랜잭션이 필요한지 여부를 고려해야 합니다. 트랜잭션은 서버 응용 프로그램에서 소비되는 가장 비용이 높은 단일 리소스이며 불필요하게 사용할 경우 확장성을 감소시킵니다. 트랜잭션 사용과 관련된 다음 지침을 고려하십시오.
자동 트랜잭션과 수동 트랜잭션자동 트랜잭션을 사용하면 특히 여러 구성 요소가 데이터베이스 업데이트를 수행하는 경우 프로그래밍 모델이 다소 간소화됩니다. 수동 로컬 트랜잭션을 사용하면 Microsoft DTC와의 상호 작용이 필요하지 않기 때문에 항상 속도가 더 빠릅니다. 성능 감소가 줄어들긴 하지만 단일 로컬 리소스 관리자(예: SQL Server)에 대해 자동 트랜잭션을 사용하는 경우, 수동의 로컬 트랜잭션은 DTC와의 불필요한 상호 프로세스 통신(IPC)을 방지하기 때문에 자동 트랜잭션이 수동 트랜잭션보다 느립니다. 다음 경우에 수동 트랜잭션을 사용하십시오.
다음 경우에 자동 트랜잭션을 사용하십시오.
참고트랜잭션 모델은 혼합하지 마십시오. 하나만 선택해서 사용하십시오. 성능이 충분하다고 여겨지는 응용 프로그램 시나리오의 경우에는 프로그래밍 모델을 단순화하기 위해 단일 데이터베이스의 경우라도 자동 트랜잭션을 사용하는 것이 바람직합니다. 자동 트랜잭션을 사용하면 여러 구성 요소에서 동일 트랜잭션의 일부인 작업을 쉽게 수행할 수 있습니다. 수동 트랜잭션 사용수동 트랜잭션의 경우 구성 요소 코드 또는 저장 프로시저에서 각각 ADO.NET 또는 Transact-SQL의 트랜잭션 지원 기능을 직접 사용하는 코드를 작성합니다. 대부분의 경우 이 방식은 뛰어난 캡슐화 성능을 제공하고 성능적인 관점에서 ADO.NET 코드로 트랜잭션을 수행하는 것과 비슷하기 때문에 저장 프로시저에서 트랜잭션을 제어할 수 있어야 합니다. ADO.NET에서 수동 트랜잭션 수행ADO.NET은 새 트랜잭션을 시작하고 커밋 또는 롤백 여부를 명시적으로 제어할 수 있는 트랜잭션을 지원합니다. 트랜잭션 개체는 단일 데이터베이스 연결과 연결되어 있으며 연결 개체의 BeginTransaction 메서드로 얻어집니다. 이 메서드를 호출해도 이후의 명령이 이 트랜잭션의 컨텍스트로 실행되지는 않습니다. 그렇게 하려면 명령의 Transaction 속성을 설정하여 각 명령을 트랜잭션과 명시적으로 연결해야 합니다. 트랜잭션 개체와 여러 명령 개체를 연결하면 단일 트랜잭션의 단일 데이터베이스에 대해 여러 작업을 그룹화합니다. ADO.NET 트랜잭션 코드 사용 예를 보려면 부록에서 ADO.NET 수동 트랜잭션 코딩 방법을 참조하십시오. 추가 정보
저장 프로시저로 수동 트랜잭션 수행또한 저장 프로시저에서 Transact-SQL 문을 사용하여 수동 트랜잭션을 직접 제어할 수 있습니다. 예를 들어 BEGIN TRANSACTION, END TRANSACTION 및 ROLLBACK TRANSACTION과 같은 Transact-SQL 트랜잭션 문을 사용하는 단일 저장 프로시저를 사용하여 트랜잭션 작업을 수행할 수 있습니다. 추가 정보
자동 트랜잭션 사용자동 트랜잭션은 새로운 트랜잭션을 명시적으로 시작하거나 트랜잭션을 명시적으로 커밋 또는 취소할 필요가 없기 때문에 프로그래밍 모델을 단순화합니다. 하지만 자동 트랜잭션의 가장 중요한 장점은 DTC와 함께 작동할 수 있다는 점입니다. 따라서 단일 트랜잭션을 여러 분산 데이터 원본으로 확장할 수 있습니다. 대규모 분산 응용 프로그램의 경우 이러한 장점은 매우 유용할 수 있습니다. DTC를 직접 프로그래밍하여 분산 트랜잭션을 수동으로 제어할 수 있는 경우라도 자동 트랜잭션은 작업을 크게 간소화할 수 있으며 구성 요소 기반 시스템에 맞게 디자인되었습니다. 예를 들어 단일 트랜잭션으로 구성된 작업을 수행하도록 여러 구성 요소를 선언적으로 쉽게 구성할 수 있습니다. 자동 트랜잭션은 COM+에서 제공되는 분산 트랜잭션 지원 기능에 의존하며, 그 결과 서비스 구성 요소(즉, ServicedComponent 클래스에서 파생된 구성 요소)에서 자동 트랜잭션을 사용할 수 있습니다. 자동 트랜잭션에 대한 클래스를 구성하려면
추가 정보
트랜잭션 격리 수준 구성COM+ 버전1.0(Windows 2000에서 실행 중인 COM+)에 대한 트랜잭션 격리 수준은 직렬화됩니다. 이 수준은 최고의 격리 수준을 제공하지만 성능 감소를 수반합니다. 관련된 리소스 관리자(일반적으로 데이터베이스)가 트랜잭션 기간 동안 읽기 및 쓰기 잠금을 모두 유지해야 하기 때문에 시스템의 전반적인 처리량이 감소됩니다. 이 기간 동안 다른 모든 트랜잭션이 차단되어 응용 프로그램의 확장 성능에 심각한 영향을 줄 수 있습니다. Microsoft Windows .NET과 함께 제공되는 COM+ 버전 1.5를 사용하면 트랜잭션 격리 수준을 구성 요소별 기반에 따라 COM+ 카탈로그에 구성할 수 있습니다. 트랜잭션의 루트 구성 요소와 연결된 설정은 트랜잭션의 격리 수준을 결정합니다. 또한 동일 트랜잭션 스트림의 일부인 내부 하위 구성 요소의 트랜잭션 수준은 루트 구성 요소에서 정의된 것보다 높지 않아야 합니다. 격리 수준이 높으면 하위 구성 요소가 인스턴스화될 때 오류가 발생합니다. .NET 관리 클래스의 경우 Transaction 특성은 공용 Isolation 속성을 지원합니다. 이 속성을 사용하여 다음 코드에 표시된 것과 같이 특정 격리 수준을 선언적으로 지정할 수 있습니다. [Transaction(TransactionOption.Supported, Isolation=TransactionIsolationLevel.ReadCommitted)]public class Account : ServicedComponent{ . . .} 추가 정보
트랜잭션 결과 확인자동 트랜잭션의 결과는 단일 트랜잭션 스트림의 모든 트랜잭션 구성 요소 컨텍스트에서 일관성 플래그와 함께 트랜잭션 취소 플래그의 상태로 제어됩니다. 트랜잭션 결과는 트랜잭션 스트림의 루트 구성 요소가 비활성화되고 컨트롤이 호출자에게 반환되는 시점에 확인됩니다. 이러한 내용은 기존의 은행 예금 전송 트랜잭션을 보여 주는 그림 5에 설명되어 있습니다. 그림 1.5. 트랜잭션 스트림 및 컨텍스트 트랜잭션의 결과는 루트 개체(이 예에서는 Transfer 개체)가 비활성화되고 클라이언트 메서드 호출이 반환될 때 확인됩니다. 임의의 컨텍스트 내의 일관성 플래그가 False로 설정되었거나 트랜잭션 취소 플래그가 True로 설정된 경우 기본 물리적 DTC 트랜잭션이 취소됩니다. 다음 두 방법 중 하나로 .NET 개체로부터 트랜잭션 결과를 제어할 수 있습니다.
심각도가 11 이상인 SQL Server 오류가 발생하면 관리 데이터 공급자가 SqlException 유형의 예외가 발생할 수 있습니다. 메서드가 예외를 알리고 처리하는 경우 트랜잭션을 취소하도록 수동으로 선택하거나 [AutoComplete]로 플래깅된 메서드의 경우 예외가 호출자에게 전파되도록 하십시오. [AutoComplete] 메서드AutoComplete 특성으로 표시된 메서드의 경우 다음 중 하나를 수행하십시오.
예외를 전파하지 못하면 데이터베이스 오류가 있더라도 개체가 트랜잭션을 취소하도록 선택하지 못할 수 있습니다. 즉, 동일 트랜잭션 스트림을 공유하는 다른 개체에서 수행한 성공적인 작업이 커밋될 수 있습니다. 다음 코드에서는 SqlException을 알리고 이를 호출자에게 직접 전파합니다. 이 개체의 일관성 플래그는 비활성화시에 자동으로 False로 설정되기 때문에 트랜잭션이 결과적으로 취소됩니다. [AutoComplete]void SomeMethod(){ try { // Open the connection, and perform database operation . . . } catch (SqlException sqlex ) { LogException( sqlex ); // Log the exception details throw; // Rethrow the exception, causing the consistent // flag to be set to false. } finally { // Close the database connection . . . }} 비-[AutoComplete] 메서드AutoComplete 특성으로 표시되지 않은 메서드의 경우 다음을 수행해야 합니다.
다음 코드에서는 이러한 방식에 대해 설명합니다. void SomeOtherMethod(){ try { // Open the connection, and perform database operation . . . ContextUtil.SetComplete(); // Manually vote to commit the transaction } catch (SqlException sqlex) { LogException( sqlex ); // Log the exception details ContextUtil.SetAbort(); // Manually vote to abort the transaction // Exception is handled at this point and is not propagated to the caller } finally { // Close the database connection . . . }} 참고여러 catch 블록이 있는 경우 메서드 시작 시에 ContextUtil.SetAbort를 한 번 호출하고 try 블록의 끝에서 ContextUtil.SetComplete를 호출하는 것이 더 쉽습니다. 이러한 방식에서는 모든 catch 블록 내에서 ContextUtil.SetAbort를 반복해서 호출할 필요가 없습니다. 이러한 메서드로 결정된 일관성 플래그의 설정은 메서드가 반환하는 경우에만 필요합니다 이 방법은 호출 중인 코드에서 트랜잭션이 실패할 것이라는 사실을 인식하도록 만들기 때문에 예외(또는 래핑된 예외)를 호출 스택으로 항상 전파해야 합니다. 이렇게 하면 호출 중인 코드에서 최적화를 수행할 수 있습니다. 예를 들어 은행 예금 전송 시나리오의 경우 전송 구성 요소는 차변 작업이 이미 실패한 경우 대변 작업을 수행하지 않도록 결정할 수 있습니다. 일관성 플래그를 False로 설정한 다음 예외가 발생하지 않고 반환된 경우 호출 중인 코드는 트랜잭션이 실패할 것이라는 사실을 알 수 있는 방법이 없습니다. 부울 값을 반환하거나 부울 출력 매개 변수를 설정할 수 있는 경우에도 일관적으로 예외를 발생시켜 오류 조건을 나타내야 합니다. 이렇게 하면 오류 처리에 대한 표준 방식으로 보다 명확하고 일관적인 코드를 만들 수 있습니다. 데이터 페이징데이터를 통한 페이징은 분산 응용 프로그램에서 일반적인 요구 사항입니다. 예를 들어 전체 목록을 한 번에 표시하는 것이 금지된 경우 책의 목록을 사용자에게 제공할 수 있습니다. 사용자는 데이터에서 데이터의 다음 또는 이전 페이지를 보거나 목록의 처음이나 마지막으로 이동하는 등의 익숙한 동작을 수행하길 바랄 것입니다. 이 섹션에서는 이 기능의 구현 옵션에 대해 설명하고 각 옵션이 확장성 및 성능에 주는 영향에 대해 설명합니다. 옵션 비교데이터 페이징 옵션은 다음과 같습니다.
데이터 페이징을 위한 최상의 옵션은 아래 나열된 요소에 따라 달라집니다.
성능 테스트에서는 저장 프로시저를 사용한 수동 방식이 여러 스트레스 수준 범위에서 최상의 성능을 제공하는 것으로 나타났습니다. 하지만 수동 방식은 해당 작업을 서버에서 수행하기 때문에 사이트 기능 중 대부분이 데이터 페이징 기능에 의존하는 경우 서버 스트레스 수준이 중요한 문제가 될 수 있습니다. 이 방식이 사용자의 특정 환경에 적합한지 보장하기 위해서는 특정 요구 사항에 대해 모든 옵션을 테스트해야 합니다. 이에 대한 여러 옵션은 아래에 설명됩니다. SqlDataAdapter의 Fill 메서드 사용앞에서 설명한 것과 같이 SqlDataAdapter는 데이터베이스의 데이터로 DataSet을 채우는 데 사용됩니다. 오버로드된 Fill 메서드(다음 코드 참조) 중 하나는 두 개의 정수 인덱스 값을 사용합니다. public int Fill( DataSet dataSet, int startRecord, int maxRecords, string srcTable); startRecord 값은 시작 레코드의 0 기반 인덱스를 나타냅니다. maxRecords 값은 startRecord부터 시작하여 새로운 DataSet에 복사할 레코드 수를 나타냅니다. 내부적으로 SqlDataAdapter는 SqlDataReader를 사용하여 쿼리를 실행하고 결과를 반환합니다. SqlDataAdapter는 결과를 읽고 SqlDataReader로부터 데이터 읽기에 따라 DataSet을 만듭니다. SqlDataAdapter는 startRecord 및 maxRecords를 통해 모든 결과를 새로 생성된 DataSet에 복사하고 필요하지 않은 결과는 삭제합니다. 즉, 불필요한 여러 데이터를 네트워크에서 데이터 액세스 클라이언트로 가져올 가능성이 있으며, 이러한 점이 바로 이 방식의 주요 단점이 됩니다. 예를 들어 1,000개의 레코드가 있고 900에서 950까지의 레코드만 필요한 경우에도 앞 부분의 899개의 레코드를 네트워크에서 가져와서 클라이언트 측에서 삭제합니다. 이러한 오버헤드는 결과 집합이 적을 경우 크게 문제가 되지 않을 수 있지만 대규모 데이터 집합에서 페이징을 수행할 경우 심각해질 수 있습니다. ADO 사용페이징 구현을 위한 또 다른 옵션은 COM 기반 ADO를 사용하여 페이징을 수행하는 것입니다. 이러한 옵션의 주요 목적은 ADO Recordset 개체를 통해 제공되는 서버 측 커서에 대한 액세스를 얻기 위한 것입니다. Recordset 커서 위치를 adUseServer로 설정할 수 있습니다. OLE DB 공급자가 이를 지원하는 경우(예: SQLOLEDB), 서버측 커서를 사용할 수 있게 됩니다. 그런 다음 이 커서를 사용하여 네트워크에서 모든 레코드를 데이터 액세스 클라이언트 코드로 끌어오지 않아도 시작 레코드로 직접 이동할 수 있습니다. 이 방식은 다음과 같은 두 가지 주요 단점이 있습니다.
수동 구현 사용이 섹션에서 다뤄지는 데이터 페이징에 대한 마지막 옵션은 저장 프로시저를 사용하여 응용 프로그램에 페이징 기능을 수동으로 구현하는 것입니다. 고유 키가 포함된 테이블의 경우 저장 프로시저를 비교적 쉽게 구현할 수 있습니다. 고유 키가 없는(고유키가 많지 않아야 하는) 테이블의 경우 이 프로세스는 복잡해질 수 있습니다. 고유 키가 있는 테이블에 대한 페이징테이블에 고유 키가 있는 경우 WHERE 절에서 키를 사용하여 특정 행으로부터 시작하는 결과 집합을 만들 수 있습니다. SET ROWCOUNT 문 또는 결과 집합의 크기를 제한하는 데 사용되는 SQL Server TOP 문과 함께 사용되는 이 방식은 효율적인 페이징 메커니즘을 제공합니다. 이 방식은 다음 저장 프로시저 코드에 설명되어 있습니다. CREATE PROCEDURE GetProductsPaged@lastProductID int,@pageSize intASSET ROWCOUNT @pageSizeSELECT *FROM ProductsWHERE [standard search criteria]AND ProductID > @lastProductIDORDER BY [Criteria that leaves ProductID monotonically increasing]GO 이 저장 프로시저의 호출자는 단순히 lastProductID 값을 유지하고 연속적인 호출 사이에 선택된 페이지 크기에 따라 이를 늘리거나 줄입니다. 고유 키가 없는 테이블에 대한 페이징페이징하려는 테이블에 고유 키가 없는 경우 ID 열 사용 등의 방법으로 고유 키를 추가하는 방법을 고려하십시오. 이렇게 하면 앞에서 설명한 페이징 솔루션을 구현할 수 있습니다. 고유 키가 없는 테이블의 경우에도 결과 집합의 일부인 두 개 이상의 필드를 조합하여 고유성을 생성할 수만 있다면 효과적인 페이징 솔루션을 구현할 수 있습니다. 예를 들어 다음 테이블을 고려하십시오.
이 테이블에서는 Col1, Col2 및 Col3을 조합하여 고유성을 생성할 수 있습니다. 따라서 다음 저장 프로시저에 설명된 방식을 사용하여 페이징 메커니즘을 구현할 수 있습니다. CREATE PROCEDURE RetrieveDataPaged@lastKey char(40),@pageSize intASSET ROWCOUNT @pageSizeSELECTCol1, Col2, Col3, Col4, Col1+Col2+Col3 As KeyFieldFROM SampleTableWHERE [Standard search criteria]AND Col1+Col2+Col3 > @lastKeyORDER BY Col1 ASC, Col2 ASC, Col3 ASCGO 클라이언트는 저장 프로시저로 반환된 KeyField 열의 마지막 값을 유지하고 이를 다시 저장 프로시저에 삽입하여 테이블의 페이징을 제어합니다. 수동 구현이 데이터베이스 서버의 오버헤드를 증가시키지만 네트워크에서 불필요한 데이터 전송은 방지합니다. 성능 테스트에서는 여러 스트레스 수준 범위에서는 이 방식이 적합한 것으로 나타났습니다. 하지만 사이트 작업 중 데이터 페이징 기능이 차지하는 비중에 따라 서버의 수동 페이징 수행이 응용 프로그램의 확장성에 미치는 영향이 달라질 수 있습니다. 특정 응용 프로그램 시나리오에 맞는 최적의 방식을 찾기 위해서는 자체 환경에서 성능 테스트를 실행해야 합니다. 부록.NET 클래스에 개체 생성을 설정하는 방법엔터프라이즈(COM+) 서비스를 사용하여 개체 생성에 .NET 관리 클래스를 설정할 수 있습니다. .NET 관리되는 클래스를 설정하려면
다음 코드 단편은 COM+ 생성 문자열을 사용하여 데이터베이스 연결 문자열을 가져오는 DataAccessComponent라는 서비스 구성 요소를 보여 줍니다. using System;using System.EnterpriseServices;// the ApplicationName attribute specifies the name of the// COM+ Application which will hold assembly components[assembly : ApplicationName("DataServices")]// the ApplicationActivation.ActivationOption attribute specifies // where assembly components are loaded on activation// Library : components run in the creator's process// Server : components run in a system process, dllhost.exe[assembly: ApplicationActivation(ActivationOption.Library)]// Sign the assembly. The snk key file is created using the // sn.exe utility[assembly: AssemblyKeyFile("DataServices.snk")][ConstructionEnabled(Default="Default DSN")]public class DataAccessComponent : ServicedComponent{ private string connectionString; public DataAccessComponent() { // constructor is called on instance creation } public override void Construct( string constructString ) { // Construct method is called next after constructor. // The configured DSN is supplied as the single argument this.connectionString = constructString; }} SqlDataAdapter를 사용하여 여러 행을 검색하는 방법다음 코드에서는 SqlDataAdapter 개체를 사용하여 DataSet 또는 DataTable을 생성하는 명령을 실행하는 방법을 보여 줍니다. 이 코드는 SQL Server Northwind 데이터베이스에서 제품 범주 집합을 검색합니다. using System.Data;using System.Data.SqlClient;public DataTable RetrieveRowsWithDataTable(){ using ( SqlConnection conn = new SqlConnection(connectionString) ) { conn.Open(); SqlCommand cmd = new SqlCommand("DATRetrieveProducts", conn); cmd.CommandType = CommandType.StoredProcedure; SqlDataAdapter adapter = new SqlDataAdapter( cmd ); DataTable dataTable = new DataTable("Products"); adapter .Fill(dataTable); return dataTable; }} SqlAdapter를 사용하여 DataSet 또는 DataTable을 생성하려면
SqlDataReader를 사용하여 여러 행을 검색하는 방법SqlDataReader를 사용하여 여러 행을 검색하는 방법은 다음 코드 단편에 설명되어 있습니다. using System.IO;using System.Data;using System.Data.SqlClient;public SqlDataReader RetrieveRowsWithDataReader(){ SqlConnection conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=northwind"); SqlCommand cmd = new SqlCommand("DATRetrieveProducts", conn ); cmd.CommandType = CommandType.StoredProcedure; try { conn.Open(); // Generate the reader. CommandBehavior.CloseConnection causes the // the connection to be closed when the reader object is closed return( cmd.ExecuteReader( CommandBehavior.CloseConnection ) ); } catch { conn.Close(); throw; }}// Display the product list using the consoleprivate void DisplayProducts(){ SqlDataReader reader = RetrieveRowsWithDataReader(); try { while (reader.Read()) { Console.WriteLine("{0} {1} {2}", reader.GetInt32(0).ToString(), reader.GetString(1) ); } } finally { reader.Close(); // Also closes the connection due to the // CommandBehavior enum used when generating the reader }} SqlDataReader를 사용하여 행을 검색하려면
XmlReader를 사용하여 여러 행을 검색하는 방법SqlCommand 개체를 사용하여 XML 데이터에 대한 전진 전용의 스트림 기반 액세스를 제공하는 XmlReader 개체를 생성합니다. 이 명령(일반적으로 저장 프로시저)은 SQL Server 2000의 경우 일반적으로 유효한 FOR XML 절이 포함된 SELECT 문으로 구성되는 XML 기반 결과 집합을 생성해야 합니다. 다음 코드 단편에서는 이러한 방식에 대해 설명합니다. public void RetrieveAndDisplayRowsWithXmlReader(){ using( SqlConnection conn = new SqlConnection(connectionString) ) {; SqlCommand cmd = new SqlCommand("DATRetrieveProductsXML", conn ); cmd.CommandType = CommandType.StoredProcedure;try { conn.Open(); XmlTextReader xreader = (XmlTextReader)cmd.ExecuteXmlReader(); while ( xreader.Read() ) { if ( xreader.Name == "PRODUCTS" ) { string strOutput = xreader.GetAttribute("ProductID"); strOutput += " "; strOutput += xreader.GetAttribute("ProductName"); Console.WriteLine( strOutput ); } } xreader.Close(); // XmlTextReader does not support IDisposable so it can't be // used within a using keyword }} 앞의 코드에는 다음 저장 프로시저가 사용됩니다. CREATE PROCEDURE DATRetrieveProductsXMLASSELECT * FROM PRODUCTS FOR XML AUTOGO XmlReader를 사용하여 XML 데이터를 검색하려면
저장 프로시저 출력 매개 변수를 사용하여 단일 행을 검색하는 방법명명된 출력 매개 변수를 사용하여 단일 행 내의 검색된 데이터 항목을 반환하는 저장 프로시저를 호출할 수 있습니다. 다음 코드 단편에서는 저장 프로시저를 사용하여 Northwind 데이터베이스에 있는 Products 테이블에 포함된 특정 제품에 대한 제품 이름 및 단가를 검색합니다. void GetProductDetails( int ProductID, out string ProductName, out decimal UnitPrice ){ using( SqlConnection conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=Northwind") ) { // Set up the command object used to execute the stored proc SqlCommand cmd = new SqlCommand( "DATGetProductDetailsSPOutput", conn ) cmd.CommandType = CommandType.StoredProcedure; // Establish stored proc parameters. // @ProductID int INPUT // @ProductName nvarchar(40) OUTPUT // @UnitPrice money OUTPUT // Must explicitly set the direction of output parameters SqlParameter paramProdID = cmd.Parameters.Add( "@ProductID", ProductID ); paramProdID.Direction = ParameterDirection.Input; SqlParameter paramProdName = cmd.Parameters.Add( "@ProductName", SqlDbType.VarChar, 40 ); paramProdName.Direction = ParameterDirection.Output; SqlParameter paramUnitPrice = cmd.Parameters.Add( "@UnitPrice", SqlDbType.Money ); paramUnitPrice.Direction = ParameterDirection.Output; conn.Open(); // Use ExecuteNonQuery to run the command. // Although no rows are returned any mapped output parameters // (and potentially return values) are populated cmd.ExecuteNonQuery( ); // Return output parameters from stored proc ProductName = paramProdName.Value.ToString(); UnitPrice = (decimal)paramUnitPrice.Value; }} 저장 프로시저 출력 매개 변수를 사용하여 단일 행을 검색하려면
앞의 코드 단편에서는 다음 저장 프로시저를 호출합니다. CREATE PROCEDURE DATGetProductDetailsSPOutput@ProductID int,@ProductName nvarchar(40) OUTPUT,@UnitPrice money OUTPUTASSELECT @ProductName = ProductName, @UnitPrice = UnitPrice FROM Products WHERE ProductID = @ProductIDGO SqlDataReader를 사용하여 단일 행을 검색하는 방법SqlDataReader 개체를 사용하여 단일 행을 검색하고 특히 반환된 데이터 스트림에서 원하는 열을 검색할 수 있습니다. 이에 대해서는 다음 코드 단편에서 설명합니다. void GetProductDetailsUsingReader( int ProductID, out string ProductName, out decimal UnitPrice ){ using( SqlConnection conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=Northwind") ) { // Set up the command object used to execute the stored proc SqlCommand cmd = new SqlCommand( "DATGetProductDetailsReader", conn ); cmd.CommandType = CommandType.StoredProcedure; // Establish stored proc parameters. // @ProductID int INPUT SqlParameter paramProdID = cmd.Parameters.Add( "@ProductID", ProductID ); paramProdID.Direction = ParameterDirection.Input; conn.Open(); using( SqlDataReader reader = cmd.ExecuteReader() ) { if( reader.Read() ) // Advance to the one and only row { // Return output parameters from returned data stream ProductName = reader.GetString(0); UnitPrice = reader.GetDecimal(1); } } }} SqlDataReader 개체를 사용하여 단일 행을 반환하려면
앞의 코드 단편에서는 다음 저장 프로시저를 호출합니다. CREATE PROCEDURE DATGetProductDetailsReader@ProductID intASSELECT ProductName, UnitPrice FROM ProductsWHERE ProductID = @ProductIDGO ExecuteScalar를 사용하여 단일 항목을 검색하는 방법ExecuteScalar 메서드는 단일 값만 반환하는 쿼리를 위해 디자인되었습니다. 여러 열 및/또는 행을 반환하는 쿼리의 경우 ExecuteScalar는 첫 번째 행의 첫 번째 열만 반환합니다. 다음 코드에서는 특정 제품 ID에 대한 제품 이름을 조회하는 방법을 보여 줍니다. void GetProductNameExecuteScalar( int ProductID, out string ProductName ){ using( SqlConnection conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=northwind") ) { SqlCommand cmd = new SqlCommand("LookupProductNameScalar", conn ); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add("@ProductID", ProductID ); conn.Open(); ProductName = (string)cmd.ExecuteScalar(); }} ExecuteScalar를 사용하여 단일 항목을 검색하려면
앞의 코드에는 다음 저장 프로시저가 사용됩니다. CREATE PROCEDURE LookupProductNameScalar@ProductID intASSELECT TOP 1 ProductNameFROM ProductsWHERE ProductID = @ProductIDGO 저장 프로시저 출력 또는 반환 매개 변수를 사용하여 단일 항목을 검색하는 방법저장 프로시저 출력 또는 반환 매개 변수를 사용하여 단일 값을 조회할 수 있습니다. 다음 코드에는 출력 매개 변수를 사용하는 방법이 설명되어 있습니다. void GetProductNameUsingSPOutput( int ProductID, out string ProductName ){ using( SqlConnection conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=northwind") ) { SqlCommand cmd = new SqlCommand("LookupProductNameSPOutput", conn ); cmd.CommandType = CommandType.StoredProcedure; SqlParameter paramProdID = cmd.Parameters.Add("@ProductID", ProductID ); ParamProdID.Direction = ParameterDirection.Input; SqlParameter paramPN = cmd.Parameters.Add("@ProductName", SqlDbType.VarChar, 40 ); paramPN.Direction = ParameterDirection.Output; conn.Open(); cmd.ExecuteNonQuery(); ProductName = paramPN.Value.ToString(); }} 저장 프로시저 출력 매개 변수를 사용하여 단일 값을 검색하려면
앞의 코드에는 다음 저장 프로시저가 사용됩니다. CREATE PROCEDURE LookupProductNameSPOutput @ProductID int,@ProductName nvarchar(40) OUTPUTASSELECT @ProductName = ProductNameFROM ProductsWHERE ProductID = @ProductIDGO 다음 코드에서는 특정 행이 존재하는지 여부를 나타내기 위해 반환 값을 사용하는 방법을 보여 줍니다. 코딩 관점에서 볼 때 이 방법은 SqlParameter 방향을 ParameterDirection.ReturnValue로 명시적으로 설정해야 한다는 점을 제외하면 저장 프로시저 출력 매개 변수를 사용하는 방법과 비슷합니다. bool CheckProduct( int ProductID ){ using( SqlConnection conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=northwind") ) { SqlCommand cmd = new SqlCommand("CheckProductSP", conn ); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add("@ProductID", ProductID ); SqlParameter paramRet = cmd.Parameters.Add("@ProductExists", SqlDbType.Int ); paramRet.Direction = ParameterDirection.ReturnValue; conn.Open(); cmd.ExecuteNonQuery(); } return (int)paramRet.Value == 1;} 저장 프로시저 반환 값을 사용하여 특정 행이 존재하는지 여부를 확인하려면
앞의 코드에는 다음 저장 프로시저가 사용됩니다. CREATE PROCEDURE CheckProductSP @ProductID intASIF EXISTS( SELECT ProductID FROM Products WHERE ProductID = @ProductID ) return 1ELSE return 0GO SqlDataReader를 사용하여 단일 항목을 검색하는 방법SqlDataReader 개체를 사용하면 명령 개체의 ExecuteReader 메서드를 호출하여 단일 출력 값을 가져올 수 있습니다. 이를 위해서는 SqlDataReader Read 메서드를 호출해야 하고 원하는 값이 판독기의 접근자 메서드 중 하나를 통해 검색되기 때문에 조금 더 많은 코드가 필요합니다. 다음 코드에는 SqlDataReader 개체를 사용하는 방법이 설명되어 있습니다. bool CheckProductWithReader( int ProductID ){ using( SqlConnection conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=northwind") ) { SqlCommand cmd = new SqlCommand("CheckProductExistsWithCount", conn ); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add("@ProductID", ProductID ); cmd.Parameters["@ProductID"].Direction = ParameterDirection.Input; conn.Open(); using( SqlDataReader reader = cmd.ExecuteReader( CommandBehavior.SingleResult ) ) { if( reader.Read() ) { return (reader.GetInt32(0) > 0); } return false; }} 앞의 코드에는 다음 저장 프로시저가 사용되는 것으로 가정됩니다. CREATE PROCEDURE CheckProductExistsWithCount @ProductID intASSELECT COUNT(*) FROM ProductsWHERE ProductID = @ProductIDGO ADO.NET 수동 트랜잭션을 코딩하는 방법다음 코드에서는 트랜잭션으로 예금 전송 작업을 보호하기 위해 SQL Server .NET Data Provider에서 제공되는 트랜잭션 지원의 장점을 활용하는 방법을 보여 줍니다. 이 작업은 동일 데이터베이스에 있는 두 계정 간에 예금을 전송합니다. public void TransferMoney( string toAccount, string fromAccount, decimal amount ){ using ( SqlConnection conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=SimpleBank" ) ) { SqlCommand cmdCredit = new SqlCommand("Credit", conn ); cmdCredit.CommandType = CommandType.StoredProcedure; cmdCredit.Parameters.Add( new SqlParameter("@AccountNo", toAccount) ); cmdCredit.Parameters.Add( new SqlParameter("@Amount", amount )); SqlCommand cmdDebit = new SqlCommand("Debit", conn ); cmdDebit.CommandType = CommandType.StoredProcedure; cmdDebit.Parameters.Add( new SqlParameter("@AccountNo", fromAccount) ); cmdDebit.Parameters.Add( new SqlParameter("@Amount", amount )); conn.Open(); // Start a new transaction using ( SqlTransaction trans = conn.BeginTransaction() ) { // Associate the two command objects with the same transaction cmdCredit.Transaction = trans; cmdDebit.Transaction = trans; try { cmdCredit.ExecuteNonQuery(); cmdDebit.ExecuteNonQuery(); // Both commands (credit and debit) were successful trans.Commit(); } catch( Exception ex ) { // transaction failed trans.Rollback(); // log exception details . . . throw ex; } } }} Transact-SQL을 사용하여 트랜잭션을 수행하는 방법다음 저장 프로시저에서는 Transact-SQL 저장 프로시저 내에서 트랜잭션 예금 전송 작업을 수행하는 방법을 보여 줍니다. CREATE PROCEDURE MoneyTransfer@FromAccount char(20),@ToAccount char(20),@Amount moneyASBEGIN TRANSACTION-- PERFORM DEBIT OPERATIONUPDATE AccountsSET Balance = Balance - @AmountWHERE AccountNumber = @FromAccountIF @@RowCount = 0BEGIN RAISERROR('Invalid From Account Number', 11, 1) GOTO ABORTENDDECLARE @Balance moneySELECT @Balance = Balance FROM ACCOUNTSWHERE AccountNumber = @FromAccountIF @BALANCE < 0BEGIN RAISERROR('Insufficient funds', 11, 1) GOTO ABORTEND-- PERFORM CREDIT OPERATIONUPDATE Accounts SET Balance = Balance + @Amount WHERE AccountNumber = @ToAccountIF @@RowCount = 0BEGIN RAISERROR('Invalid To Account Number', 11, 1) GOTO ABORTENDCOMMIT TRANSACTIONRETURN 0ABORT: ROLLBACK TRANSACTIONGO 이 저장 프로시저는 BEGIN TRANSACTION, COMMIT TRANSACTION 및 ROLLBACK TRANSACTION 문을 사용하여 트랜잭션을 수동으로 제어합니다. 트랜잭션 .NET 클래스를 코딩하는 방법다음 코드 예에서는 자동 트랜잭션으로 구성된 세 가지 서비스 .NET 관리 클래스를 보여 줍니다. 각 클래스는 Transaction 특성으로 주석이 달려 있으며 이 특성의 값은 새 트랜잭션 스트림이 시작되어야 하는지 여부 또는 개체가 해당 직속 호출자의 스트림을 공유해야 하는지 여부를 결정합니다. 이러한 구성 요소는 함께 작동하여 은행 예금 전송을 수행합니다. Transfer 클래스는 RequiresNew 트랜잭션 특성으로 구성되어 있으며 Debit 및 Credit 클래스는 Required로 구성되어 있습니다. 따라서 런타임 시 세 가지 개체 모두 동일한 트랜잭션을 공유합니다. using System;using System.EnterpriseServices;[Transaction(TransactionOption.RequiresNew)]public class Transfer : ServicedComponent{ [AutoComplete] public void Transfer( string toAccount, string fromAccount, decimal amount ) { try { // Perform the debit operation Debit debit = new Debit(); debit.DebitAccount( fromAccount, amount ); // Perform the credit operation Credit credit = new Credit(); credit.CreditAccount( toAccount, amount ); } catch( SqlException sqlex ) { // Handle and log exception details // Wrap and propagate the exception throw new TransferException( "Transfer Failure", sqlex ); } }}[Transaction(TransactionOption.Required)]public class Credit : ServicedComponent{ [AutoComplete] public void CreditAccount( string account, decimal amount ) { try { using( SqlConnection conn = new SqlConnection( "Server=(local); Integrated Security=SSPI"; database="SimpleBank") ) { SqlCommand cmd = new SqlCommand("Credit", conn ); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add( new SqlParameter("@AccountNo", account) ); cmd.Parameters.Add( new SqlParameter("@Amount", amount )); conn.Open(); cmd.ExecuteNonQuery(); } } }catch( SqlException sqlex ){ // Log exception details here throw; // Propagate exception }}[Transaction(TransactionOption.Required)]public class Debit : ServicedComponent{ public void DebitAccount( string account, decimal amount ) { try { using( SqlConnection conn = new SqlConnection( "Server=(local); Integrated Security=SSPI"; database="SimpleBank") ) { SqlCommand cmd = new SqlCommand("Debit", conn ); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add( new SqlParameter("@AccountNo", account) ); cmd.Parameters.Add( new SqlParameter("@Amount", amount )); conn.Open(); cmd.ExecuteNonQuery(); } } catch (SqlException sqlex) { // Log exception details here throw; // Propagate exception back to caller } }} 도와주신 분들많은 도움을 주신 아래 분들에게 감사드립니다. Bill Vaughn, Mike Pizzo, Pablo Castro, Doug Rothaus, Kevin White, Blaine Dokter, David Schleifer, Graeme Malcolm (Content Master), Bernard Chen (Sapient), Matt Drucker (Turner Broadcasting), Steve Kirk, David Sceppa, Scott Densmore, Diego González (Lagash Systems) .NET의 최선의 구현 방법에 대한 자세한 내용을 보려면 patterns & practices (영문) 웹 페이지를 방문해 주십시오. 이 항목과 관련된 온라인 협력 개발 환경에 참여하려면 GotDotNet 작업 공간: Microsoft Patterns & Practices Data Access for .NET Workspace (영문)에 참여하십시오. 이 작업 공간의 커뮤니티와 귀하의 데이터 액세스 차단 질문, 제안 및 특정 정보를 공유하시기 바랍니다. 질문, 정보 또는 제안할 사항이 있거나 이 문서에 대한 의견을 보내시려면 devfdbck@microsoft.com으로 전자 메일을 보내 주시기 바랍니다. |
'알고리즘' 카테고리의 다른 글
방정식, 미적분법 그리고 delegate (0) | 2008.10.01 |
---|---|
한글,영문 섞여 있을때 문자의 수 or Byte수 구하기.. (C#) (0) | 2008.02.27 |
Advanced C# 9. 알고리즘 분석(Algorithm Analysis) (0) | 2008.01.08 |
Advanced C# 10. 분할의 아름다움 - 재귀와 비재귀 (0) | 2008.01.08 |
Advanced C# 6. DP - Abstract Factory (0) | 2008.01.08 |