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

时间:2014-04-02 13:32:28

标签: c# entity-framework indexing entity-framework-6

Entity Framework 6.1(代码优先)增加了通过IndexAttribute添加索引的可能性。该属性采用一个参数来指定索引应该是聚簇还是非聚簇。

同时,AFAIK,实体框架要求每个实体都有一个主键(使用KeyAttribute注释),并且该主键始终创建为集群键。

因此,只要我将IndexAttributeIsClustered = true一起使用,就会出现错误,因为由于密钥,已经 聚集索引。

那么,如何使用IndexAttribute创建不是主键的聚簇索引? IsClustered的{​​{1}}属性是否可用?

(对于更多上下文:我正在映射一个仅用于通过LINQ查询进行读取的表。我不需要实际插入,更新或删除该表中的实体。因此,我不需要一个主键。理想情况下,我想要一个没有主键的表,但是有一个非唯一的聚簇索引,专为阅读而优化。)

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

5 个答案:

答案 0 :(得分:9)

表上只能有一个聚簇索引,默认情况下,实体框架/ 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 queries填充类。您需要将自己的sql添加到迁移中以创建表,因为EF不会自动化它,但这意味着您可以根据需要创建表和索引。

答案 1 :(得分: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 :(得分: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());
    }
}

答案 3 :(得分:1)

告诉你真相 - IndexAttribute是完全多余的,不适合专业发展。他们缺乏核心功能,专注于那些毫无意义的东西。

为什么呢?因为它永远不会,也应该像构建脚本一样灵活。聚簇索引只是一件事 - 接下来我会想念的是一个过滤索引,主要是在一个字段上的“非空的唯一索引,非唯一索引为null”的形式,我碰巧经常使用它来选择唯一代码(因为在SQL Server中,NULL等于SQL生成中的另一个NULL,因此在唯一索引中一次只能有一个NULL)。

如果我是你,我将远离数据库生成和迁移 - 并使用经典的设置/迁移脚本方法。这是你可以进行更复杂的多步骤迁移而不会丢失的东西。除了最基本的场景外,EF不会处理任何事情 - 在这些方面我怀疑这已经足够了。可能是因为我也主要在大型数据库上工作,我们非常谨慎地进行更改 - 添加索引可能需要一些时间才能达到数十亿行的两位数(!0 +)。

我希望开发人员能够专注于一些无法轻松解决的缺失领域,比如性能,如核心ORM功能(更好的枚举,二级缓存,批量删除API,更多性能插入和更新 - 所有可行的事情)。 Code First很不错。 Code First生成和维护数据库是非常简单的场景之外的痛苦。

答案 4 :(得分: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);

    } 

Here是关于自定义MigrationCodeGenerator的文章。