Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change a clustered index in Entity Framework 6.1 Code First model and apply it to an Azure database

Using the Entity Framework 6.1 code first model, what is the best way to go about changing the clustered index on a table from the default ID to another set of columns. Azure doesn't allow a table without a clustered index.

  public partial class UserProfile 
  {
    public override Guid ID { get; set; }

    [Index( "CI_UserProfiles_UserID", IsClustered = true)]
    public Guid UserID { get; set; }

    [Required]
    public Guid FieldID { get; set; }

    [Required]
    [StringLength(400)]
    public string Value { get; set; }
 }

On the table UserProfiles, ID is already the primary key and clustered index. Adding

[Index( "CI_UserProfiles_UserID", IsClustered = true)] 

to UserID creates this migration:

CreateIndex("dbo.UserProfiles", "UserID", clustered: true, name: "IX_UserProfiles_UserID");

Executing the migration generates the following error:

Cannot create more than one clustered index on table 'dbo.UserProfiles'. Drop the existing clustered index 'PK_dbo.UserProfiles' before creating another.

like image 579
David Sopko Avatar asked Nov 17 '15 20:11

David Sopko


People also ask

How do you change a cluster index?

You cannot alter a clustered index. The only option is to drop it and re-create it with the new column. In your case, you'll probably have to re-create the table with the new clustered index on ID and then copy the data over.

Can we drop and recreate clustered index?

For tables with a clustered index, this value is a pointer to the clustered index key related to the data. When a table's clustered index is dropped using a DROP command and then re-added using a CREATE command, each non-clustered index on the table can be re-created twice.

What is the correct syntax to create a clustered index?

In Object Explorer, expand the table on which you want to create a clustered index. Right-click the Indexes folder, point to New Index, and select Clustered Index.... In the New Index dialog box, on the General page, enter the name of the new index in the Index name box.

How can we change non clustered primary key to clustered primary key?

Taking all this into consideration, you cannot alter an index to make it clustered. However, you can create a new one so long as one does not already exist. Either that or drop the clustered index, create your new clustered index and then create your old clustered index as a non clustered index.


2 Answers

To solve your problem, after you generate your migration file, you must modify the generated code by disabling clustered index for your primary key by assigning false as a value of clustered parameter of PrimaryKey.

After your modifications you must have something like this into your migration file:

CreateTable(
    "dbo.UserProfiles",
    c => new
        {
            Id = c.Guid(nullable: false),
            UserID = c.Guid(nullable: false),
            FieldID = c.Guid(nullable: false),
            Value = c.String(nullable: false, maxLength: 400),
        })
    .PrimaryKey(t => t.Id, clustered: false)
    .Index(t => t.UserID, clustered: true, name: "CI_UserProfiles_UserID");

This is not done in OnModelCreating method by using Fluent API like Manish Kumar said, but in migration file. The file that is created when you use Add-Migration command.

Existing Database

As you say in comments, your database already exist. After executing Add-Migration command, you will have this line on your DbMigration file in your Up() method:

public override void Up()
{
    CreateIndex("dbo.UserProfiles", "UserID", clustered: true, name: "CI_UserProfiles_UserID");
}

You must modify the Up() method to have this code:

public override void Up()
{
    this.Sql("ALTER TABLE dbo.UserProfiles DROP CONSTRAINT \"PK_dbo.UserProfiles\"");
    this.Sql("ALTER TABLE dbo.UserProfiles ADD CONSTRAINT \"PK_dbo.UserProfiles\" PRIMARY KEY NONCLUSTERED (Id);");
    this.CreateIndex("dbo.UserProfiles", "UserID", clustered: true, name: "CI_UserProfiles_UserID");
}

In the code above I assumed that the created clustered index is named PK_dbo.UserProfiles in your database. If not then put at this place the correct name.

like image 102
CodeNotFound Avatar answered Dec 25 '22 01:12

CodeNotFound


This is truly an area where EntityFramwork (Core) had to advance and it still is hard.

So, I could not use IsClustered(false) for my GUID / string Primary keys, for the simple reason, the project having DbContexts was DB - agnostic. So you needed to Add EntityFrameworkCore.SqlServer and IsClustered is available then, and only.

So, my solution was simple. Add no nuget package but this attribute. This ONLY works on EF Core.

I have tested this on SQL. Though, not sure if the other providers would allow this string not having any meaning. (e.g. SQLite does not know clustered indexes)

 p.HasKey(k => k.Id).HasAnnotation("SqlServer:Clustered", false);
like image 41
Egbert Nierop Avatar answered Dec 25 '22 00:12

Egbert Nierop