Мы уже создавали свои собственные - статические - типы данных и интегрировали их в C1. Теперь сделаем еще один шаг вперед.
Что если у нас имеется некий сторонний тип данных, например, таблица в какой-то базе данных или некое подобие таблицы или типа данных в стороннем приложении?
Для этого необходимо создать:
- статический тип данных - интерфейс данных C1, который будет своего рода оберткой для внешнего типа (таблицы и т.п.). Это то, что мы уже умеем делать.
- поставщик данных - который будет выполнять операции со сторонним типом данных, "понятным" ему способом, предоставляя доступ к этим действиям в С1, "понятным" способом для C1. Это то, что мы научимся делать.
Для примера возьмем пример (с официального сайта C1) по созданию поставщика данных, который дает доступ к таблицам в базе данных на SQL-сервере в режиме "только для чтения". Я буду опираться на проект-образец "C1 Northwind Integration", который можно скачать здесь и изучить самостоятельно.
Создание проекта библиотеки классов
- В Visual Studio создайте проект библиотеки классов.
- Добавьте к нему ссылки на Composite.dll, Microsoft.Practices.EnterpriseLibrary.Common.dll и System.Configuration, которые находятся в каталоге /Bin вашего сайта.
- Добавьте "LINQ to SQL" к вашему проекту (Add New Item > LINQ to SQL)
- Создайте соединение в Server Explorer...
- ... и добавьте базу данных, к которой вы будет предоставлять доступ через поставщика. Для примера, возьмите базу-образец от Майкрософт - "Northwind".
- Перетащите (drag'n'drop) нужные вам таблицы. Для примера возьмите Suppliers (Поставщики) и Products (Продукты).
- Сохраните проект.
Создание IData-интерфейсов
Теперь для каждой таблицы, добавленной в проект, сделайте следующее:
- Добавьте интерфейс, наследуемый от IData, напр. ISupplier и IProduct.
- Добавьте такие атрибуты к этим интерфейсам: KeyPropertyName, ImmutableTypeId, DataScope, DataAncestorProvider, NotReferenceableAttribute.
- Добавьте свойства в интерфейсы, который будут представлять реально существующие в таблицах колонки - необязательно все, а только те, которые вам нужны. Но ключевой столбец должен быть добавлен обязательно.
- Добавьте такие атрибуты к свойствам: StoreFieldType, ImmutableFieldId.
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; }
}
}
{
[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; }
}
}
{
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;
}
}
}
}
{
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 пересоздает эти классы, и ваши внесенные изменения теряются, поэтому обязательно проверяйте, что ваши изменения остались на месте.
Поэтому:
- Закройте визуальную часть LINQ to SQL.
- Внесите изменения в классы контекста данных (как указано выше: интерфейс + базовый класс).
Вам также нужно удалить конструктор без параметров во всех сгенерированных классах контекста данных.
Создание класса поставщика данных
Ну и наконец, создадим класс поставщика данных:
- Создайте класс, который будет служить классом поставщика данных.
- Пусть он реализует интерфейс поставщика данных в режиме "только для чтения" - IDataProvider.
- В этом классе создайте экземпляр классов контекста данных (см. выше).
- Используя экземпляр классов контекста данных, реализуйте методы 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();
}
}
}
{
[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
Когда вы создали своего поставщика данных:
- Соберите проект.
- Получившуюся сборку скопируйте в каталог /Bin вашего сайта.
- В файле конфигурации С1 /App_Data/Composite/Composite.config, в элемент configuration/Composite.Data.Plugins.DataProviderConfiguration/DataProviderPlugins, добавьте дочерний элемент, который зарегистрирует ваш поставщик данных в C1, напр.:
<add type="C1NorthwindIntegration.NorthwindDataProvider, C1NorthwindIntegration" name="NorthwindDataProvider"/>
- И затем в админке перезапустите сервер (Инструменты > Перезапустить сервер).
Чтобы увидеть элементы данных, которые вы получаете в С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>
<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>
Далее мы рассмотрим создание поставщика данных, который дает возможность не только извлекать данные из типов сторонних приложений, но добавлять, изменять и удалять данные.
(продолжение следует...)
Для того, чтобы не изменять модуль 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
{
}
Спасибо
ОтветитьУдалить