简体   繁体   English

在Entity Framework 6.1(非Core)中,如何使用IndexAttribute定义聚簇索引?

[英]In Entity Framework 6.1 (not Core), how can I use the IndexAttribute to define a clustered index?

Entity Framework 6.1 (code-first) has added the possibility of adding indexes via the IndexAttribute . 实体框架6.1(代码优先)增加了通过IndexAttribute添加索引的可能性。 The attribute takes a parameter for specifying whether the index should be clustered or non-clustered. 该属性采用一个参数来指定索引应该是群集还是非群集。

At the same time, AFAIK, Entity Framework requires every entity to have a primary key (annotated with the KeyAttribute ), and that primary key is always created as a clustered key. 同时,AFAIK,Entity Framework要求每个实体都有一个主键(使用KeyAttribute注释),并且该主键始终作为聚簇键创建。

Therefore, as soon as I apply the IndexAttribute with IsClustered = true , I get an error because, due to the key, there already is a clustered index. 因此,只要我套用IndexAttributeIsClustered = true ,我得到一个错误,因为,由于关键,已经一个聚集索引。

So, how can I create a clustered index that is not the primary key using the IndexAttribute ? 那么,如何使用IndexAttribute创建不是主键的聚簇索引? Is the IsClustered property of the IndexAttribute usable at all? IsClustered属性IndexAttribute可用?

(For a little more context: I'm mapping a table that is only used for reading via LINQ queries. I do not need to actually insert, update, or delete entities from that table. Therefore, I don't need a primary key at all. Ideally, I'd like a table without a primary key, but with a non-unique, clustered index optimized for reading.) (有关更多上下文:我正在映射一个仅用于通过LINQ查询进行读取的表。我不需要实际插入,更新或删除该表中的实体。因此,我不需要主键理想情况下,我想要一个没有主键的表,但是有一个非唯一的聚簇索引,专为阅读而优化。)

Edit (2014-04-11): See also https://entityframework.codeplex.com/workitem/2212 . 编辑(2014-04-11):另请参阅https://entityframework.codeplex.com/workitem/2212

There can only be one clustered index on a table and by default Entity Framework/Sql Server puts it on the primary key. 表上只能有一个聚簇索引,默认情况下,Entity Framework / Sql Server将其放在主键上。

So what use is the IsClustered attribute on an index that is not the primary key? 那么,对于不是主键的索引, IsClustered属性有什么用? Good question! 好问题! (+1) (1)

This class: 这个班:

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; }

}

will generate this migration: 将生成此迁移:

    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");
    }

Alter the migration to this: 改变迁移到此:

    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");

    }

And that should create your table without a primary key but with the clustered index on the other columns 这应该创建没有主键但在其他列上使用聚簇索引的表

EDIT In your scenario where you don't need to insert, update or delete data, you don't need a full blown entity, you could use raw sql queries to populate the classes. 编辑在您不需要插入,更新或删除数据的场景中,您不需要一个完整的实体,您可以使用原始SQL查询来填充类。 You would need to add your own sql to the migration to create the table because EF won't automate it, but that means you can create the table and index just as you want it. 您需要将自己的sql添加到迁移中以创建表,因为EF不会自动化它,但这意味着您可以根据需要创建表和索引。

You can derive your own class from SqlServerMigrationSqlGenerator and change pk creation there: 您可以从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);
    }

full example here https://entityframework.codeplex.com/workitem/2163 完整示例https://entityframework.codeplex.com/workitem/2163

Below is the code based on raditch's answer that worked for me. 下面是基于raditch的答案的代码,对我有用。 This allows the primary keys to default to clustered. 这允许主键默认为群集。 It may need tweaked as we do not use the built in ef migrations to actually handle the changes 它可能需要调整,因为我们不使用内置的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());
    }
}

Telling you the truth - the IndexAttribute is totally redundant and not suitable for professinal development. 告诉你真相 - IndexAttribute完全是多余的,不适合专业发展。 They lack core functionality and focus on stuff that makes little sense. 他们缺乏核心功能,专注于没有意义的东西。

Why? 为什么? Because it never can will and should be as flexible as a build script. 因为它永远不会,也应该像构建脚本一样灵活。 Clustered index is only one thing - the next thing I would miss is a filtered index, mostly in teh form of "Unique index for non null, non-unique index for null" on a field, which I happen to use very regularly for optional unique codes (because in SQL Server a NULL is equal to another NULL in SQL generation, so you can only have one NULL at a time in a unique index). 聚簇索引只是一件事 - 接下来我会想念的是一个过滤索引,主要是在一个字段上的“非空的唯一索引,非唯一索引为null”的形式,我碰巧经常使用它来选择唯一代码(因为在SQL Server中,NULL等于SQL生成中的另一个NULL,因此在唯一索引中一次只能有一个NULL)。

If I were you I would stay away from database generation - and migrations - and use a classical setup/migration scripts approach. 如果我是你,我将远离数据库生成 - 和迁移 - 并使用经典的设置/迁移脚本方法。 Thta is something where you can do more complex multi step migrations without possibly ata loss. 这是你可以进行更复杂的多步骤迁移而不会丢失的东西。 EF does not handle anything but the most basic scenarios - and in these areas I doubt that is enough. 除了最基本的场景外,EF不会处理任何事情 - 在这些方面我怀疑这已经足够了。 Can be it is because I also and mostly work on large databases where we do our changes very carefully - adding an index can take some time when you hit a double digit number of billions of rows (!0+). 可能是因为我也主要在大型数据库上工作,我们非常谨慎地进行更改 - 添加索引可能需要一些时间才能达到数十亿行(!0+)的两位数。

I would prefer the developers would focus on some ofher missing areas tht can not easily and better be worked around, like performance, like core ORM features (better enums, second level caching, bulk delete API, more performance inserts and updates - all things that are doable). 我更希望开发人员能够专注于一些无法轻松更好地解决的缺失领域,比如性能,如核心ORM功能(更好的枚举,二级缓存,批量删除API,更多性能插入和更新 - 所有这些是可行的)。 Code First is nice. Code First很不错。 Code First generating and maintainign the database is - painfull outside extremely simple scenarios. Code First生成和维护数据库是非常简单的场景之外的痛苦。

I write here my solution if anyone still interested in this subject. 如果有人对此主题感兴趣,我会在此写下我的解决方案。 Below code changes output of add-migration command. 下面的代码更改了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);
    }
}

You can register this generator in migration configuration: 您可以在迁移配置中注册此生成器:

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)
    {

    }
}

And here is the generated migration code: 这是生成的迁移代码:

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);

    } 

Here is the article about custom MigrationCodeGenerator. 以下是有关自定义MigrationCodeGenerator的文章。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM