3

我有一个MVC ASP.NET应用程序使用实体框架6 - 代码优先的方法。如何使用Fluent API在ASC/DESC排序的多列上添加索引?

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

我见过很多使用多列的例子,但没有办法设置索引中列的排序顺序。我想要一个索引在以下列:类型(ASC),DateFor(Desc),DateCreated(Desc)。

+0

我不认为这是可能的EF6(希望EF7但不知道),但你可以运行自己的原始SQL添加索引。 – DavidG

+0

我知道我可以运行原始SQL,但是我在运行中生成实体,所以这将是一个痛苦的管理。 – Maxime

+0

也许你可以扩大http://stackoverflow.com/questions/23892553/creating-unique-index-with-entity-framework-6-1-fluent-api – ErikEJ

回答

4

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

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

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

因此,让我们假设我需要指数这样的性质:

  • 类型(ASC)
  • DateFor(DESC)
  • dateCreated会(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文件:

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。

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

+0

很好的答案。看起来像这是实体框架中的缺失功能。 – jjxtra

+0

我同意你的意见。 :-) – Maxime

0

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

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

我已经设法通过手动编辑Migrations并添加正确的代码来完成此操作。模型检查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; } 
+2

此解决方案不回答具有升序和降序排列的多列的问题。 – Maxime