В Entity Framework 6.1, как я могу использовать IndexAttribute для определения кластерного индекса?

Entity Framework 6.1 (с кодовым первым) добавила возможность добавления индексов через IndexAttribute. Атрибут принимает параметр для указания того, должен ли индекс кластеризоваться или не кластеризоваться.

В то же время AFAIK, Entity Framework требует, чтобы каждый объект имел первичный ключ (аннотированный с помощью KeyAttribute), и этот первичный ключ всегда создается как кластеризованный ключ.

Поэтому, как только я применяю IndexAttribute с IsClustered = true, я получаю сообщение об ошибке, потому что из-за ключа уже есть кластеризованный индекс.

Итак, как я могу создать кластеризованный индекс, который не является первичным ключом, используя IndexAttribute? Является ли свойство IsClustered для IndexAttribute применимым вообще?

(Для немного большего контекста: я сопоставляю таблицу, которая используется только для чтения через запросы LINQ. Мне не нужно фактически вставлять, обновлять или удалять объекты из этой таблицы. Поэтому мне не нужно первичный ключ вообще. В идеале мне нужна таблица без первичного ключа, но с уникальным кластеризованным индексом, оптимизированным для чтения.)

Изменить (2014-04-11): См. также https://entityframework.codeplex.com/workitem/2212.

Ответы

Ответ 1

В таблице может быть только один кластерный индекс, и по умолчанию Entity Framework/Sql Server помещает его в первичный ключ.

Итак, каково использование атрибута IsClustered для индекса, который не является первичным ключом? Хороший вопрос! (+1)

Этот класс:

public class Blog
{
    [Key()]
    public int Id { get; set; }

    [MaxLength(256)]//Need to limit size of column for clustered indexes
    public string Title { get; set; }

    [Index("IdAndRating", IsClustered = true)]
    public int Rating { get; set; }

}

создаст эту миграцию:

    public override void Up()
    {
        CreateTable(
            "dbo.Blogs",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Title = c.String(maxLength: 256),
                    Rating = c.Int(nullable: false),
                });
            .PrimaryKey(t => t.Id)
            .Index(t => t.Rating, clustered: true, name: "IdAndRating");
    }

Измените переход на это:

    public override void Up()
    {
        CreateTable(
            "dbo.Blogs",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Title = c.String(maxLength: 256),
                    Rating = c.Int(nullable: false),
                });

        CreateIndex("dbo.Blogs", 
                    new[] { "Rating", "Title" }, 
                    clustered: true, 
                    name: "IdAndRating");

    }

И это должно создать вашу таблицу без первичного ключа, но с кластеризованным индексом в других столбцах

ИЗМЕНИТЬ В вашем сценарии, где вам не нужно вставлять, обновлять или удалять данные, вам не нужен полный объект, вы можете использовать raw sql query для заполнения классов. Вам нужно будет добавить свой собственный sql для миграции, чтобы создать таблицу, потому что EF не будет ее автоматизировать, но это означает, что вы можете создать таблицу и индексировать так, как хотите.

Ответ 2

Вы можете получить свой собственный класс из SqlServerMigrationSqlGenerator и измените там pk:

public class NonClusteredPrimaryKeySqlMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
    protected override void Generate(System.Data.Entity.Migrations.Model.AddPrimaryKeyOperation addPrimaryKeyOperation)
    {
        addPrimaryKeyOperation.IsClustered = false;
        base.Generate(addPrimaryKeyOperation);
    }

    protected override void Generate(System.Data.Entity.Migrations.Model.CreateTableOperation createTableOperation)
    {
        createTableOperation.PrimaryKey.IsClustered = false;
        base.Generate(createTableOperation);
    }

    protected override void Generate(System.Data.Entity.Migrations.Model.MoveTableOperation moveTableOperation)
    {
        moveTableOperation.CreateTableOperation.PrimaryKey.IsClustered = false;
        base.Generate(moveTableOperation);
    }

полный пример здесь https://entityframework.codeplex.com/workitem/2163

Ответ 3

Сообщая вам правду - IndexAttribute полностью избыточен и не подходит для профессионального развития. Они не имеют основной функциональности и сосредоточены на вещах, которые не имеют никакого смысла.

Почему? Потому что он никогда не сможет и должен быть таким же гибким, как сборка script. Кластеризованный индекс - это только одно: следующая вещь, которую я пропустил, - это отфильтрованный индекс, в основном в форме "Уникальный индекс для непустого, не уникального индекса для нуля" в поле, который я использую очень регулярно для необязательного уникальные коды (потому что в SQL Server NULL равен другому NULL в генерации SQL, поэтому вы можете иметь только один NULL за один раз в уникальном индексе).

