entity framework代码First Fluent Api:向列添加索引
我正在运行EF 4.2 CF,并希望在我的POCO对象的某些列上创build索引。
举一个例子,让我们说我们有这个雇员类:
public class Employee { public int EmployeeID { get; set; } public string EmployeeCode { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime HireDate { get; set; } }
我们经常通过他们的EmployeeCode来search雇员,而且由于有很多雇员,为了性能原因,这样做会很好。
我们可以用stream利的API做些什么吗? 或者可能是数据注释?
我知道可以执行这样的sql命令:
context.Database.ExecuteSqlCommand("CREATE INDEX IX_NAME ON ...");
我非常想避免这样的原始SQL。
我知道这不存在,但寻找的东西沿着这些线路:
class EmployeeConfiguration : EntityTypeConfiguration<Employee> { internal EmployeeConfiguration() { this.HasIndex(e => e.EmployeeCode) .HasIndex(e => e.FirstName) .HasIndex(e => e.LastName); } }
或者可能使用System.ComponentModel.DataAnnotations
POCO可能看起来像这样(我知道这不存在):
public class Employee { public int EmployeeID { get; set; } [Indexed] public string EmployeeCode { get; set; } [Indexed] public string FirstName { get; set; } [Indexed] public string LastName { get; set; } public DateTime HireDate { get; set; } }
任何人有任何想法如何做到这一点,或者如果有任何计划实施这样做,代码的第一种方式?
更新:正如Robba的回答中所提到的,这个特性是在EF版本6.1中实现的
在EF 4.3中引入迁移后,现在可以在修改或创build表格时添加索引。 以下是来自ADO.NET团队博客的EF 4.3基于代码的迁移演练的摘录
namespace MigrationsCodeDemo.Migrations { using System.Data.Entity.Migrations; public partial class AddPostClass : DbMigration { public override void Up() { CreateTable( "Posts", c => new { PostId = c.Int(nullable: false, identity: true), Title = c.String(maxLength: 200), Content = c.String(), BlogId = c.Int(nullable: false), }) .PrimaryKey(t => t.PostId) .ForeignKey("Blogs", t => t.BlogId, cascadeDelete: true) .Index(t => t.BlogId) .Index(p => p.Title, unique: true); AddColumn("Blogs", "Rating", c => c.Int(nullable: false, defaultValue: 3)); } public override void Down() { DropIndex("Posts", new[] { "BlogId" }); DropForeignKey("Posts", "BlogId", "Blogs"); DropColumn("Blogs", "Rating"); DropTable("Posts"); } } }
这是一个很好的强types添加索引的方式,这正是我第一次发布该问题时所要查找的内容。
您可以创build一个名为indexed的属性(如您所build议的),然后在自定义初始化程序中find该属性。
我创build了以下属性:
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)] public class IndexAttribute : Attribute { public IndexAttribute(bool isUnique = false, bool isClustered = false, SortOrder sortOrder = SortOrder.Ascending) { IsUnique = isUnique; IsClustered = isClustered; SortOrder = sortOrder == SortOrder.Unspecified ? SortOrder.Ascending : sortOrder; } public bool IsUnique { get; private set; } public bool IsClustered { get; private set; } public SortOrder SortOrder { get; private set; } //public string Where { get; private set; } }
然后我创build了一个自定义的初始化程序,它获得了在我的上下文中为实体创build的表名称列表。 我有两个基本类,所有我的实体inheritance,所以我做了以下获取表名称:
var baseEF = typeof (BaseEFEntity); var baseLink = typeof (BaseLinkTable); var types = AppDomain.CurrentDomain.GetAssemblies().ToList().SelectMany(s => s.GetTypes()).Where( baseEF.IsAssignableFrom).Union(AppDomain.CurrentDomain.GetAssemblies().ToList().SelectMany( s => s.GetTypes()).Where( baseLink.IsAssignableFrom)); var sqlScript = context.ObjectContext.CreateDatabaseScript(); foreach (var type in types) { var table = (TableAttribute) type.GetCustomAttributes(typeof (TableAttribute), true).FirstOrDefault(); var tableName = (table != null ? table.Name : null) ?? Pluralizer.Pluralize(type.Name);
然后,我发现每个实体上具有此属性的所有属性,然后执行SQL命令以在每个属性上生成索引。 甜!
//Check that a table exists if (sqlScript.ToLower().Contains(string.Format(CREATETABLELOOKUP, tableName.ToLower()))) { //indexes var indexAttrib = typeof (IndexAttribute); properties = type.GetProperties().Where(prop => Attribute.IsDefined(prop, indexAttrib)); foreach (var property in properties) { var attributes = property.GetCustomAttributes(indexAttrib, true).ToList(); foreach (IndexAttribute index in attributes) { var indexName = string.Format(INDEXNAMEFORMAT, tableName, property.Name, attributes.Count > 1 ? UNDERSCORE + (attributes.IndexOf(index) + 1) : string.Empty); try { context.ObjectContext.ExecuteStoreCommand( string.Format(INDEX_STRING, indexName, tableName, property.Name, index.IsUnique ? UNIQUE : string.Empty, index.IsClustered ? CLUSTERED : NONCLUSTERED, index.SortOrder == SortOrder.Ascending ? ASC : DESC)); } catch (Exception) { } } }
我甚至继续以相同的方式添加基于类的索引(可以有多列),唯一约束和默认约束。 还有什么更好的是,如果将这些属性放在inheritance的类上,那么索引或约束就会应用于所有inheritance它的类(表)。
BTW的pluralizer助手包含以下内容:
public static class Pluralizer { private static object _pluralizer; private static MethodInfo _pluralizationMethod; public static string Pluralize(string word) { CreatePluralizer(); return (string) _pluralizationMethod.Invoke(_pluralizer, new object[] {word}); } public static void CreatePluralizer() { if (_pluralizer == null) { var aseembly = typeof (DbContext).Assembly; var type = aseembly.GetType( "System.Data.Entity.ModelConfiguration.Design.PluralizationServices.EnglishPluralizationService"); _pluralizer = Activator.CreateInstance(type, true); _pluralizationMethod = _pluralizer.GetType().GetMethod("Pluralize"); } } }
要build立冻结的响应,您可以自行将代码手动编入迁移。
首先,进入软件包pipe理器控制台并使用add-migration
创build新add-migration
,然后为其命名。 将出现空白迁移。 坚持这个:
public override void Up() { CreateIndex("TableName", "ColumnName"); } public override void Down() { DropIndex("TableName",new[] {"ColumnName"}); }
请注意,如果您使用的是string字段,则需要将其封顶到450个字符的长度。
我最近也研究过这个,没有find其他的方法,所以我在创build数据库的时候创build了索引:
public class MyDBInitializer : DropCreateDatabaseIfModelChanges<MyContext> { private MyContext _Context; protected override void Seed(MyContext context) { base.Seed(context); _Context = context; // We create database indexes CreateIndex("FieldName", typeof(ClassName)); context.SaveChanges(); } private void CreateIndex(string field, Type table) { _Context.Database.ExecuteSqlCommand(String.Format("CREATE INDEX IX_{0} ON {1} ({0})", field, table.Name)); } }
请注意,在Entity Framework 6.1(目前处于testing阶段)将支持IndexAttribute注解索引属性,这将自动导致Code First Migrations中的(唯一)索引。
对于任何使用Entity Framework 6.1+的人来说,你可以用stream利的api来做以下事情:
modelBuilder .Entity<Department>() .Property(t => t.Name) .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));
阅读文档中的更多内容 。
那么我find了一个在线解决scheme,并适应了我的需要在这里它是:
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)] public class IndexAttribute : Attribute { public IndexAttribute(string name, bool unique = false) { this.Name = name; this.IsUnique = unique; } public string Name { get; private set; } public bool IsUnique { get; private set; } } public class IndexInitializer<T> : IDatabaseInitializer<T> where T : DbContext { private const string CreateIndexQueryTemplate = "CREATE {unique} INDEX {indexName} ON {tableName} ({columnName});"; public void InitializeDatabase(T context) { const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance; Dictionary<IndexAttribute, List<string>> indexes = new Dictionary<IndexAttribute, List<string>>(); string query = string.Empty; foreach (var dataSetProperty in typeof(T).GetProperties(PublicInstance).Where(p => p.PropertyType.Name == typeof(DbSet<>).Name)) { var entityType = dataSetProperty.PropertyType.GetGenericArguments().Single(); TableAttribute[] tableAttributes = (TableAttribute[])entityType.GetCustomAttributes(typeof(TableAttribute), false); indexes.Clear(); string tableName = tableAttributes.Length != 0 ? tableAttributes[0].Name : dataSetProperty.Name; foreach (PropertyInfo property in entityType.GetProperties(PublicInstance)) { IndexAttribute[] indexAttributes = (IndexAttribute[])property.GetCustomAttributes(typeof(IndexAttribute), false); NotMappedAttribute[] notMappedAttributes = (NotMappedAttribute[])property.GetCustomAttributes(typeof(NotMappedAttribute), false); if (indexAttributes.Length > 0 && notMappedAttributes.Length == 0) { ColumnAttribute[] columnAttributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), false); foreach (IndexAttribute indexAttribute in indexAttributes) { if (!indexes.ContainsKey(indexAttribute)) { indexes.Add(indexAttribute, new List<string>()); } if (property.PropertyType.IsValueType || property.PropertyType == typeof(string)) { string columnName = columnAttributes.Length != 0 ? columnAttributes[0].Name : property.Name; indexes[indexAttribute].Add(columnName); } else { indexes[indexAttribute].Add(property.PropertyType.Name + "_" + GetKeyName(property.PropertyType)); } } } } foreach (IndexAttribute indexAttribute in indexes.Keys) { query += CreateIndexQueryTemplate.Replace("{indexName}", indexAttribute.Name) .Replace("{tableName}", tableName) .Replace("{columnName}", string.Join(", ", indexes[indexAttribute].ToArray())) .Replace("{unique}", indexAttribute.IsUnique ? "UNIQUE" : string.Empty); } } if (context.Database.CreateIfNotExists()) { context.Database.ExecuteSqlCommand(query); } } private string GetKeyName(Type type) { PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public); foreach (PropertyInfo propertyInfo in propertyInfos) { if (propertyInfo.GetCustomAttribute(typeof(KeyAttribute), true) != null) return propertyInfo.Name; } throw new Exception("No property was found with the attribute Key"); } }
然后在你的dbcontext中重载OnModelCreating
protected override void OnModelCreating(DbModelBuilder modelBuilder) { Database.SetInitializer(new IndexInitializer<MyContext>()); base.OnModelCreating(modelBuilder); }
将索引属性应用于您的实体types,使用此解决scheme,您可以在同一个索引中使用相同的名称和唯一的多个字段。
为了支持多列和独特的约束,扩展了Tsuushin的答案:
private void CreateIndex(RBPContext context, string field, string table, bool unique = false) { context.Database.ExecuteSqlCommand(String.Format("CREATE {0}NONCLUSTERED INDEX IX_{1}_{2} ON {1} ({3})", unique ? "UNIQUE " : "", table, field.Replace(",","_"), field)); }
拓展Petoj
我修改了CreateIndexQueryTemplate
private const string CreateIndexQueryTemplate = "IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = '{indexName}') CREATE {unique} INDEX {indexName} ON {tableName} ({columnName});";
并从OnModelCreating中删除以下内容
Database.SetInitializer(new IndexInitializer<MyContext>());
并将以下内容添加到Configuration Seeding方法中
new IndexInitializer<MyContext>().InitializeDatabase(context);
这样每次执行更新数据库时都会运行索引属性。
如果你想把这个function添加到EF,那么你可以在这里投票http://entityframework.codeplex.com/workitem/57
jwsadler的Data Annotations扩展对我们来说是一个不错的select。 我们使用Annotations来影响对类或属性和Fluent API进行全局变更的处理。
我们的注释包括索引(唯一的和不唯一的)以及getdate()和(1)的缺省值。 代码示例显示了我们如何将其应用于我们的情况。 我们所有的类都从一个基类inheritance而来。 这个实现做了很多假设,因为我们有一个非常简单的模型。 我们正在使用entity framework6.0.1。 包括了很多意见。
using System; using System.Linq; using System.Data.Entity; using System.Data.Entity.Infrastructure; namespace YourNameSpace { public enum SqlOption { Active = 1, GetDate = 2, Index = 3, Unique = 4, } [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)] public class SqlAttribute : Attribute { public SqlAttribute(SqlOption selectedOption = SqlOption.Index) { this.Option = selectedOption; } public SqlOption Option {get; set;} } // See enum above, usage examples: [Sql(SqlOption.Unique)] [Sql(SqlOption.Index)] [Sql(SqlOption.GetDate)] public class SqlInitializer<T> : IDatabaseInitializer<T> where T : DbContext { // Create templates for the DDL we want generate const string INDEX_TEMPLATE = "CREATE NONCLUSTERED INDEX IX_{columnName} ON [dbo].[{tableName}] ([{columnName}]);"; const string UNIQUE_TEMPLATE = "CREATE UNIQUE NONCLUSTERED INDEX UQ_{columnName} ON [dbo].[{tableName}] ([{columnName}]);"; const string GETDATE_TEMPLATE = "ALTER TABLE [dbo].[{tableName}] ADD DEFAULT (getdate()) FOR [{columnName}];"; const string ACTIVE_TEMPLATE = "ALTER TABLE [dbo].[{tableName}] ADD DEFAULT (1) FOR [{columnName}];"; // Called by Database.SetInitializer(new IndexInitializer< MyDBContext>()); in MyDBContext.cs public void InitializeDatabase(T context) { // To be used for the SQL DDL that I generate string sql = string.Empty; // All of my classes are derived from my base class, Entity var baseClass = typeof(Entity); // Get a list of classes in my model derived from my base class var modelClasses = AppDomain.CurrentDomain.GetAssemblies().ToList(). SelectMany(s => s.GetTypes()).Where(baseClass.IsAssignableFrom); // For debugging only - examine the SQL DDL that Entity Framework is generating // Manipulating this is discouraged. var generatedDDSQL = ((IObjectContextAdapter)context).ObjectContext.CreateDatabaseScript(); // Define which Annotation Attribute we care about (this class!) var annotationAttribute = typeof(SqlAttribute); // Generate a list of concrete classes in my model derived from // Entity class since we follow Table Per Concrete Class (TPC). var concreteClasses = from modelClass in modelClasses where !modelClass.IsAbstract select modelClass; // Iterate through my model's concrete classes (will be mapped to tables) foreach (var concreteClass in concreteClasses) { // Calculate the table name - could get the table name from list of DbContext's properties // to be more correct (but this is sufficient in my case) var tableName = concreteClass.Name + "s"; // Get concrete class's properties that have this annotation var propertiesWithAnnotations = concreteClass.GetProperties().Where(prop => Attribute.IsDefined(prop, annotationAttribute)); foreach (var annotatedProperty in propertiesWithAnnotations) { var columnName = annotatedProperty.Name; var annotationProperties = annotatedProperty.GetCustomAttributes(annotationAttribute, true).ToList(); foreach (SqlAttribute annotationProperty in annotationProperties) { // Generate the appropriate SQL DLL based on the attribute selected switch (annotationProperty.Option) { case SqlOption.Active: // Default value of true plus an index (for my case) sql += ACTIVE_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName); sql += INDEX_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName); break; case SqlOption.GetDate: // GetDate plus an index (for my case) sql += GETDATE_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName); sql += INDEX_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName); break; case SqlOption.Index: // Default for empty annotations for example [Sql()] sql += INDEX_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName); break; case SqlOption.Unique: sql += UNIQUE_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName); break; } // switch } // foreach annotationProperty } // foreach annotatedProperty } // foreach concreteClass // Would have been better not to go through all the work of generating the SQL // if we weren't going to use it, but putting it here makes it easier to follow. if (context.Database.CreateIfNotExists()) context.Database.ExecuteSqlCommand(sql); } // InitializeDatabase } // SqlInitializer } // Namespace
这是我们的背景:
using System; using System.Data.Entity; using System.Data.Entity.ModelConfiguration.Conventions; namespace YourNameSpace { public class MyDBContext : DbContext { protected override void OnModelCreating(DbModelBuilder modelBuilder) { // Only including my concrete classes here as we're following Table Per Concrete Class (TPC) public virtual DbSet<Attendance> Attendances { get; set; } public virtual DbSet<Course> Courses { get; set; } public virtual DbSet<Location> Locations { get; set; } public virtual DbSet<PaymentMethod> PaymentMethods { get; set; } public virtual DbSet<Purchase> Purchases { get; set; } public virtual DbSet<Student> Students { get; set; } public virtual DbSet<Teacher> Teachers { get; set; } // Process the SQL Annotations Database.SetInitializer(new SqlInitializer<MyDBContext>()); base.OnModelCreating(modelBuilder); // Change all datetime columns to datetime2 modelBuilder.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2")); // Turn off cascading deletes modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); } } }
为进一步构build所有这些优秀的响应,我们添加了以下代码,以便从关联的元数据types中获取Index属性。 有关详细信息,请参阅我的博客文章 ,但总结在这里是详细信息。
元数据types是这样使用的:
[MetadataType(typeof(UserAccountAnnotations))] public partial class UserAccount : IDomainEntity { [Key] public int Id { get; set; } // Unique ID sealed class UserAccountAnnotations { [Index("IX_UserName", unique: true)] public string UserName { get; set; } } }
在这个例子中,元数据types是嵌套类,但不一定是,它可以是任何types。 属性匹配只能通过名称完成,因此元数据types只需具有相同名称的属性,并且应用于该types的任何数据注释都应该应用于关联的实体类。 这在原始解决scheme中不起作用,因为它不检查关联的元数据types。 我们使用下面的帮助器方法:
/// <summary> /// Gets the index attributes on the specified property and the same property on any associated metadata type. /// </summary> /// <param name="property">The property.</param> /// <returns>IEnumerable{IndexAttribute}.</returns> IEnumerable<IndexAttribute> GetIndexAttributes(PropertyInfo property) { Type entityType = property.DeclaringType; var indexAttributes = (IndexAttribute[])property.GetCustomAttributes(typeof(IndexAttribute), false); var metadataAttribute = entityType.GetCustomAttribute(typeof(MetadataTypeAttribute)) as MetadataTypeAttribute; if (metadataAttribute == null) return indexAttributes; // No metadata type Type associatedMetadataType = metadataAttribute.MetadataClassType; PropertyInfo associatedProperty = associatedMetadataType.GetProperty(property.Name); if (associatedProperty == null) return indexAttributes; // No metadata on the property var associatedIndexAttributes = (IndexAttribute[])associatedProperty.GetCustomAttributes(typeof(IndexAttribute), false); return indexAttributes.Union(associatedIndexAttributes); }
我发现@highace给出的答案有问题 – 向下迁移对DropIndex使用错误的覆盖。 这是我做的:
- 为了遵守Sql Server对索引列(900字节)的限制,我缩小了模型中几个字段的大小
- 我使用Add-Migration“Add Unique Indexes”添加了迁移
- 我手动将CreateIndex和DropIndex方法添加到迁移。 我使用了覆盖单个索引的索引名。 我使用了覆盖索引跨越多个列的列名称数组
这里是代码与两个覆盖每个方法的例子:
public partial class AddUniqueIndexes : DbMigration { public override void Up() { //Sql Server limits indexes to 900 bytes, //so we need to ensure cumulative field sizes do not exceed this //otherwise inserts and updates could be prevented //http://www.sqlteam.com/article/included-columns-sql-server-2005 AlterColumn("dbo.Answers", "Text", c => c.String(nullable: false, maxLength: 400)); AlterColumn("dbo.ConstructionTypes", "Name", c => c.String(nullable: false, maxLength: 300)); //[IX_Text] is the name that Entity Framework would use by default // even if it wasn't specified here CreateIndex("dbo.Answers", "Text", unique: true, name: "IX_Text"); //Default name is [IX_Name_OrganisationID] CreateIndex("dbo.ConstructionTypes", new string[] { "Name", "OrganisationID" }, unique: true); } public override void Down() { //Drop Indexes before altering fields //(otherwise it will fail because of dependencies) //Example of dropping an index based on its name DropIndex("dbo.Answers", "IX_Text"); //Example of dropping an index based on the columns it targets DropIndex("dbo.ConstructionTypes", new string[] { "Name", "OrganisationID" }); AlterColumn("dbo.ConstructionTypes", "Name", c => c.String(nullable: false)); AlterColumn("dbo.Answers", "Text", c => c.String(nullable: false, maxLength: 500)); }
对于EF7,您可以使用hasIndex()
方法。 我们也可以设置聚簇索引和非聚簇索引。 默认情况下,主键将被聚集。 我们也可以改变这种行为。
supplierItemEntity.HasKey(supplierItem => supplierItem.SupplierItemId).ForSqlServerIsClustered(false); supplierItemEntity.HasIndex(s => new { s.ItemId }).ForSqlServerIsClustered(true);