Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

Entity Framework Horizontal Entity splitting

Продолжая тему The splendour and misery of Entity Framework, хочу рассказать подробнее о схеме Horizontal Entity splitting, которая не поддерживается текущей версией дизайнера EF Visual Studio 2008 SP1.

Подготовка EDM дизайнером

Начем с простой концептуальной модели.
Sample Entity diagram
Схема базы данных описывается таким скриптом:

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=&quot;Data Source=veastduo;Initial Catalog=Test;Integrated Security=True;MultipleActiveResultSets=True&quot;" 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 коммент.:

Отправить комментарий

Copyright 2007-2011 Chabster