Если бы я был вами, я бы держался подальше от генерации базы данных - и миграции - и использовал классический подход сценариев установки/миграции. Thta - это то, где вы можете выполнять более сложные многоступенчатые миграции без потери ata. EF не обрабатывает ничего, кроме самых основных сценариев, и в этих областях я сомневаюсь, что этого достаточно. Может быть, потому, что я также и в основном работаю над большими базами данных, где мы делаем наши изменения очень тщательно - добавление индекса может занять некоторое время, когда вы нажмете двузначное число миллиардов строк (! 0 +).

Я бы предпочел, чтобы разработчики сосредоточились на некоторых недостающих областях, которые не могут легко и лучше работать, например производительность, такие как функции ядра ORM (лучшие перечисления, кэширование второго уровня, API для удаления лишних файлов, дополнительные вставки и обновления производительности) все, что можно сделать). Код Сначала приятно. Код. Сначала создавайте и поддерживайте базу данных - больно за пределами чрезвычайно простых сценариев.

Ответ 4

Ниже приведен код, основанный на ответе raditch, который работал на меня. Это позволяет кластеризовать первичные ключи по умолчанию. Это может потребоваться изменить, поскольку мы не используем встроенные миграции ef, чтобы фактически обрабатывать изменения.

public class NonClusteredPrimaryKeySqlMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
    public override IEnumerable<System.Data.Entity.Migrations.Sql.MigrationStatement> Generate(IEnumerable<MigrationOperation> migrationOperations, string providerManifestToken)
    {
        var primaries = migrationOperations.OfType<CreateTableOperation>().Where(x => x.PrimaryKey.IsClustered).Select(x => x.PrimaryKey).ToList();
        var indexes = migrationOperations.OfType<CreateIndexOperation>().Where(x => x.IsClustered).ToList();
        foreach (var index in indexes)
        {
            var primary = primaries.Where(x => x.Table == index.Table).SingleOrDefault();
            if (primary != null)
            {
                primary.IsClustered = false;
            }
        }
        return base.Generate(migrationOperations, providerManifestToken);
    }
}
public class EFCustomConfiguration : DbConfiguration
{
    public EFCustomConfiguration()
    {
        SetMigrationSqlGenerator("System.Data.SqlClient", () => new NonClusteredPrimaryKeySqlMigrationSqlGenerator());
    }
}

Ответ 5

Я пишу здесь свое решение, если кто-то еще интересуется этой темой. Ниже кода изменяется вывод команды add-migration.

public class CustomMigrationCodeGenerator : CSharpMigrationCodeGenerator
{
    protected override void Generate(CreateTableOperation createTableOperation, IndentedTextWriter writer)
    {
        if (createTableOperation.Columns.Any(x => x.Name == "Index") &&
             createTableOperation.Columns.Any(x => x.Name == "Id"))
        {
            if (createTableOperation.PrimaryKey != null)
            {
                createTableOperation.PrimaryKey.IsClustered = false;
            }
        }
        base.Generate(createTableOperation, writer);
    }
}

Вы можете зарегистрировать этот генератор в конфигурации миграции:

internal sealed class Configuration : DbMigrationsConfiguration<Ubrasoft.Freeman.WebApi.Db.MainDb>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
        CodeGenerator = new CustomMigrationCodeGenerator();  
        SetSqlGenerator("System.Data.SqlClient", new CustomMigrationSqlGenerator());        
    }

    protected override void Seed(Ubrasoft.Freeman.WebApi.Db.MainDb context)
    {

    }
}

И вот сгенерированный код миграции:

public override void Up()
    {
        CreateTable(
            "Tenant.Tenant",
            c => new
                {
                    Id = c.Guid(nullable: false),
                    TenantNo = c.Byte(nullable: false),
                    Name = c.String(nullable: false, maxLength: 20),
                    Index = c.Int(nullable: false, identity: true),
                    CreatedDate = c.DateTime(nullable: false, precision: 0, storeType: "datetime2"),
                    UpdatedDate = c.DateTime(nullable: false, precision: 0, storeType: "datetime2"),
                    IsDeleted = c.Boolean(nullable: false),
                })
            .PrimaryKey(t => t.Id, clustered: false)
            .Index(t => t.Index, unique: true, clustered: true);

    } 

Здесь - статья о пользовательском MigrationCodeGenerator.