Поделиться через


Программирование фундаментальных объектов AMO

Фундаментальные объекты, как правило, являются простыми и понятными. Чаще всего пользователь создает эти объекты и их экземпляры, а затем, когда они больше не нужны, отключается от них. К фундаментальным классам относятся следующие объекты: Server, Database, DataSource и DataSourceView. Единственным сложным объектом среди фундаментальных объектов AMO является DataSourceView, которому необходимы подробные данные для построения абстрактной модели, которая является представлением источника данных.

Обычно объектам Server и Database требуются для использования вложенные объекты OLAP или объекты интеллектуального анализа данных.

Этот раздел состоит из следующих подразделов.

  • Объекты Server

  • Объекты исключений AMOException

  • Объекты Database

  • Объекты DataSource

  • Объекты DataSourceView

Объекты Server

Работа с объектом Server включает следующие шаги: подключение к серверу, проверка подключения объекта Server к серверу и в случае положительного ответа отсоединение Server от сервера.

Соединение с объектом Server

Соединение с сервером заключается в написании правильной строки подключения.

Следующий образец кода возвращает объект Server, если соединение прошло успешно, либо значение null в случае ошибки. Ошибки во время процесса соединения обрабатываются операторами try и catch. Ошибки объектов AMO обрабатываются с помощью класса исключения AmoException. В данном примере ошибка выдается пользователю в окне сообщения.

        static Server ServerConnect( String strStringConnection)
        {
            string methodCaption = "ServerConnect method";
            Server svr = new Server();
            try
            {
                svr.Connect(strStringConnection);
            }
            #region ErrorHandling
            catch (AmoException e)
            {
                MessageBox.Show( "AMO exception " + e.ToString());
                svr = null;
            }
            catch (Exception e)
            {
                MessageBox.Show("General exception " + e.ToString());
                svr = null;
            }
            #endregion
            
            return svr;
        }

Структура строки соединения:

"Data source=<имя сервера>".

Дополнительные сведения о строке соединения см. в разделе ConnectionString.

Проверка соединения

Прежде чем приступать к работе с объектом Server, необходимо проверить соединение с сервером. В следующем образце кода показано, как это сделать. Образец предполагает, что svr — это объект Server, определенный в коде.

            if ( (svr != null) && ( svr.Connected))
            {
                // Do what it is needed if connection is good
            }

Отсоединение от сервера

После завершения работы необходимо отсоединиться от сервера с помощью метода Disconnect. В следующем образце кода показано, как это сделать. Образец предполагает, что svr — это объект Server, существующий в коде.

            if ( (svr != null) && ( svr.Connected))
            {
                svr.Disconnect()
            }

Объекты исключений AmoException

При возникновении различных проблем объекты AMO формируют исключения. Подробное обсуждение исключений см. в разделе Другие классы и методы объектов AMO. В следующем образце кода показан правильный способ захвата исключений в объектах AMO.

try
{
   //... some AMO code in here
}

catch (  OutOfSynchException e)
{
   // error handling code for OutOfSynchException 
}

catch (  OperationException e)
{
   // error handling code for OperationException 
} 

catch (  ResponseFormatException e)

{
   // error handling code for ResponseFormatException 
} 

catch (  ConnectionException e)

{
   // error handling code for ConnectionException 
}

catch (  AMOException e)

{
   //... here is the place where you end if it is an AMO exception, but none of the previous exceptions
   // if you start with AMOException in the first catch you will never see any one of the previous exceptions
}

Объекты Database

Работать с объектом Database очень просто. Возвращается существующая база данных из коллекции баз данных объекта Server.

Создание, удаление и поиск базы данных

Следующий образец кода демонстрирует создание базы данных по ее имени. Перед созданием базы данных выполните запрос к объекту DatabaseCollection сервера, чтобы проверить, не существует ли она уже. Если база данных существует, то она будет удалена и снова создана. В противном случае производится создание новой базы данных. Если необходимо удалить базу данных, то сначала ее необходимо получить из коллекции баз данных.

        static Database CreateDatabase(Server svr, String DatabaseName)
        {
            Database db = null;
            if ( (svr != null) && ( svr.Connected))
            {
                // Drop the database if it already exists
                db = svr.Databases.FindByName(DatabaseName);
                if (db != null)
                {
                    db.Drop();
                }

                // Create the database
                db = svr.Databases.Add(DatabaseName);
                db.Update();
            }
            
            return db;
        }

