如何使用 Fluent API 在具有 ASC/DESC 排序的多列上添加索引?
How to add an index on multiple columns with ASC/DESC sort using the Fluent API?
我有一个 MVC ASP.NET 应用程序使用 Entity Framework 6 - Code First 方法。
使用 Fluent API,我如何在多个列上添加索引,每个列的 ASC/DESC 排序不同?
我见过很多使用多列的示例,但无法设置索引中列的排序顺序。
Table
-----
Id
Type
DateFor
DateCreated
Value
我想要以下列的索引:Type(ASC)、DateFor(Desc)、DateCreated(Desc)。
简答: Entity Framework 6 不允许多个索引具有不同的排序。
长答案:可能无法直接执行此操作,但可以通过一些调整来实现。看了很多之后,我发现创建一个新的 class 会继承 IndexAnnotation
并添加一个 SortOrder
属性.
真的很复杂
我发现实现此目的的最简单方法是查看现有 属性 我可以调整哪些内容以实现多索引排序。使用 Name
属性 可以做到这一点,因为它是一个字符串。可以直接在名称中加上排序索引,后面生成SQL代码时截取。
所以假设我需要像这样索引属性:
- 类型 (ASC)
- DateFor(降序)
- 创建日期(描述)
然后我会命名我的索引,后跟分隔符 (:) 和排序顺序。它看起来像这样:
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 生成 class 以生成正确的 SQL 代码来解析我们的 "tweaked" 索引名称:
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.
非常感谢Rowan Miller and all the articles on his blog. This answer was inspired by: 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]");
}
我有一个 MVC ASP.NET 应用程序使用 Entity Framework 6 - Code First 方法。
使用 Fluent API,我如何在多个列上添加索引,每个列的 ASC/DESC 排序不同?
我见过很多使用多列的示例,但无法设置索引中列的排序顺序。
Table
-----
Id
Type
DateFor
DateCreated
Value
我想要以下列的索引:Type(ASC)、DateFor(Desc)、DateCreated(Desc)。
简答: Entity Framework 6 不允许多个索引具有不同的排序。
长答案:可能无法直接执行此操作,但可以通过一些调整来实现。看了很多之后,我发现创建一个新的 class 会继承 IndexAnnotation
并添加一个 SortOrder
属性.
我发现实现此目的的最简单方法是查看现有 属性 我可以调整哪些内容以实现多索引排序。使用 Name
属性 可以做到这一点,因为它是一个字符串。可以直接在名称中加上排序索引,后面生成SQL代码时截取。
所以假设我需要像这样索引属性:
- 类型 (ASC)
- DateFor(降序)
- 创建日期(描述)
然后我会命名我的索引,后跟分隔符 (:) 和排序顺序。它看起来像这样:
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 生成 class 以生成正确的 SQL 代码来解析我们的 "tweaked" 索引名称:
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.
非常感谢Rowan Miller and all the articles on his blog. This answer was inspired by: 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]");
}