Entity Framework Code First Fluent Api: Adding Indexes to columns

前端 未结 15 1247
一生所求
一生所求 2020-11-30 17:43

I\'m running EF 4.2 CF and want to create indexes on certain columns in my POCO objects.

As an example lets say we have this employee class:

public c         


        
相关标签:
15条回答
  • 2020-11-30 18:12

    I've also looked into this recently and found no other way, so I settled with creating indexes when seeding the database:

    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));
        }    
    }   
    
    0 讨论(0)
  • 2020-11-30 18:12

    Extending Tsuushin's answer above to support multiple columns and unique constraints:

        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));
        } 
    
    0 讨论(0)
  • 2020-11-30 18:13

    To build on frozen's response, you can hand code it into a migration yourself.

    First, go to the Package Manager Console and create a new migration with add-migration, then give it a name. A blank migration will appear. Stick this in:

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

    Note that if you're using a string field it needs to be capped to a length of 450 chars as well.

    0 讨论(0)
  • 2020-11-30 18:14

    If you want this feature added to EF then you can vote for it here http://entityframework.codeplex.com/workitem/57

    0 讨论(0)
  • 2020-11-30 18:17

    Well i found a solution online and adapted it to fit my needs here it is:

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

    Then overload OnModelCreating in your dbcontext

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

    Apply the index attribute to your Entity type, with this solution you can have multiple fields in the same index just use the same name and unique.

    0 讨论(0)
  • 2020-11-30 18:18

    I discovered a problem with the answer @highace gave - the down migration uses the wrong override for DropIndex. Here is what I did:

    1. To comply with Sql Server's limitation on index columns (900 bytes) I reduced the size of a couple of fields in my model
    2. I added the migration using Add-Migration "Add Unique Indexes"
    3. I manually added the CreateIndex and DropIndex methods to the migration. I used the override that takes the index name for the single column index. I used the override that takes an array of column names where the index spans more than one column

    And here is the code with examples of both overrides of each method:

    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));
    }
    
    0 讨论(0)
提交回复
热议问题