如何使用 Fluent API 通过 ASC/DESC 排序在多列上添加索引?

Posted

技术标签:

【中文标题】如何使用 Fluent API 通过 ASC/DESC 排序在多列上添加索引?【英文标题】:How to add an index on multiple columns with ASC/DESC sort using the Fluent API? 【发布时间】:2015-07-07 11:05:13 【问题描述】:

我有一个使用 Entity Framework 6 - Code First 方法的 MVC ASP.NET 应用程序。

使用 Fluent API,如何使用 ASC/DESC 排序在多列上添加索引,每列都不同?

我见过很多使用多列但无法设置索引中列的排序顺序的示例。

Table
-----
Id
Type
DateFor
DateCreated
Value

我想要以下列为索引:Type(ASC)、DateFor(Desc)、DateCreated(Desc)。

【问题讨论】:

我认为 EF6 不可能(希望是 EF7,但不确定),但您可以运行自己的原始 SQL 来添加索引。 我知道我可以运行原始 SQL,但我会动态生成实体,这样管理起来会很痛苦。 也许你可以扩展***.com/questions/23892553/… 【参考方案1】:

简答: Entity Framework 6 不允许具有不同排序的多个索引。

长答案:可能无法直接做到这一点,但可以通过一些调整来实现。经过大量阅读,我发现创建一个继承IndexAnnotation的新类并添加SortOrder属性会非常复杂。

我发现实现这一点的最简单方法是查看我可以调整哪些现有属性以实现多索引排序。使用 Name 属性可以做到这一点,因为它是一个字符串。可以直接在名称中添加排序索引,稍后生成SQL代码时截取。

假设我需要像这样索引属性:

类型 (ASC) DateFor (Desc) 创建日期(Desc)

然后我将命名我的索引,后跟分隔符 (:) 和排序顺序。它看起来像这样:

var indexName = "IX_Table:ASC,DESC,DESC";

具有多个字段的索引如下所示:

this.Property(t => t.Type)
    .HasColumnAnnotation(
        IndexAnnotation.AnnotationName,
        new IndexAnnotation(new[]
            
                new IndexAttribute(indexName)  Order = 1 
            
        )
    );

this.Property(t => t.DateFor)
    .HasColumnAnnotation(
        IndexAnnotation.AnnotationName,
        new IndexAnnotation(new[]
            
                new IndexAttribute(indexName)  Order = 2 
            
        )
    );

this.Property(t => t.DateCreated)
    .HasColumnAnnotation(
        IndexAnnotation.AnnotationName,
        new IndexAnnotation(new[]
            
                new IndexAttribute(indexName)  Order = 3 
            
        )
    );

我们现在必须创建一个自定义 SQL 生成类,以便生成正确的 SQL 代码来解析我们的“调整”索引名称:

public class CustomSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator

    protected override void Generate(CreateIndexOperation createIndexOperation)
    
        using (var writer = Writer())
        
            writer.Write("CREATE ");

            if (createIndexOperation.IsUnique)
            
                writer.Write("UNIQUE ");
            

            if (createIndexOperation.IsClustered)
            
                writer.Write("CLUSTERED ");
            
            else
            
                writer.Write("NONCLUSTERED ");
            

            string name = createIndexOperation.Name;
            string[] sorts = ;
            if (createIndexOperation.Name.Contains(":"))
            
                var parts = createIndexOperation.Name.Split(':');

                if (parts.Length >= 1)
                
                    name = parts[0];
                
                if (parts.Length >= 2)
                
                    sorts = parts[1].Split(',');
                
            

            writer.Write("INDEX ");
            writer.Write(Quote(name));
            writer.Write(" ON ");
            writer.Write(Name(createIndexOperation.Table));
            writer.Write("(");

            // Add the columns to the index with their respective sort order
            string fields = "";
            if (sorts.Length == 0 || sorts.Length == createIndexOperation.Columns.Count)
            
                for (int i=0 ; i<createIndexOperation.Columns.Count ; i++)
                
                    string sort = "ASC";
                    if (sorts.Length == 0)
                    
                        // Do nothing
                    
                    else if (sorts[i] != "ASC" && sorts[i] != "DESC")
                    
                        throw new Exception(string.Format("Expected sort for 0 is 'ASC' or 'DESC. Received: 1", name, sorts[i]));
                    
                    else 
                     
                        sort = sorts[i];  
                    

                    fields = fields + Quote(createIndexOperation.Columns[i]) + " " + sort + ",";
                
                fields = fields.Substring(0, fields.Length - 1);
            
            else
            
                throw new Exception(string.Format("The sort (ASC/DEC) count is not equal to the number of fields in your Index (0).", name));
            

            writer.Write(fields);

            writer.Write(")");
            Statement(writer);
        
    

