如何使用Fluent API在具有ASC / DESC排序的多个列上添加索引?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用Fluent API在具有ASC / DESC排序的多个列上添加索引?相关的知识,希望对你有一定的参考价值。

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

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

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

Table
-----
Id
Type
DateFor
DateCreated
Value

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

答案

简短回答:实体框架6不允许具有不同排序的多个索引。

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

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

所以我们假设我需要索引这样的属性:

  • 类型(ASC)
  • DateFor(Desc)
  • dateCreated会(商品说明)

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

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

另一答案

我非常喜欢@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; }
另一答案

您可以手动编辑迁移,如下所示:

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

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

如何使用 Fluent Api 命名多对多关系表?

如何使用 Fluent API 映射这种冗余的父子关系

Fluent API:如何将 HasComputedColumnSql 与外键对象一起使用

如何使用 Entity Framework Code First Fluent API 指定表名

使用 EF4.1 Fluent API 将具有导航属性的实体拆分为两个表

EF Fluent Api 是不是可以设置最小长度?