22

From EF6.1, we have a way of specifying a clustered index on a property

public class Person 
{
  [Index(IsClustered = true, IsUnique = true)]
  public long UserName { get; set; }
}

But this Index attribute does not seem to be in EF Core right now? In EF Core how do you achieve this?

wonea
  • 4,783
  • 17
  • 86
  • 139
Ray
  • 12,101
  • 27
  • 95
  • 137

5 Answers5

41

From the current EF Core documentation - Indexes section:

Data Annotations

Indexes can not be created using data annotations.

But for sure you can specify that via Fluent API (note the extension methods having ForSqlServer prefix which seem to denote SqlServer specific features):

modelBuilder.Entity<Person>()
    .HasIndex(e => e.UserName)
    .IsUnique()
    .ForSqlServerIsClustered();

Update: for EF Core 3.0+ the method is called just IsClustered:

modelBuilder.Entity<Person>()
    .HasIndex(e => e.UserName)
    .IsUnique()
    .IsClustered();

Update: Starting with EF Core 5.0, there is Index data annotation now mentioned in the Indexes documentation link, but it can't be used to specify database specific attributes like clustered (SqlServer specific), so the original answer still applies.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • Is there a MySql specific version for `.ForSqlServerIsClustered();` ? – Zze Mar 06 '18 at 10:44
  • 1
    @Zze Since every provides adds its own extension methods and there are currently [several MySql providers for EF Core](https://learn.microsoft.com/en-us/ef/core/providers/), you should check the one that you are using. I would check for extension methods staring with `ForMySql`. – Ivan Stoev Mar 06 '18 at 11:26
  • I get ...Cannot create more than one clustered index on table 'FooDto'. Drop the existing clustered index 'PK_FooDto' before creating another, because I have an Id field in my FooDto. Is there a way round this? – andrew pate May 30 '19 at 16:16
  • 2
    Fixed it my entity was automatically clustering on its Id field until I added modelBuilder.Entity().HasKey(e => e.Id).ForSqlServerIsClustered(clustered: false); – andrew pate May 30 '19 at 16:39
  • Am getting error like this 'IndexBuilder' does not contain a definition for 'ForSqlServerIsClustered' and no accessible extension method 'ForSqlServerIsClustered' accepting a first argument of type 'IndexBuilder' could be found? – Karthic G Feb 26 '21 at 12:52
  • @KarthicG In EF Core 3.0 the name of the method has been changed. It is mentioned in another post, but I've also updated mine. – Ivan Stoev Feb 26 '21 at 15:02
20

For EF Core 3.0+ You can now use IsClustered:

modelBuilder.Entity<Person>()
.HasIndex(e => e.UserName)
.IsUnique()
.IsClustered();

.ForSqlServerIsClustered() is now marked as obsolete.

Also be aware that if you have a Primary Key on the table you may also need to explicitly remove the clustering on it before you add the clustering on your Username:

modelBuilder.Entity<Person>()
.HasKey(e => e.PersonId)
.IsClustered(false);

modelBuilder.Entity<Person>()
.HasIndex(e => e.UserName)
.IsUnique()
.IsClustered();
sarin
  • 5,227
  • 3
  • 34
  • 63
  • 1
    Thanks saving me time and sharing an alternative for `.ForSqlServerIsClustered()` as on Microsoft's site it's indicated to be obsolete, however, they didn't include info what should be used instead. – Łukasz Sypniewski Mar 20 '20 at 22:24
  • Am getting error like this 'IndexBuilder' does not contain a definition for 'IsClustered' and no accessible extension method 'IsClustered' accepting a first argument of type 'IndexBuilder' could be found? using EF Core 5.0.3 – Karthic G Feb 26 '21 at 13:01
  • @KarthicG IsClustered is still available in EF Core 5. Check your using statements arent clashing and you are bringing in the correct one : https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.sqlserverindexextensions.isclustered?view=efcore-5.0 – sarin Feb 26 '21 at 16:08
  • Just to clarify, the nuget which is needed is Microsoft.EntityFrameworkCore.SqlServer – Casper Skoubo Sep 30 '21 at 07:52
10

In the absence of built-in support, you can use a custom attribute of your own to annotate model properties and apply in OnModelCreating():

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    foreach (var entity in modelBuilder.Model.GetEntityTypes())
    {
        foreach (var prop in entity.GetProperties())
        {
            var attr = prop.PropertyInfo.GetCustomAttribute<IndexAttribute>();
            if (attr != null)
            {
                var index = entity.AddIndex(prop);
                index.IsUnique = attr.IsUnique;
                index.SqlServer().IsClustered = attr.IsClustered;
            }
        }
    }
}

