在 Entity Framework 6.1(非核心)中,如何使用 IndexAttribute 来定义聚集索引?

Posted

技术标签:

【中文标题】在 Entity Framework 6.1(非核心)中,如何使用 IndexAttribute 来定义聚集索引?【英文标题】:In Entity Framework 6.1 (not Core), how can I use the IndexAttribute to define a clustered index? 【发布时间】:2014-05-13 20:03:27 【问题描述】:

Entity Framework 6.1(代码优先)增加了通过IndexAttribute 添加索引的可能性。该属性带有一个参数,用于指定索引是集群还是非集群。

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

因此,当我将IndexAttributeIsClustered = true 一起应用时,我会收到一个错误,因为由于键的原因,已经存在 一个聚集索引。

那么,如何使用IndexAttribute 创建一个不是主键的聚集索引? IndexAttributeIsClustered 属性是否可用?

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

编辑(2014-04-11):另见https://entityframework.codeplex.com/workitem/2212。

【问题讨论】:

How to create a Clustered Index with Entity Framework Core的可能重复 @Zze 当 Entity Framework Core 与 Entity Framework 6.1 完全不同时,它怎么会重复这个问题?此外,这个问题来自 2014 年,当时 Entity Framework Core 甚至还不存在。 【参考方案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 queries 来填充类。您需要将自己的 sql 添加到迁移中以创建表,因为 EF 不会自动执行它,但这意味着您可以根据需要创建表和索引。

【讨论】:

感谢您的回答;我只是认为“IndexAttribute 的 IsClustered 属性实际上是无用的,需要手动解决方法,例如手写迁移或子类代码生成器”。 这对于已经存在的表没有用处。我们需要能够删除现有的聚集索引,将其重新创建为非聚集索引,然后在其他列上创建新的聚集索引。如果我们手动完成这一切,EF 怎么知道主键不再聚集?它是否关心,还是会影响查询生成? @Triynko 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】:

以下是基于 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());
    

【讨论】:

【参考方案4】:

实话告诉你——IndexAttribute 完全是多余的,不适合专业开发。他们缺乏核心功能,专注于毫无意义的事情。

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

如果我是你,我会远离数据库生成 - 和迁移 - 并使用经典的设置/迁移脚本方法。 Thta 是您可以进行更复杂的多步骤迁移而不会丢失数据的地方。 EF 只处理最基本的场景——在这些领域我怀疑这是否足够。可能是因为我也主要在大型数据库上工作,在这些数据库中我们非常仔细地进行更改 - 当您达到数十亿行 (!0+) 的两位数时,添加索引可能需要一些时间。

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

【讨论】:

不确定您所说的“远离迁移”是什么意思。什么是“经典设置/迁移脚本方法”?当我想要一个唯一索引时,我在迁移中使用 Sql - 有点像这样:***.com/a/22038834/150342 或这个***.com/a/18205124/150342 这是一组用于更新数据库版本号的脚本。与过去 50 年人们管理数据库版本控制的方式相同。新版本执行一组脚本以受控方式更新数据库。 好的,但您不使用迁移来生成这些脚本并处理版本控制吗? 不,从来没有。看,迁移是超原始的。您如何从具有用户名的字段移动到查找表?你必须首先生成表,然后填充它,然后为外键添加字段,然后填充它。没有简单的“从模式 a 转到模式 b,然后忘记删除字段中的所有数据”。我也不希望在程序启动时发生这种情况 - 这是必须安排的管理更改(备份、运行迁移)(由于迁移运行时可能有更长的停机时间)。迁移完全不涵盖除最原始/小数据场景之外的所有场景。 好的。这就是我在迁移中的做法: 1. 将模型更改为新设计。 2.编写sql以填充新设计 3.将sql添加到create table指令和drop column指令之间的迁移中。然后使用迁移生成更改脚本【参考方案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);

     

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

【讨论】:

以上是关于在 Entity Framework 6.1(非核心)中,如何使用 IndexAttribute 来定义聚集索引?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Entity Framework 6.1 fluent API 创建唯一索引

使用 Entity Framework 6.1 和 MVC 5 从数据库中使用 Code First 后如何同步模型?

如何将针对 Entity Framework .Net 4.6.1 的库与 .Net Core 应用程序一起使用

linq to sql 鍜?entity framework 杈撳嚭sql璇彞

Entity Framework 学习系列 - 认识理解Entity Framework

MVC5 Entity Framework学习之Entity Framework高级功能