пятница, 29 июля 2011 г.

Поставщики данных

Мы уже создавали свои собственные - статические - типы данных и интегрировали их в C1. Теперь сделаем еще один шаг вперед.

Что если у нас имеется некий сторонний тип данных, например, таблица в какой-то базе данных или некое подобие таблицы или типа данных в стороннем приложении?

Для этого необходимо создать:
  • статический тип данных - интерфейс данных C1, который будет своего рода оберткой для внешнего типа (таблицы и т.п.). Это то, что мы уже умеем делать.
  • поставщик данных - который будет выполнять операции со сторонним типом данных, "понятным" ему способом, предоставляя доступ к этим действиям в С1, "понятным" способом для C1. Это то, что мы научимся делать.
Для примера возьмем пример (с официального сайта C1) по созданию поставщика данных, который дает доступ к таблицам в базе данных на SQL-сервере в режиме "только для чтения". Я буду опираться на проект-образец "C1 Northwind Integration", который можно скачать здесь и изучить самостоятельно.


Создание проекта библиотеки классов
  1. В Visual Studio создайте проект библиотеки классов.
  2. Добавьте к нему ссылки на Composite.dllMicrosoft.Practices.EnterpriseLibrary.Common.dll и System.Configuration, которые находятся в каталоге /Bin вашего сайта.
  3. Добавьте "LINQ to SQL" к вашему проекту (Add New Item > LINQ to SQL)
  4. Создайте соединение в Server Explorer...
  5. ... и добавьте базу данных, к которой вы будет предоставлять доступ через поставщика. Для примера, возьмите базу-образец от Майкрософт - "Northwind".
  6. Перетащите (drag'n'drop) нужные вам таблицы. Для примера возьмите Suppliers (Поставщики) и Products (Продукты).
  7. Сохраните проект.

Создание IData-интерфейсов

Теперь для каждой таблицы, добавленной в проект, сделайте следующее:
  1. Добавьте интерфейс, наследуемый от IData, напр. ISupplier и IProduct.
  2. Добавьте такие атрибуты к этим интерфейсам: KeyPropertyNameImmutableTypeIdDataScopeDataAncestorProviderNotReferenceableAttribute.
  3. Добавьте свойства в интерфейсы, который будут представлять реально существующие в таблицах колонки - необязательно все, а только те, которые вам нужны. Но ключевой столбец должен быть добавлен обязательно.
  4. Добавьте такие атрибуты к свойствам: StoreFieldTypeImmutableFieldId.
namespace C1NorthwindIntegration
{
  [KeyPropertyName("SupplierID")]
  [ImmutableTypeId("{717CABAD-6EA7-4451-92C1-B305080E2D8B}")]
  [DataScope(DataScopeIdentifier.PublicName)]
  [DataAncestorProvider(typeof(NoAncestorDataAncestorProvider))]
  [NotReferenceableAttribute]
  public interface ISupplier : IData
  {
    [StoreFieldType(PhysicalStoreFieldType.Integer)]
    [ImmutableFieldId("{FBAEC5FA-9A12-4D5F-B8CD-A1750164DB63}")]
    int SupplierID { get; set; }

    [StoreFieldType(PhysicalStoreFieldType.String, 40)]
    [ImmutableFieldId("{60405037-6ED7-4878-BF9F-4F7F82802986}")]
    string CompanyName { get; set; }

    [StoreFieldType(PhysicalStoreFieldType.String, 30)]
    [ImmutableFieldId("{6C076D79-CC73-4FE4-A971-2F43A00153C9}")]
    string ContactName { get; set; }

    [StoreFieldType(PhysicalStoreFieldType.String, 30)]
    [ImmutableFieldId("{C06EC433-BCF0-4AF1-B5BB-D5BCD4E11C25}")]
    string ContactTitle { get; set; }

    [StoreFieldType(PhysicalStoreFieldType.String, 60)]
    [ImmutableFieldId("{90F4DCD4-879C-4A5B-B90E-CF02E8E80151}")]
    string Address { get; set; }

    [StoreFieldType(PhysicalStoreFieldType.String, 15)]
    [ImmutableFieldId("{FB3664D5-2BAE-4AAE-9338-C252892D3795}")]
    string City { get; set; }

    [StoreFieldType(PhysicalStoreFieldType.String, 15)]
    [ImmutableFieldId("{9081D811-B9EB-4296-94A7-D872B9E71609}")]
    string Region { get; set; }

