Entity Framework Code First Fluent Api: добавление индексов в столбцы

Я запускаю EF 4.2 CF и хочу создавать индексы в определенных столбцах в моих объектах POCO.

В качестве примера можно сказать, что у нас есть этот class сотрудников:

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, и, поскольку есть много сотрудников, было бы неплохо иметь индексированные по соображениям производительности.

Можем ли мы сделать это с беглым api каким-то образом? или, возможно, annotations данных?

Я знаю, что можно выполнить sql-команды примерно так:

 context.Database.ExecuteSqlCommand("CREATE INDEX IX_NAME ON ..."); 

Я бы очень хотел, чтобы избежать необработанного SQL.

я знаю, что этого не существует, но ищет что-то в этом направлении:

 class EmployeeConfiguration : EntityTypeConfiguration { 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; } } 

У кого-нибудь есть идеи о том, как это сделать, или если есть какие-то планы по реализации способа сделать это, первый код?

ОБНОВЛЕНИЕ: Как упоминалось в ответ Роббы, эта функция реализована в версии EF 6.1

    После того, как в EF 4.3 был введен Migrations, вы можете добавлять индексы при изменении или создании таблицы. Вот отрывок из EF 4.3 Code-Based Migrations Walkthrough из блога команды ADO.NET

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

    Это хороший строго типизированный способ добавления индексов, который был тем, что я искал, когда я впервые разместил вопрос.

    Вы можете создать атрибут с индексированным (как вы сказали), который затем выбирается в пользовательском инициализаторе.

    Я создал следующий атрибут:

     [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; } } 

    Затем я создал пользовательский инициализатор, который получил список имен таблиц, созданных для объектов в моем контексте. У меня есть два базовых classа, которые наследуют все мои объекты, поэтому для получения имен таблиц я сделал следующее:

      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) { } } } 

    Я даже добавил, что индексы classов (которые могут иметь несколько столбцов), уникальные ограничения и ограничения по умолчанию все одинаковы. Также приятно, что если вы поместите эти атрибуты в унаследованный class, индекс или ограничение будут применены ко всем classам (таблицам), которые наследуют его.

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

    Чтобы исходить из реакции заморозки, вы можете вручную передать ее в перенос.

    Сначала перейдите в консоль диспетчера пакетов и создайте новую миграцию с add-migration , а затем дайте ей имя. Появится пустая миграция. Придерживайтесь этого:

      public override void Up() { CreateIndex("TableName", "ColumnName"); } public override void Down() { DropIndex("TableName",new[] {"ColumnName"}); } 

    Обратите внимание, что если вы используете поле строки, оно должно быть ограничено до 450 символов.

    Я также изучил это недавно и не нашел другого способа, поэтому я решил создать индексы при посеве базы данных:

     public class MyDBInitializer : DropCreateDatabaseIfModelChanges { 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 (в настоящее время в бета-версии) поддержка IndexAttribute будет аннотировать свойства индекса, что автоматически приведет к (уникальному) индексу в ваших первых переходах кода.

    Для тех, кто использует Entity Framework 6.1+, вы можете сделать следующее с быстрым api:

     modelBuilder .Entity() .Property(t => t.Name) .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute())); 

    Подробнее читайте в документации .

    Ну, я нашел решение онлайн и адаптировал его для удовлетворения моих потребностей здесь:

     [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 : IDatabaseInitializer 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> indexes = new Dictionary>(); 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()); } 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"); } } 

    Затем перегрузите OnModelCreating в свой dbcontext

      protected override void OnModelCreating(DbModelBuilder modelBuilder) { Database.SetInitializer(new IndexInitializer()); base.OnModelCreating(modelBuilder); } 

    Примените атрибут индекса к типу Entity, с помощью этого решения вы можете иметь несколько полей в одном и том же индексе, просто используя одно и то же имя и уникальный.

    Расширение ответа 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)); } 

    расширение на Пете

    я модифицировал 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()); 

    и добавили следующее в метод настройки посева

     new IndexInitializer().InitializeDatabase(context); 

    таким образом атрибуты индекса запускаются каждый раз, когда вы делаете базу данных обновлений.

    Если вы хотите, чтобы эта функция была добавлена ​​в EF, вы можете проголосовать за нее здесь http://entityframework.codeplex.com/workitem/57

    Расширение jwsadler «Аннотации данных» было для нас подходящим. Мы используем annotations, чтобы влиять на обработку classа или свойства и Fluent API для глобальных изменений.

    Наши annotations охватывают индексы (уникальные, а не уникальные) плюс значения по умолчанию для getdate () и (1). Образец кода показывает, как мы применили его к нашей ситуации. Все наши classы наследуются от одного базового classа. Эта реализация делает много предположений, потому что у нас довольно простая модель. Мы используем Entity Framework 6.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 : IDatabaseInitializer 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 Attendances { get; set; } public virtual DbSet Courses { get; set; } public virtual DbSet Locations { get; set; } public virtual DbSet PaymentMethods { get; set; } public virtual DbSet Purchases { get; set; } public virtual DbSet Students { get; set; } public virtual DbSet Teachers { get; set; } // Process the SQL Annotations Database.SetInitializer(new SqlInitializer()); base.OnModelCreating(modelBuilder); // Change all datetime columns to datetime2 modelBuilder.Properties().Configure(c => c.HasColumnType("datetime2")); // Turn off cascading deletes modelBuilder.Conventions.Remove(); } } } 

    Чтобы продолжить работу над этими замечательными ответами, мы добавили следующий код, чтобы активировать атрибут Index из связанного с ним типа метаданных. Для получения полной информации см. Мой блог , но вкратце вот подробности.

    Типы метаданных используются следующим образом:

      [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; } } } 

    В этом примере тип метаданных представляет собой вложенный class, но он не обязательно должен быть любым типом. Сопоставление свойств выполняется только по имени, поэтому тип метаданных должен иметь свойство с тем же именем, и любые annotations данных, применяемые к этому, должны затем применяться к связанному classу сущностей. Это не работает в исходном решении, потому что оно не проверяет соответствующий тип метаданных. Мы применили следующий вспомогательный метод:

     ///  /// Gets the index attributes on the specified property and the same property on any associated metadata type. ///  /// The property. /// IEnumerable{IndexAttribute}. IEnumerable 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. Вот что я сделал:

    1. Чтобы соответствовать ограничениям Sql Server на столбцах индекса (900 байт), я уменьшил размер пары полей в моей модели
    2. Я добавил миграцию с помощью Add-Migration «Добавить уникальные индексы»
    3. Я вручную добавил методы 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); 

    введите описание изображения здесь

    Interesting Posts

    Windows 8 не подключается автоматически к сети Wi-Fi, если соединение Ethernet активно; Маршрутизатор не позволяет Ethernet-устройствам видеть некоторые Wi-Fi-устройства

    Windows застряла в цикле перезагрузки из-за существующей запланированной задачи

    Создание более старого компьютера

    Сделайте изображение из мозаики из 900 изображений

    Как очистить кеш / историю в Windows 7?

    Компьютер застрял в «Проверка данных пула DMI»

    От 32 до 64 бит с большей оперативной памятью или SSD?

    Как копировать / дублировать файл в другой каталог с помощью SFTP?

    Вызов функции PInvoke ” не сбалансировал стек

    Не удалось расширить раздел Windows

    Как выйти из общей папки Windows?

    Когда для доступа к свойствам с помощью «self»

    Получить последние 10 строк очень большого текстового файла> 10 ГБ

    Mocking $ modal в модульных тестах AngularJS

    CSS3 порядок преобразования имеет значение: самая правая операция сначала

    Давайте будем гением компьютера.