Метод FindByName используется для определения факта существования базы данных в коллекции. Если база данных уже существует, то этот метод возвращает соответствующий объект, если нет — возвращается значение NULL.

После добавления объекта Database в коллекцию баз данных сервер необходимо обновить методом Update. Если не сделать этого, объект Database не будет создан на сервере.

Обработка базы данных

Процедура обработки базы данных вместе со всеми дочерними объектами очень проста, поскольку объект Database имеет метод Process.

Этому методу можно передавать параметры, но все они не являются обязательными. Если не передано ни одного параметра, то все дочерние объекты будут обработаны с параметром ProcessDefault. Дополнительные сведения о возможностях обработки см. в разделе Microsoft.AnalysisServices..::..Database.

  1. Следующий образец кода обрабатывает базу данных в соответствии со значением по умолчанию.
        static Database ProcessDatabase(Database db, ProcessType pt)
        {
            db.Process( pt);
            return db;
        }

Объекты DataSource

Объект DataSource связывает службы Службы Analysis Services и базу данных, в которой находятся данные. Схема, которая представляет базовую модель для служб Службы Analysis Services, определяется объектом DataSourceView. Объект DataSource можно рассматривать как строку соединения с базой данных, в которой расположены данные.

Следующий образец кода демонстрирует создание объекта DataSource. Этот образец проверяет, что объект Server имеет соединение, а сервер и база данных существуют. Если объект DataSource существует, то он будет удален и создан повторно. Создается объект DataSource с теми же именем и внутренним идентификатором. В этом образце проверка строки подключения не выполняется, что позволяет проверить это.

        static string CreateDataSource(Database db, string strDataSourceName, string strConnectionString)
        {
                Server svr = db.Parent;
                DataSource ds = db.DataSources.FindByName(strDataSourceName);
                if (ds != null)
                    ds.Drop();
                // Create the data source
                ds = db.DataSources.Add(strDataSourceName, strDataSourceName);
                ds.ConnectionString = strConnectionString;

                // Send the data source definition to the server.
                ds.Update();

                return ds.Name;
        }

Объекты DataSourceView

Объект DataSourceView отвечает за хранение модели схемы для служб Службы Analysis Services. Чтобы объект DataSourceView мог хранить схему, сначала ее необходимо создать. Схемы создаются через объекты DataSet из пространства имен System.Data.

Следующий образец кода показывает создание части схемы, включенной в образец проекта служб AdventureWorks Analysis Services. Дополнительные сведения об установке образцов см. в разделе Образцы баз данных AdventureWorks2008R2. Текущий образец создает определения схемы для таблиц, вычисляемых столбцов, простых и составных связей. Схемы являются материализованными наборами данных.

Образец кода выполняет следующие действия.

  1. Создание объекта DataSourceView.

    Сначала проверьте, что объект DataSource существует. Если значение равно true, удалите объект DataSource и создайте его. Если объект DataSource отсутствует, создайте его.

  2. Открытие соединения с базой данных со строкой соединения DataSource.

  3. Создайте схему.

    Схема состоит из следующих компонентов.

    • Определение таблицы, метод AddTable().

    • Необязательный набор вычисляемых столбцов, метод AddComputedColumn().

    • Необязательный набор связей, метод AddRelation.

    • Необязательный набор составных связей, метод AddCompositeRelations.

  4. Обновите сервер.

ПримечаниеПримечание

Следующий образец кода усечен с целью повышения его наглядности. Полный код приведен в конце этого раздела.

ПримечаниеПримечание

В следующем образце кода использованы следующие методы: AddTable, AddComputedColumn, AddRelation и AddCompositeRelation.

ПримечаниеПримечание