With a simple marker attribute class:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class IndexAttribute : Attribute
{
    public bool IsUnique { get; set; } 
    public bool IsClustered { get; set; } 
}

Then in your model class, just add the attribute to create a secondary index:

public class User
{
    public int UserId { get; set; }
    [Index(IsUnique = true, IsClustered = true)]
    public string Nickname { get; set; }
}
StephenD
  • 3,662
  • 1
  • 19
  • 28
0

Or this works too, say if you wanted to cluster by age...

            modelBuilder
            .Entity<Person>()
            .Property(t => t.Age)
            .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute() { IsClustered = true}));
andrew pate
  • 3,833
  • 36
  • 28
  • Have you try this in ef "core"? – Jie Wang Aug 16 '19 at 14:59
  • Thinking back I also needed to remove clustering on the id field. I think using something like: https://stackoverflow.com/questions/33765908/how-to-change-a-clustered-index-in-entity-framework-6-1-code-first-model-and-app – andrew pate Aug 16 '19 at 15:10
0

I liked the answer of @StephenD. But I would do some improvements. In your answer you need to add '?' on the PropertyInfo. In case there are no custom attributes that will result in null.

var attr = prop.PropertyInfo?.GetCustomAttribute<IndexAttribute>();

I searched the solution for more than one 'Index' attributes on the property. For each entity, group all indexes by names and get all the properties that corresponds to the appropriate index (in my case System.ComponentModel.DataAnnotations.Schema.IndexAttribute). After that add all indexes to the entity.

public class User
{
    [Index("IX_UserIdAndNickname", 1, IsUnique = false)]
    public int UserId { get; set; }

    [Index("IX_UserIdAndNickname", 2, IsUnique = false)]
    [Index(IsUnique = true)]
    public string Nickname { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    foreach (var entity in modelBuilder.Model.GetEntityTypes())
    {
        var indexeNameToAttribute = new Dictionary<string, System.ComponentModel.DataAnnotations.Schema.IndexAttribute>();
        var indexNameToProperties = new Dictionary<string, List<IMutableProperty>>();

        foreach (var prop in entity.GetProperties())
        {
            var attributes = prop.PropertyInfo?.GetCustomAttributes<System.ComponentModel.DataAnnotations.Schema.IndexAttribute>();
            if (attributes != null && attributes.Count() > 0)
            {
                foreach (var attr in attributes)
                {
                    // Index can be without name, so we add our convention
                    var indexName = attr.Name ?? $"IX_{prop.Name}";
                    if(!indexeNameToAttribute.ContainsKey(indexName))
                    {
                       indexeNameToAttribute.Add(indexName, attr);
                    }

                    if (!indexNameToProperties.ContainsKey(indexName))
                    {
                       indexNameToProperties.Add(indexName, new List<IMutableProperty>());
                    }

                    indexNameToProperties[indexName].Add(prop);
                 }
              }
           }

           foreach (var prop in indexNameToProperties)
           {
               var addedIndex = entity.AddIndex(prop.Value, prop.Key);
               var indexAttribute = indexeNameToAttribute[prop.Key];
               addedIndex.IsUnique = indexAttribute.IsUnique;
           }
        }
    }
}
AFish
  • 11
  • 2