    [StoreFieldType(PhysicalStoreFieldType.String, 10)]
    [ImmutableFieldId("{BE0BD02D-6065-4800-90B4-65B389792725}")]
    string PostalCode { get; set; }

    [StoreFieldType(PhysicalStoreFieldType.String, 15)]
    [ImmutableFieldId("{E6E7FF7E-9EB9-4382-BC01-1F9F0E1B3229}")]
    string Country { get; set; }

    [StoreFieldType(PhysicalStoreFieldType.String, 24)]
    [ImmutableFieldId("{F008CF27-1B27-4B55-B2C5-591A42DCEC17}")]
    string Phone { get; set; }

    [StoreFieldType(PhysicalStoreFieldType.String, 24)]
    [ImmutableFieldId("{F0553941-D3CB-4A10-931D-0F01755C0378}")]
    string Fax { get; set; }

    [StoreFieldType(PhysicalStoreFieldType.LargeString)]
    [ImmutableFieldId("{A12419B8-082E-4583-A066-C4E253099FAA}")]
    string HomePage { get; set; }
  }
}




Создание класса для ключевого столбца

Также для каждой таблицы добавьте класс, который реализует интерфейс IDataId - для хранения значения из ключевого столбца. На самом деле, можно создать один общий класс для всех таблиц, если их ключевой столбец - одного и того же типа. Напр.:

namespace C1NorthwindIntegration
{
  public class DataId : IDataId
  {
    public int Id { get; set; }
  }
}


Реализация интерфейсов данных

Для каждой таблицы, создайте базовый класс, который реализует соответствующий интерфейс данных, и который работает со свойством DataSourceId. Напр.:

namespace C1NorthwindIntegration
{
  public class SupplierBase
  {
    private DataSourceId _dataSourceId = null;

    public DataSourceId DataSourceId
    {
      get
      {
        if (_dataSourceId == null)
        {
          DataId intDataId = new DataId { Id = ((ISupplier)this).SupplierID };
          _dataSourceId = NorthwindDataProvider.DataProviderContextHolder.CreateDataSourceId(intDataId, typeof(ISupplier));
        }
        return _dataSourceId;
      }
    }
  }
}

Этот класс должен реализовывать свойство DataSourceId, который будет получать экземпляр класса контекста данных (см. ниже), используя поставщик данных (который мы создадим ниже).


Изменение классов контекста данных

Когда вы добавляете таблицы к файлу "LINQ to SQL" (.dbml), Visual Studio генерирует классы контекста данных (.designer.cs) для каждой отдельной таблицы.

Нам нужно, чтобы каждый такой класс, дополнительно реализовывал соответствующий IData-интерфейс и наследовался от соответствующего базового класса. Напр.:

public partial class Supplier : SupplierBase, ISupplier, INotifyPropertyChanging, INotifyPropertyChanged

Всякий раз когда вы открываете визуальную часть LINQ to SQL, Visual Studio пересоздает эти классы, и ваши внесенные изменения теряются, поэтому обязательно проверяйте, что ваши изменения остались на месте.

Поэтому:
  1. Закройте визуальную часть LINQ to SQL.
  2. Внесите изменения в классы контекста данных (как указано выше: интерфейс + базовый класс).
Вам также нужно удалить конструктор без параметров во всех сгенерированных классах контекста данных.


Создание класса поставщика данных

Ну и наконец, создадим класс поставщика данных:
  1. Создайте класс, который будет служить классом поставщика данных.
  2. Пусть он реализует интерфейс поставщика данных в режиме "только для чтения" - IDataProvider.
  3. В этом классе создайте экземпляр классов контекста данных (см. выше).
  4. Используя экземпляр классов контекста данных, реализуйте методы GetData<>() и GetData<>(IDataId).
Поставщик должен приводить экземпляр типа Table<> из классов контекста данных к типу IQueryable<IData>  и возвращать последний.

Напр.:

namespace C1NorthwindIntegration
{
  [ConfigurationElementType(typeof(NonConfigurableDataProvider))]
  public class NorthwindDataProvider : IDataProvider
  {
    internal static DataProviderContext DataProviderContextHolder;

    private NorthwindDataClassesDataContext _context;