Предложение WHERE 1=0 используется для того, чтобы запрос не возвращал строки в объект DataSet.

        static DataSourceView CreateDataSourceView(Database db, string strDataSourceName)
        {
            // Create the data source view
            DataSourceView dsv = db.DataSourceViews.FindByName(strDataSourceName);
            if ( dsv != null)
               dsv.Drop();
            dsv = db.DataSourceViews.Add(strDataSourceName);
            dsv.DataSourceID = strDataSourceName;
            dsv.Schema = new DataSet();
            dsv.Schema.Locale = CultureInfo.CurrentCulture;

            // Open a connection to the data source
            OleDbConnection connection
                = new OleDbConnection(dsv.DataSource.ConnectionString);
            connection.Open();

            #region Create tables

            // Add the DimTime table
            AddTable(dsv, connection, "DimTime");
            AddComputedColumn(dsv, connection, "DimTime", "SimpleDate", "DATENAME(mm, FullDateAlternateKey) + ' ' + DATENAME(dd, FullDateAlternateKey) + ',' + ' ' + DATENAME(yy, FullDateAlternateKey)");

            // Add the DimProductCategory table
            AddTable(dsv, connection, "DimProductCategory");

            // Add the DimProductSubcategory table
            AddTable(dsv, connection, "DimProductSubcategory");
            AddRelation(dsv, "DimProductSubcategory", "ProductCategoryKey", "DimProductCategory", "ProductCategoryKey");

            // Add the FactInternetSales table
            AddTable(dsv, connection, "FactInternetSales");
"DimTime", "TimeKey");
            AddRelation(dsv, "FactInternetSales", "ShipDateKey", "DimTime", "TimeKey");
            AddRelation(dsv, "FactInternetSales", "DueDateKey", "DimTime", "TimeKey");

            // Add the FactInternetSalesReason table
            AddTable(dsv, connection, "FactInternetSalesReason");
            AddCompositeRelation(dsv, "FactInternetSalesReason", "FactInternetSales", "SalesOrderNumber", "SalesOrderLineNumber");
            dsv.Update();

            #endregion

            // Send the data source view definition to the server
            dsv.Update();

            return dsv;
        }

        static void AddTable(DataSourceView dsv, OleDbConnection connection, String tableName)
        {
            string strSelectText = "SELECT * FROM [dbo].[" + tableName + "] WHERE 1=0";
            OleDbDataAdapter adapter = new OleDbDataAdapter(strSelectText, connection);
            DataTable[] dataTables = adapter.FillSchema(dsv.Schema,
                SchemaType.Mapped, tableName);
            DataTable dataTable = dataTables[0];

            dataTable.ExtendedProperties.Add("TableType", "Table");
            dataTable.ExtendedProperties.Add("DbSchemaName", "dbo");
            dataTable.ExtendedProperties.Add("DbTableName", tableName);
            dataTable.ExtendedProperties.Add("FriendlyName", tableName);


            dataTable = null;
            dataTables = null;
            adapter = null;
        }

        static void AddComputedColumn(DataSourceView dsv, OleDbConnection connection, String tableName, String computedColumnName, String expression)
        {
            DataSet tmpDataSet = new DataSet();
            tmpDataSet.Locale = CultureInfo.CurrentCulture;
            OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT ("
                + expression + ") AS [" + computedColumnName + "] FROM [dbo].["
                + tableName + "] WHERE 1=0", connection);
            DataTable[] dataTables = adapter.FillSchema(tmpDataSet,
                SchemaType.Mapped, tableName);
            DataTable dataTable = dataTables[0];
            DataColumn dataColumn = dataTable.Columns[computedColumnName];

            dataTable.Constraints.Clear();
            dataTable.Columns.Remove(dataColumn);

            dataColumn.ExtendedProperties.Add("DbColumnName", computedColumnName);
            dataColumn.ExtendedProperties.Add("ComputedColumnExpression",
                expression);
            dataColumn.ExtendedProperties.Add("IsLogical", "True");

            dsv.Schema.Tables[tableName].Columns.Add(dataColumn);

            dataColumn = null;
            dataTable = null;
            dataTables = null;
            adapter = null;
            tmpDataSet = null;
        }

        static void AddRelation(DataSourceView dsv, String fkTableName, String fkColumnName, String pkTableName, String pkColumnName)
        {
            DataColumn fkColumn
                = dsv.Schema.Tables[fkTableName].Columns[fkColumnName];
            DataColumn pkColumn
                = dsv.Schema.Tables[pkTableName].Columns[pkColumnName];
            dsv.Schema.Relations.Add("FK_" + fkTableName + "_"
                + fkColumnName, pkColumn, fkColumn, true);
        }

        static void AddCompositeRelation(DataSourceView dsv, String fkTableName, String pkTableName, String columnName1, String columnName2)
        {
            DataColumn[] fkColumns = new DataColumn[2];
            fkColumns[0] = dsv.Schema.Tables[fkTableName].Columns[columnName1];
            fkColumns[1] = dsv.Schema.Tables[fkTableName].Columns[columnName2];

            DataColumn[] pkColumns = new DataColumn[2];
            pkColumns[0] = dsv.Schema.Tables[pkTableName].Columns[columnName1];
            pkColumns[1] = dsv.Schema.Tables[pkTableName].Columns[columnName2];

            dsv.Schema.Relations.Add("FK_" + fkTableName + "_" + columnName1
                + "_" + columnName2, pkColumns, fkColumns, true);
        }

