在 Entity Framework 6.1(不是 Core 版本)中,我如何使用 IndexAttribute 定义聚集索引?

18
Entity Framework 6.1(代码优先)通过IndexAttribute增加了添加索引的可能性。该属性带有一个参数,用于指定索引是聚集还是非聚集。
同时,据我所知,Entity Framework要求每个实体都必须有一个主键(用KeyAttribute注释),并且该主键总是创建为聚集键。
因此,一旦我应用了IsClustered = true的IndexAttribute,就会出现错误,因为由于键已经存在聚集索引。
那么,如何使用IndexAttribute创建非主键的聚集索引呢? IndexAttribute的IsClustered属性是否可用?
(为了更好地理解:我正在映射一个仅用于通过LINQ查询进行读取的表。我不需要从该表中实际插入、更新或删除实体。因此,我根本不需要主键。理想情况下,我希望有一个没有主键但具有针对读取进行优化的非唯一聚集索引的表。)

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


1
@Zze 如果Entity Framework Core是完全不同于Entity Framework 6.1的东西,那么它怎么可能是那个问题的重复呢?此外,这个问题来自2014年,当时Entity Framework Core甚至还不存在。 - Fabian Schmied
5个回答

9

一张表只能有一个聚集索引,默认情况下,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");

    }

这将创建一个没有主键但有其他列的聚集索引的表。 编辑 在您不需要插入、更新或删除数据的情况下,您不需要完整的实体,可以使用原始SQL查询来填充类。您需要添加自己的SQL到迁移中来创建表,因为EF不会自动化它,但这意味着您可以按照您想要的方式创建表和索引。

1
谢谢你的回答;我会把它理解为“IndexAttribute的IsClustered属性实际上是无用的,需要手动解决,比如手写迁移或子类化代码生成器”。 - Fabian Schmied
这对于已经存在的表没有用处。我们需要能够删除现有的聚集索引,重新创建为非聚集索引,然后在其他列上创建新的聚集索引。如果我们手动执行所有这些操作,EF如何知道主键不再是聚集的?它在意吗,或者会影响查询生成? - Triynko
@Triynko EF 不需要知道主键不再是聚集的。它并不关心,也不会对查询生成产生影响。 - Colin

5
您可以从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

2
以下是根据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());
    }
}

1
告诉你实话,IndexAttribute是完全多余的,不适合专业开发。它们缺乏核心功能,专注于一些毫无意义的东西。
为什么?因为它永远不能像构建脚本一样灵活。集群索引只是其中一件事 - 我会错过的下一件事是过滤索引,大多以“非空唯一索引,空的非唯一索引”为形式出现在字段上,我经常用于可选的唯一代码(因为在SQL Server中,一个NULL等于另一个NULL,在SQL生成中只能有一个NULL在唯一索引中)。
如果我是你,我会远离数据库生成和迁移,并使用传统的设置/迁移脚本方法。这是一种可以执行更复杂的多步迁移而不会丢失数据的方法。EF处理的只是最基本的情况- 在这些领域我怀疑是否足够。也许是因为我主要在大型数据库上工作,我们非常谨慎地进行更改- 当您达到数十亿条记录时,添加索引可能需要一些时间!

我希望开发人员能够专注于其他一些缺失的领域,这些领域不容易且更好地解决,比如性能、核心ORM功能(更好的枚举、二级缓存、批量删除API、更高效的插入和更新——所有这些都是可行的)。Code First很好。但在极简单的情况下,生成和维护数据库仍然很痛苦。


这是一组脚本,用于更新数据库版本号。人们在过去的50年中管理数据库版本的方式都是相同的。新版本会执行一组脚本以控制方式更新数据库。 - TomTom
好的,但是你不使用迁移生成这些脚本并处理版本控制吗? - Colin
不,绝对不行。你看,迁移非常原始。如何从具有用户名的字段移动到查找表?你必须先生成表,然后填充它,然后添加外键字段,然后再填充它。没有简单的“从模式a转移到模式b并忘记删除字段中的所有数据”。我也永远不希望在程序启动时发生这种情况 - 这是一项管理更改(备份,运行迁移)必须安排时间表(由于可能需要较长的停机时间而迁移运行)。除了最原始/小型数据方案之外,所有方案都完全不包括在迁移中。 - TomTom
好的。这是我在迁移中的做法:1. 修改模型以适应新设计。2.编写 SQL 以填充新设计。3.将 SQL 添加到迁移中,在创建表指令和删除列指令之间。然后使用迁移生成更改脚本。 - Colin
我觉得你可以在迁移中插入任何类型的SQL语句,因此我让迁移来完成这项工作。我也喜欢使用迁移文件向前和向后回滚到任何我想要的架构版本的事实。这可能不适合你,但我很喜欢它。 - Colin
显示剩余7条评论

0

如果还有人对这个问题感兴趣,我在这里写下了我的解决方案。 以下代码更改了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的文章。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接