最后,您需要通过编辑 Configuration.cs 文件来告诉 Entity Framework 使用您的新代码生成方法而不是默认方法:

internal sealed class MyConfiguration : DbMigrationsConfiguration<MyContext>


    /// <summary>
    /// Constructor
    /// </summary>
    public MyConfiguration()
    
        // Other stuff here...

        // Index/Unique custom generation (Ascending and Descending)
        SetSqlGenerator("System.Data.SqlClient", new CustomSqlServerMigrationSqlGenerator());
    

就是这样。它可能不是最干净的解决方案,但如果您动态生成实体(如我所做的那样),您将节省大量时间并避免忘记运行原始 SQL。

See the code here

非常感谢Rowan Miller 和他博客上的所有文章。这个答案的灵感来自:Customizing Code First Migrations Provider。

【讨论】:

很好的答案。似乎这是实体框架中一个损坏的缺失功能。 我同意你的看法。 :-)【参考方案2】:

我真的很喜欢@Maxime 的回答,但它很复杂,但我会努力学习这些东西。

我的解决方案更简单一些,而且它只是工作,所以在这里添加它,以防它对某人有用。

我已经通过手动编辑迁移并添加适当的代码来做到这一点。在模型检查时,EF 只检查索引是否存在而不是它的顺序。

    public override void Up()
    
        DropIndex("dbo.MonitoringItemHistory", "IX_ItemDate");
        Sql("ALTER TABLE dbo.MonitoringItemHistory DROP CONSTRAINT [PK_dbo.MonitoringItemHistory]");
        CreateIndex("dbo.MonitoringItemHistory", new[]  "MonitoringItemId", "ChangeTime" , clustered: true, name: "IX_ItemDate");
        Sql("ALTER TABLE dbo.MonitoringItemHistory ADD CONSTRAINT [PK_dbo.MonitoringItemHistory] PRIMARY KEY NONCLUSTERED (Id)");
    

    public override void Down()
    
        Sql("ALTER TABLE dbo.MonitoringItemHistory DROP CONSTRAINT [PK_dbo.MonitoringItemHistory]");
        DropIndex("dbo.MonitoringItemHistory", "IX_ItemDate");
        Sql("ALTER TABLE dbo.MonitoringItemHistory ADD CONSTRAINT [PK_dbo.MonitoringItemHistory] PRIMARY KEY CLUSTERED (Id)");
        CreateIndex("dbo.MonitoringItemHistory", new[]  "MonitoringItemId", "ChangeTime" , name: "IX_ItemDate");
    

在我的数据库实体代码中:

    [Index("IX_ItemDate", 1, IsClustered = true)]
    public int MonitoringItemId  get; set;
    [Index("IX_ItemDate", 2, IsClustered = true)]
    public DateTimeOffset ChangeTime  get; set; 

【讨论】:

此解决方案不回答具有多个列的升序和降序排序的问题。 不错,使用 sql 你可以做你需要的:) 只注意它依赖于数据库。这里有一些例子docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/…【参考方案3】:

您可以像这样手动编辑迁移:

public override void Up()

    Sql("CREATE NONCLUSTERED INDEX [IX_Index_name] ON [dbo].[TableName] ([ColumnName1] Asc,[ColumnName2] Desc,[ColumnName3] Desc)");


public override void Down()

     Sql("DROP INDEX [dbo].[TableName].[IX_Index_name]");

【讨论】:

注意:Sql 是传入的 MigrationBuilder 的方法,如果您的迁移方法类似于 protected override void Up(MigrationBuilder migrationBuilder)

以上是关于如何使用 Fluent API 通过 ASC/DESC 排序在多列上添加索引?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 Fluent API Entity Framework 定义多对多关系?

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

Code First约定-Fluent API配置

Code First约定-Fluent API配置

如何访问 Castle Windsor 的 Fluent Interfaces API?

如何使用 fluent api 命名地图关联