В этом образце кода методы AddTable и AddComputedColumn используют метод FillSchema объекта DataAdapter для добавления объекта DataTable в объект DataSet, а также для настройки схемы таким образом, чтобы она соответствовала схеме источника данных. Расширенные свойства добавляют сведения, необходимые для настройки схемы для служб Службы Analysis Services.

В этом образце кода методы AddRelation и AddCompositeRelation добавляют реляционные столбцы в зависимости от существующей схемы и существующих в модели столбцов. Столбцы должны принадлежать таблице, определенной в схеме для работы этих методов.

Ниже приведен полный образец кода.

        static DataSourceView CreateDataSourceView(Database db, string strDataSourceName)
        {
            // Create the data source view
            DataSourceView dsv = db.DataSourceViews.FindByName(strDataSourceName);
            if ( dsv != null)
               dsv.Drop();
            dsv = db.DataSourceViews.Add(strDataSourceName);
            dsv.DataSourceID = strDataSourceName;
            dsv.Schema = new DataSet();
            dsv.Schema.Locale = CultureInfo.CurrentCulture;

            // Open a connection to the data source
            OleDbConnection connection
                = new OleDbConnection(dsv.DataSource.ConnectionString);
            connection.Open();

            #region Create tables

            // Add the DimTime table
            AddTable(dsv, connection, "DimTime");
            AddComputedColumn(dsv, connection, "DimTime", "SimpleDate", "DATENAME(mm, FullDateAlternateKey) + ' ' + DATENAME(dd, FullDateAlternateKey) + ',' + ' ' + DATENAME(yy, FullDateAlternateKey)");
            AddComputedColumn(dsv, connection, "DimTime", "CalendarYearDesc", "'CY' + ' ' + CalendarYear");
            AddComputedColumn(dsv, connection, "DimTime", "CalendarSemesterDesc", "CASE WHEN CalendarSemester = 1 THEN 'H1'+' '+ 'CY' +' '+ CONVERT(CHAR (4), CalendarYear) ELSE 'H2'+' '+ 'CY' +' '+ CONVERT(CHAR (4), CalendarYear) END");
            AddComputedColumn(dsv, connection, "DimTime", "CalendarQuarterDesc", "'Q' + CONVERT(CHAR (1), CalendarQuarter) +' '+ 'CY' +' '+ CONVERT(CHAR (4), CalendarYear)");
            AddComputedColumn(dsv, connection, "DimTime", "MonthName", "EnglishMonthName+' '+ CONVERT(CHAR (4), CalendarYear)");
            AddComputedColumn(dsv, connection, "DimTime", "FiscalYearDesc", "'FY' + ' ' + FiscalYear");
            AddComputedColumn(dsv, connection, "DimTime", "FiscalSemesterDesc", "CASE WHEN FiscalSemester = 1 THEN 'H1'+' '+ 'FY' +' '+ CONVERT(CHAR (4), FiscalYear) ELSE 'H2'+' '+ 'FY' +' '+ CONVERT(CHAR (4), FiscalYear) END");
            AddComputedColumn(dsv, connection, "DimTime", "FiscalQuarterDesc", "'Q' + CONVERT(CHAR (1), FiscalQuarter) +' '+ 'FY' +' '+ CONVERT(CHAR (4), FiscalYear)");
            AddComputedColumn(dsv, connection, "DimTime", "FiscalMonthNumberOfYear", "CASE WHEN MonthNumberOfYear = '1'  THEN CONVERT(int,'7') WHEN MonthNumberOfYear = '2'  THEN CONVERT(int,'8') WHEN MonthNumberOfYear = '3'  THEN CONVERT(int,'9') WHEN MonthNumberOfYear = '4'  THEN CONVERT(int,'10') WHEN MonthNumberOfYear = '5'  THEN CONVERT(int,'11') WHEN MonthNumberOfYear = '6'  THEN CONVERT(int,'12') WHEN MonthNumberOfYear = '7'  THEN CONVERT(int,'1') WHEN MonthNumberOfYear = '8'  THEN CONVERT(int,'2') WHEN MonthNumberOfYear = '9'  THEN CONVERT(int,'3') WHEN MonthNumberOfYear = '10' THEN CONVERT(int,'4') WHEN MonthNumberOfYear = '11' THEN CONVERT(int,'5') WHEN MonthNumberOfYear = '12' THEN CONVERT(int,'6') END");
            dsv.Update();

            // Add the DimGeography table
            AddTable(dsv, connection, "DimGeography");

            // Add the DimProductCategory table
            AddTable(dsv, connection, "DimProductCategory");

            // Add the DimProductSubcategory table
            AddTable(dsv, connection, "DimProductSubcategory");
            AddRelation(dsv, "DimProductSubcategory", "ProductCategoryKey", "DimProductCategory", "ProductCategoryKey");

            // Add the DimProduct table
            AddTable(dsv, connection, "DimProduct");
            AddComputedColumn(dsv, connection, "DimProduct", "ProductLineName", "CASE ProductLine WHEN 'M' THEN 'Mountain' WHEN 'R' THEN 'Road' WHEN 'S' THEN 'Accessory' WHEN 'T' THEN 'Touring' ELSE 'Components' END");
            AddRelation(dsv, "DimProduct", "ProductSubcategoryKey", "DimProductSubcategory", "ProductSubcategoryKey");
            dsv.Update();

            // Add the DimCustomer table
            AddTable(dsv, connection, "DimCustomer");
            AddComputedColumn(dsv, connection, "DimCustomer", "FullName", "CASE WHEN MiddleName IS NULL THEN FirstName + ' ' + LastName ELSE FirstName + ' ' + MiddleName + ' ' + LastName END");
            AddComputedColumn(dsv, connection, "DimCustomer", "GenderDesc", "CASE WHEN Gender = 'M' THEN 'Male' ELSE 'Female' END");
            AddComputedColumn(dsv, connection, "DimCustomer", "MaritalStatusDesc", "CASE WHEN MaritalStatus = 'S' THEN 'Single' ELSE 'Married' END");
            AddRelation(dsv, "DimCustomer", "GeographyKey", "DimGeography", "GeographyKey");

            // Add the DimReseller table
            AddTable(dsv, connection, "DimReseller");
            AddComputedColumn(dsv, connection, "DimReseller", "OrderFrequencyDesc", "CASE WHEN OrderFrequency = 'A' THEN 'Annual' WHEN OrderFrequency = 'S' THEN 'Bi-Annual' ELSE 'Quarterly' END");
            AddComputedColumn(dsv, connection, "DimReseller", "OrderMonthDesc", "CASE WHEN OrderMonth = '1' THEN 'January' WHEN OrderMonth = '2' THEN 'February' WHEN OrderMonth = '3' THEN 'March' WHEN OrderMonth = '4' THEN 'April' WHEN OrderMonth = '5' THEN 'May' WHEN OrderMonth = '6' THEN 'June' WHEN OrderMonth = '7' THEN 'July' WHEN OrderMonth = '8' THEN 'August' WHEN OrderMonth = '9' THEN 'September' WHEN OrderMonth = '10' THEN 'October' WHEN OrderMonth = '11' THEN 'November' WHEN OrderMonth = '12' THEN 'December' ELSE 'Never Ordered' END");

            // Add the DimCurrency table
            AddTable(dsv, connection, "DimCurrency");
            dsv.Update();

            // Add the DimSalesReason table
            AddTable(dsv, connection, "DimSalesReason");

            // Add the FactInternetSales table
            AddTable(dsv, connection, "FactInternetSales");
            AddRelation(dsv, "FactInternetSales", "ProductKey", "DimProduct", "ProductKey");
            AddRelation(dsv, "FactInternetSales", "CustomerKey", "DimCustomer", "CustomerKey");
            AddRelation(dsv, "FactInternetSales", "OrderDateKey", "DimTime", "TimeKey");
            AddRelation(dsv, "FactInternetSales", "ShipDateKey", "DimTime", "TimeKey");
            AddRelation(dsv, "FactInternetSales", "DueDateKey", "DimTime", "TimeKey");
            AddRelation(dsv, "FactInternetSales", "CurrencyKey", "DimCurrency", "CurrencyKey");
            dsv.Update();

            // Add the FactResellerSales table
            AddTable(dsv, connection, "FactResellerSales");
            AddRelation(dsv, "FactResellerSales", "ProductKey", "DimProduct", "ProductKey");
            AddRelation(dsv, "FactResellerSales", "ResellerKey", "DimReseller", "ResellerKey");
            AddRelation(dsv, "FactResellerSales", "OrderDateKey", "DimTime", "TimeKey");
            AddRelation(dsv, "FactResellerSales", "ShipDateKey", "DimTime", "TimeKey");
            AddRelation(dsv, "FactResellerSales", "DueDateKey", "DimTime", "TimeKey");
            AddRelation(dsv, "FactResellerSales", "CurrencyKey", "DimCurrency", "CurrencyKey");

            // Add the FactInternetSalesReason table
            AddTable(dsv, connection, "FactInternetSalesReason");
            AddCompositeRelation(dsv, "FactInternetSalesReason", "FactInternetSales", "SalesOrderNumber", "SalesOrderLineNumber");
            dsv.Update();

            // Add the FactCurrencyRate table
            AddTable(dsv, connection, "FactCurrencyRate");
            AddRelation(dsv, "FactCurrencyRate", "CurrencyKey", "DimCurrency", "CurrencyKey");
            AddRelation(dsv, "FactCurrencyRate", "TimeKey", "DimTime", "TimeKey");

            #endregion

            // Send the data source view definition to the server
            dsv.Update();

            return dsv;
        }

        static void AddTable(DataSourceView dsv, OleDbConnection connection, String tableName)
        {
            string strSelectText = "SELECT * FROM [dbo].[" + tableName + "] WHERE 1=0";
            OleDbDataAdapter adapter = new OleDbDataAdapter(strSelectText, connection);
            DataTable[] dataTables = adapter.FillSchema(dsv.Schema,
                SchemaType.Mapped, tableName);
            DataTable dataTable = dataTables[0];

            dataTable.ExtendedProperties.Add("TableType", "Table");
            dataTable.ExtendedProperties.Add("DbSchemaName", "dbo");
            dataTable.ExtendedProperties.Add("DbTableName", tableName);
            dataTable.ExtendedProperties.Add("FriendlyName", tableName);


            dataTable = null;
            dataTables = null;
            adapter = null;
        }

        static void AddComputedColumn(DataSourceView dsv, OleDbConnection connection, String tableName, String computedColumnName, String expression)
        {
            DataSet tmpDataSet = new DataSet();
            tmpDataSet.Locale = CultureInfo.CurrentCulture;
            OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT ("
                + expression + ") AS [" + computedColumnName + "] FROM [dbo].["
                + tableName + "] WHERE 1=0", connection);
            DataTable[] dataTables = adapter.FillSchema(tmpDataSet,
                SchemaType.Mapped, tableName);
            DataTable dataTable = dataTables[0];
            DataColumn dataColumn = dataTable.Columns[computedColumnName];

            dataTable.Constraints.Clear();
            dataTable.Columns.Remove(dataColumn);

            dataColumn.ExtendedProperties.Add("DbColumnName", computedColumnName);
            dataColumn.ExtendedProperties.Add("ComputedColumnExpression",
                expression);
            dataColumn.ExtendedProperties.Add("IsLogical", "True");

            dsv.Schema.Tables[tableName].Columns.Add(dataColumn);

            dataColumn = null;
            dataTable = null;
            dataTables = null;
            adapter = null;
            tmpDataSet = null;
        }

        static void AddRelation(DataSourceView dsv, String fkTableName, String fkColumnName, String pkTableName, String pkColumnName)
        {
            DataColumn fkColumn
                = dsv.Schema.Tables[fkTableName].Columns[fkColumnName];
            DataColumn pkColumn
                = dsv.Schema.Tables[pkTableName].Columns[pkColumnName];
            dsv.Schema.Relations.Add("FK_" + fkTableName + "_"
                + fkColumnName, pkColumn, fkColumn, true);
        }

        static void AddCompositeRelation(DataSourceView dsv, String fkTableName, String pkTableName, String columnName1, String columnName2)
        {
            DataColumn[] fkColumns = new DataColumn[2];
            fkColumns[0] = dsv.Schema.Tables[fkTableName].Columns[columnName1];
            fkColumns[1] = dsv.Schema.Tables[fkTableName].Columns[columnName2];

            DataColumn[] pkColumns = new DataColumn[2];
            pkColumns[0] = dsv.Schema.Tables[pkTableName].Columns[columnName1];
            pkColumns[1] = dsv.Schema.Tables[pkTableName].Columns[columnName2];

            dsv.Schema.Relations.Add("FK_" + fkTableName + "_" + columnName1
                + "_" + columnName2, pkColumns, fkColumns, true);
        }