    public NorthwindDataProvider()
    {
      // Замените строку соединения!!!
      _context = new NorthwindDataClassesDataContext(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True");
    }

    public DataProviderContext Context
    {
      set
      {
        DataProviderContextHolder = value;
      }
    }

    public IEnumerable<Type> GetSupportedInterfaces()
    {
      return new Type[] { typeof(ISupplier), typeof(IProduct) };
    }

    public IQueryable<T> GetData<T>() where T : class, Composite.Data.IData
    {
      if (typeof(T) == typeof(IProduct))
      {
        IQueryable<IProduct> products = _context.Products;
        return (IQueryable<T>)products;
      }
      else if (typeof(T) == typeof(ISupplier))
      {
        IQueryable<ISupplier> supplier = _context.Suppliers;
        return (IQueryable<T>)supplier;
      }
      throw new NotImplementedException();
    }

    public T GetData<T>(IDataId dataId) where T : class, Composite.Data.IData
    {
      DataId intDataId = (DataId)dataId;
      if (typeof(T) == typeof(IProduct))
      {
        IProduct product = _context.Products.Where(f => f.ProductID == intDataId.Id).Single();
        return (T)product;
      }
      else if (typeof(T) == typeof(ISupplier))
      {
        ISupplier supplier = _context.Suppliers.Where(f => f.SupplierID == intDataId.Id).Single();
        return (T)supplier;
      }
      throw new NotImplementedException();
    }
  }
}


Добавление поставщика данных в C1

Когда вы создали своего поставщика данных:
  1. Соберите проект.
  2. Получившуюся сборку скопируйте в каталог /Bin вашего сайта.
  3. В файле конфигурации С1 /App_Data/Composite/Composite.config, в элемент configuration/Composite.Data.Plugins.DataProviderConfiguration/DataProviderPlugins, добавьте дочерний элемент, который зарегистрирует ваш поставщик данных в C1, напр.:

    <add type="C1NorthwindIntegration.NorthwindDataProvider, C1NorthwindIntegration" name="NorthwindDataProvider"/>

  4. И затем в админке перезапустите сервер (Инструменты > Перезапустить сервер).
Чтобы увидеть элементы данных, которые вы получаете в С1 из строннего приложения через ваш поставщик данных, вы можете создать файл определения дерева в каталоге /App_Data/Composite/TreeDefinitions вашего сайта. Напр.:

<?xml version="1.0" encoding="utf-8"?>
<ElementStructure
xmlns="http://www.composite.net/ns/management/trees/treemarkup/1.0" xmlns:f="http://www.composite.net/ns/function/1.0">
  <ElementStructure.AutoAttachments>
    <NamedParent Name="Data" Position="Top" />
  </ElementStructure.AutoAttachments>
  <ElementRoot>
    <Children>
      <Element Label="Northwind" Id="NorthwindRoot" >
        <Children>
          <DataElements Type="C1NorthwindIntegration.ISupplier, C1NorthwindIntegration"
Label="${C1:Data:C1NorthwindIntegration.ISupplier:CompanyName} - ${C1:Data:C1NorthwindIntegration.ISupplier:ContactName}">
            <Children>
              <DataElements Type="C1NorthwindIntegration.IProduct, C1NorthwindIntegration"
Label="${C1:Data:C1NorthwindIntegration.IProduct:ProductName}">
                <Filters>
                  <ParentIdFilter ParentType="C1NorthwindIntegration.ISupplier, C1NorthwindIntegration" ReferenceFieldName="SupplierID"/>
                </Filters>
                <OrderBy>
                  <Field FieldName="ProductName"/>
                </OrderBy>
              </DataElements>
            </Children>
          </DataElements >
        </Children>
      </Element>
    </Children>
  </ElementRoot>
</ElementStructure>

Далее  мы рассмотрим создание поставщика данных, который дает возможность не только извлекать данные из типов сторонних приложений, но добавлять, изменять и удалять данные.

(продолжение следует...)

2 комментария:

  1. Для того, чтобы не изменять модуль NorthwindDataClasses.designer.cs после изменений в дизайнере, можно использовать возможности partial:

    1) модуль NorthwindDataClasses.designer.cs не изменять;

    2) в модуль IProduct.cs добавить определение:

    public partial class Product : ProductBase, IProduct
    {
    }

    3) в модуль ISupplier.cs добавить определение:

    public partial class Supplier : SupplierBase, ISupplier
    {
    }

    ОтветитьУдалить