Entity Framework Code First Fluent Api: добавление индексов в столбцы - PullRequest
61 голосов
/ 25 ноября 2011

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

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

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? или, возможно, аннотации данных?

Я знаю, что можно выполнять команды 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; }
}

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

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

Ответы [ 14 ]

3 голосов
/ 22 января 2012

Расширение ответа Цуушина выше для поддержки нескольких столбцов и уникальных ограничений:

    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));
    } 
2 голосов
/ 15 июня 2013

Для дальнейшего развития всех этих замечательных ответов мы добавили следующий код, чтобы позволить атрибуту 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; }
            }
       }

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

/// <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);
    }
1 голос
/ 22 июня 2016

Для EF7 вы можете использовать метод hasIndex(). Мы можем установить кластерный и некластеризованный индекс, а также. По умолчанию первичный ключ будет кластеризован. Мы можем изменить это поведение тоже.

supplierItemEntity.HasKey(supplierItem => supplierItem.SupplierItemId).ForSqlServerIsClustered(false);

supplierItemEntity.HasIndex(s => new { s.ItemId }).ForSqlServerIsClustered(true);

enter image description here

1 голос
/ 13 августа 2013

Я обнаружил проблему с ответом, который дал @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));
}
...