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

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.

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.

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

You need to remove the existing clustered index from your current PK 'ID' which is created by default for any "KEY" property in code first. It can be done using fluent API:
.Primarykey(x=>x.ID,clustered:false)
Once existing clustered index is removed from ID, your migration to add the clustered index on UserID should run smoothly.

After the migration file is created, modify the generated code, disabling the clustered index for the primary key by setting the clustered property to false.
Being that Azure does not allow a table without a clustered index, and there is no utility in SQL Server to 'change' a clustered index on a table, it is necessary create a new table with the clustered index and migrate the existing data to it. The code below renames the original table, migrates the data to the new table that was created with the new clustered index and drops the original table.
RenameTable("dbo.UserProfiles", "UserProfiles_PreMigrate");
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");
Sql(#"
INSERT [dbo].[UserProfiles]
(ID,
UserID,
FieldID,
Value)
SELECT
ID,
UserID,
FieldID,
Value
FROM dbo.UserProfiles_PreMigrate
");
DropTable("UserProfiles_PreMigrate");
Any existing table constraints will be lost in this operation, so it will be necessary to recreate and indexes,foreign keys, etc on the table.

Related

Entity Framework Code First Migrations Guid NewId Instead Of NewSequentialId

We're using EF6 with code first migrations pointing to an Azure SQL. We've started into using some Guid for both primary keys as well as alongside int primary keys.
Primary Key:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
Alongside int PK:
[Index, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid PolymorphicId { get; set; }
When I generate the migration, I get as follows (respectively):
Id = c.Guid(nullable: false, identity: true),
PolymorphicId = c.Guid(nullable: false, identity: true),
Expectation: SQL generated to have default values of newsequentialid
Actually Happening:
[Id] [uniqueidentifier] NOT NULL DEFAULT newid(),
[PolymorphicId] [uniqueidentifier] NOT NULL DEFAULT newid(),
How do I make my EF migrations generate with newsequentialid instead of newid? Everything I've looked up online says that they should be generating with newsequentialid.
When targeting Azure, SqlServerMigrationSqlGenerator will default to "newid()". When targeting on-premesis Sql Server 2005 or later, it will default to "newsequentialid()".
Source: GitHub SqlServerMigrationSqlGenerator.cs
Optional fix: Create a custom SqlGenerator, inheriting SqlServerMigrationSqlGenerator, override GuidColumnDefault
Optional fix: As posted by JFM, modify the generated migration file, setting the defaultValueSql
You could try setting the sql used by the sql server column key generation in your migration script, I beleive it should look somethnig similar to this:
Id = c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()")

How do I tell Entity (Code First) to not send the Key ID field to the database?

My code:
Models.Resource r = new Models.Resource();
r.Name = txtName.Text;
r.ResourceType = resTypes.Find(rt => rt.Name == "Content");
r.ResourceContents.Add(_resourceContent.Find(rc => rc.ID == _resourceContentID));
ctx.Resource.Add(r);
ctx.SaveChanges();
ctx.SaveChanges() causes the error:
Cannot insert explicit value for identity column in table 'Resources' when IDENTITY_INSERT is set to OFF.
Looking at what's being sent to SQL:
ADO.NET:Execute NonQuery "INSERT [dbo].[Resources]([ID], [Name], [Description], [IsOnFile],
[ContentOwnerAlias], [ContentOwnerGroup], [ResourceTypes_ID])
VALUES (#0, #1, #2, #3, #4, #5, NULL)"
My POCO Resource has ID as a Key:
public partial class Resource
{
public Resource()
{
}
[Key]
public int ID { get; set; }
And my Map code:
public class ResourceMap : EntityTypeConfiguration<Resource>
{
public ResourceMap()
{
// Primary Key
this.HasKey(t => t.ID);
How do I tell Entity to not send the Key ID field to the database?
If your PK is generated by the database (like an identity) you have to configure it in your Map.
public class ResourceMap : EntityTypeConfiguration<Resource>
{
public ResourceMap()
{
// Primary Key
this.HasKey(t => t.ID);
this.Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
You do not need the HasKey(t => t.ID) Fluent API mapping or the [Key] Data Attribute because by convention EF will assume that an integer field named ID is the key and is database generated.
As an aside, I'd recommend that when you are not following conventions you should choose one method or the other - otherwise you are repeating yourself and when you want to change something you need to change it in 2 places.
I'm not sure why the field in the database isn't already database generated - maybe when you define the field via the fluent api you have to specify that too. What I do know is that in order to make EF change a key field to be database generated you will need to drop the table.
So - rollback the migration or drop the table / database, then remove the data attribute, remove the fluent mapping and recreate.
This issue is currently on a "backlog" in the entity framework. If you want to vote for it you can do that here: Migrations: does not detect changes to DatabaseGeneratedOption
Other References:
Identity problem in EF
Switching Identity On/Off With A Custom Migration Operation

EF5 Database Migrations: How to move data

I'm using EF5 code first and now I need to update my database so I enabled database migrations and added a migration but the generated code was not what I needed. This is the code:
public override void Up()
{
CreateTable(
"dbo.HistoricalWeightEntities",
c => new
{
PatientMedicalDataId = c.Guid(nullable: false),
Id = c.Guid(nullable: false),
Weight = c.Single(nullable: false),
Date = c.DateTime(nullable: false),
})
.PrimaryKey(t => new { t.PatientMedicalDataId, t.Id })
.ForeignKey("dbo.PatientMedicalDataEntities", t => t.PatientMedicalDataId, cascadeDelete: true)
.Index(t => t.PatientMedicalDataId);
AddColumn("dbo.PatientDataEntities", "PatientDataFilePath", c => c.String());
//Here I need to move data from the old Weight column to the Weight column on the newly
//created table and create the id (Guid) and the foreing key before the old
//column is dropped
DropColumn("dbo.PatientMedicalDataEntities", "Weight");
}
What I need to do is to add some sql script that move data from the 'Weight' column in the dbo.PatientMedicalDataEntities to the Weight column in the newly created table dbo.HistoricalWeightEntities and also insert the Id value (key) which is a Guid and the corresponding foreign key before the column is dropped.
Can somebody show me how to do this is sql?
Thank you in advance
It should be something like that (donnow what you wanna do with the Date column)
Sql("INSERT INTO HistoricalWeightEntities(Id, Weight, PatientMedicalDataId) "+
"SELECT newid(), Weight, <theForeignKeyColumn> from PatientMedicalDataEntities");

Entity Framework auto incrementing field, that isn't the Id

I know this isn't the most ideal solution, but I need to add an auto incrementing field to one of my EF Code First objects. This column id NOT the Id, which is a guid.
Is there anyway for me to define the auto incrementing field in code, or would creating the column myself and defining in the DB that its auto incrementing work?
You can annotate that property with DatabaseGenerated(DatabaseGeneratedOption.Identity). EF allows only single identity column per table.
public class Foo
{
[Key]
public Guid Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Bar { get; set; }
}
Old post thought I would share what I found with Entity Framework 6.1.3.
I created a simple data layer library using C# and .NET Framework 4.6.1, added a simple repository/service class, a code first context class and pointed my web.config file to a local SQL Express 2014 database.
In the entity class I added the following attribute constructor to the Id column:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
Then I created a new migration by typing the following in Visual Studio 2015 Package Manager:
Add-Migration
Give the migration a name and then wait for the DbMigtation class to be created. Edit the class and add the following CreateTable operation:
CreateTable(
"dbo.Article",
c => new
{
Id = c.Guid(nullable: false, identity: true),
Title = c.String(),
Content = c.String(),
PublishedDate = c.DateTime(nullable: false),
Author = c.String(),
CreateDate = c.DateTime(nullable: false),
})
.PrimaryKey(t => t.Id);
}
The above table is an example the key point here is the following builder annotation:
nullable: false, identity: true
This tells EF to specifiy the column as not nullabe and you want to set it as an identity column to be seeded by EF.
Run the migration again with the following command:
update-database
This will run the migration class dropping the table first (Down() method) then creating the table (Up() method).
Run your unit tests and/or connect to the database and run a select query you should see your table in its new form, add some data excluding the Id column and you should see new Guid's (or whatever data type your choose) to be generated.
For those stumbling onto this question for EF Core, you can now create an auto-incrementing column with your model builder as follows:
builder.Entity<YourEntity>().Property(e => e.YourAutoIncrementProperty).UseNpgsqlIdentityAlwaysColumn();
Reference: https://www.npgsql.org/efcore/modeling/generated-properties.html

Entity Framework code first unique column

I am using Entity Framework 4.3 and using Code Fist.
I have a class
public class User
{
public int UserId{get;set;}
public string UserName{get;set;}
}
How do I tell Entity Framework that UserName has to be unique when creating database table?
I would prefer to use data anotations instead of configuration file if possible.
In Entity Framework 6.1+ you can use this attribute on your model:
[Index(IsUnique=true)]
You can find it in this namespace:
using System.ComponentModel.DataAnnotations.Schema;
If your model field is a string, make sure it is not set to nvarchar(MAX) in SQL Server or you will see this error with Entity Framework Code First:
Column 'x' in table 'dbo.y' is of a type that is invalid for use as a key column in an index.
The reason is because of this:
SQL Server retains the 900-byte limit for the maximum total size of all index key columns."
(from: http://msdn.microsoft.com/en-us/library/ms191241.aspx )
You can solve this by setting a maximum string length on your model:
[StringLength(450)]
Your model will look like this now in EF CF 6.1+:
public class User
{
public int UserId{get;set;}
[StringLength(450)]
[Index(IsUnique=true)]
public string UserName{get;set;}
}
Update:
if you use Fluent:
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
// ....
Property(x => x.Name).IsRequired().HasMaxLength(450).HasColumnAnnotation("Index", new IndexAnnotation(new[] { new IndexAttribute("Index") { IsUnique = true } }));
}
}
and use in your modelBuilder:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// ...
modelBuilder.Configurations.Add(new UserMap());
// ...
}
Update 2
for EntityFrameworkCore see also this topic: https://github.com/aspnet/EntityFrameworkCore/issues/1698
Update 3
for EF6.2 see: https://github.com/aspnet/EntityFramework6/issues/274
Update 4
ASP.NET Core Mvc 2.2 with EF Core:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Unique { get; set; }
EF doesn't support unique columns except keys. If you are using EF Migrations you can force EF to create unique index on UserName column (in migration code, not by any annotation) but the uniqueness will be enforced only in the database. If you try to save duplicate value you will have to catch exception (constraint violation) fired by the database.
In EF 6.2 using FluentAPI, you can use HasIndex()
modelBuilder.Entity<User>().HasIndex(u => u.UserName).IsUnique();
From your code it becomes apparent that you use POCO. Having another key is unnecessary: you can add an index as suggested by juFo.
If you use Fluent API instead of attributing UserName property your column annotation should look like this:
this.Property(p => p.UserName)
.HasColumnAnnotation("Index", new IndexAnnotation(new[] {
new IndexAttribute("Index") { IsUnique = true }
}
));
This will create the following SQL script:
CREATE UNIQUE NONCLUSTERED INDEX [Index] ON [dbo].[Users]
(
[UserName] ASC
)
WITH (
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
IGNORE_DUP_KEY = OFF,
DROP_EXISTING = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
) ON [PRIMARY]
If you attempt to insert multiple Users having the same UserName you'll get a DbUpdateException with the following message:
Cannot insert duplicate key row in object 'dbo.Users' with unique index 'Index'.
The duplicate key value is (...).
The statement has been terminated.
Again, column annotations are not available in Entity Framework prior to version 6.1.
Note that in Entity Framework 6.1 (currently in beta) will support the IndexAttribute to annotate the index properties which will automatically result in a (unique) index in your Code First Migrations.
Solution for EF4.3
Unique UserName
Add data annotation over column as:
[Index(IsUnique = true)]
[MaxLength(255)] // for code-first implementations
public string UserName{get;set;}
Unique ID
,
I have added decoration [Key] over my column and done.
Same solution as described here: https://msdn.microsoft.com/en-gb/data/jj591583.aspx
IE:
[Key]
public int UserId{get;set;}
Alternative answers
using data annotation
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("UserId")]
using mapping
mb.Entity<User>()
.HasKey(i => i.UserId);
mb.User<User>()
.Property(i => i.UserId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.HasColumnName("UserId");