Entity Framework Horizontal Entity splitting
Продолжая тему The splendour and misery of Entity Framework, хочу рассказать подробнее о схеме Horizontal Entity splitting, которая не поддерживается текущей версией дизайнера EF Visual Studio 2008 SP1.
Подготовка EDM дизайнером
Начем с простой концептуальной модели.
Схема базы данных описывается таким скриптом:
CREATE TABLE [HorizontalEntitySplitting].[Part1]( [ID] INT IDENTITY(1,2) PRIMARY KEY NOT NULL, [Data] NVARCHAR(max) NULL, ) CREATE TABLE [HorizontalEntitySplitting].[Part2]( [ID] INT IDENTITY(2,2) PRIMARY KEY NOT NULL, [Data] NVARCHAR(max) NULL, )
Обратите внимание на IDENTITY(1,2)
и IDENTITY(2,2)
. Автогенераторы гарантируют уникальность ID
в пределах двух таблиц вместе.
Следующий шаг - привязка модели к БД, импорт таблиц Part1
и Part2
. Желательно выполнить отображение сущности на одну из таблиц, чтобы потом было проще править msl-файл вручную.
Ручное редактирование EDM
При ручном редактировании edmx-файла возможны проблемы с валидатором. Дело в том, что msbuild выполняет проверку средствами дизайнера и завершается с ошибкой, если модель содержит непонятные элементы. С нашим сценарием именно этот случай.
Полученный edmx-файл нужно разделить на 3. Сделать это можно вручную, но лучше воспользоваться готовой утилитой EdmGen2. Полученные csdl, ssdl и msl-файлы необходимо включить в проект, а в качестве значения Build Action поставить Embedded Resource (чтобы в строке подключения указать путь к этим файлам в виде ссылки на ресурсы сборки). Затем, воспользовавшись утилитой EDM Generator (EdmGen.exe
), получить файл с кодом и тоже включить его в состав проекта.
Савый важный шаг - изменение msl-файла. Необходимо отобразить сущность на две таблицы по некоторому условию. К сожалению, текущая версия Entity Framework позволяет накладывать условие лишь на булевое свойство сущности, тем самым ограничивая количество таблиц числом 2. Добавленные фрагменты выделены комментариями:
<Mapping xmlns="urn:schemas-microsoft-com:windows:storage:mapping:CS" Space="C-S"> <Alias Key="Model" Value="HorizontalSplitting" /> <Alias Key="Target" Value="HorizontalSplittingContainer.Store" /> <EntityContainerMapping CdmEntityContainer="HorizontalSplittingEntities" StorageEntityContainer="HorizontalSplittingContainerStoreContainer"> <EntitySetMapping Name="Sample"> <EntityTypeMapping TypeName="IsTypeOf(HorizontalSplittingContainer.Sample)"> <MappingFragment StoreEntitySet="Part1"> <ScalarProperty Name="Data" ColumnName="Data" /> <ScalarProperty Name="ID" ColumnName="ID" /> <!-- BEGIN MODIFICATION --> <Condition Name="Selector" Value="true" /> <!-- END MODIFICATION --> </MappingFragment> <!-- BEGIN MODIFICATION --> <MappingFragment StoreEntitySet="Part2"> <ScalarProperty Name="Data" ColumnName="Data" /> <ScalarProperty Name="ID" ColumnName="ID" /> <Condition Name="Selector" Value="false" /> </MappingFragment> <!-- END MODIFICATION --> </EntityTypeMapping> </EntitySetMapping> </EntityContainerMapping> </Mapping>
Пример использования
Первое, что необходимо сделать - проверить адекватность строки подключения в app.config
. Может возникнуть проблема с именами ресурсов т.к. имя ресурса формируется по шаблону {DefaultProjectNamespace}.{Folder}.{Folder}...{FileName}
. В моем случае строка подключения выглядит вот так:
<add name="HorizontalSplittingEntities" connectionString="metadata=res://*/Part2_MappingSchemes.HorizontalSplitting.HorizontalSplitting.csdl|res://*/Part2_MappingSchemes.HorizontalSplitting.HorizontalSplitting.ssdl|res://*/Part2_MappingSchemes.HorizontalSplitting.HorizontalSplitting.msl;provider=System.Data.SqlClient;provider connection string="Data Source=veastduo;Initial Catalog=Test;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />
Расмотрим небольшой отрывок кода:
using (var horizontalSplittingEntities = new HorizontalSplittingEntities()) { foreach (var sample in horizontalSplittingEntities.Sample) {} if (horizontalSplittingEntities.Sample.Count() < 10000) { for (Int32 i = 0; i < 1000; ++i) { horizontalSplittingEntities.AddToSample(new Sample {Data = DateTime.Today.ToString(), Selector = i%2 == 1}); } } horizontalSplittingEntities.SaveChanges(); }
Код выполняет две операции - чтение данных из БД и запись новых объектов в БД.
Выборка сущностей происходит, как нетрудно догадаться, с использованием оператора UNION ALL
:
SELECT [UnionAll1].[ID] AS [C1], [UnionAll1].[Data] AS [C2], CASE WHEN ([UnionAll1].[C1] = 1) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C3] FROM (SELECT [Extent1].[ID] AS [ID], [Extent1].[Data] AS [Data], cast(0 as bit) AS [C1] FROM [HorizontalEntitySplitting].[Part2] AS [Extent1] UNION ALL SELECT [Extent2].[ID] AS [ID], [Extent2].[Data] AS [Data], cast(1 as bit) AS [C1] FROM [HorizontalEntitySplitting].[Part1] AS [Extent2]) AS [UnionAll1]
А колонка [C1]
, кстати, и есть наш булевый переключатель.
С сохранением тоже нетрудно догадаться - в зависимости от значения свойства Selector
используется одна из двух таблиц:
exec sp_executesql N'insert [HorizontalEntitySplitting].[Part1]([Data]) values (@0) select [ID] from [HorizontalEntitySplitting].[Part1] where @@ROWCOUNT > 0 and [ID] = scope_identity()',N'@0 nvarchar(18)',@0=N'04.09.2008 0:00:00'
exec sp_executesql N'insert [HorizontalEntitySplitting].[Part2]([Data]) values (@0) select [ID] from [HorizontalEntitySplitting].[Part1] where @@ROWCOUNT > 0 and [ID] = scope_identity()',N'@0 nvarchar(18)',@0=N'04.09.2008 0:00:00'
Выводы
Поддержка разделения сущности горизонтально - прикольная фишка
. Но в текущем варианте, когда можно использовать лишь две таблицы и булевый переключатель, ее полезность существенно падает. Кстати, многие коммерческие базы данных (Oracle, MSSQL) поддерживают горизонтальное деление таблиц на физическом уровне. Поэтому такая схема возможно понадобится малоимущим.
0 коммент.:
Отправить комментарий