Entity Framework change Key Type - entity-framework

I have created a model with various values but stupidly used a GUID for my key, I am currently attempting to change that to an Int but am getting an error when I do so.
I have run the enable migration command:
Enable-Migrations -Force -ContextTypeName project.Models.MyContext
This creates the migration I would expect but when I run:
Update-Database -Force
The error I'm getting is:
Operand type clash: uniqueidentifier is incompatible with int
I don't care about the data currently contained within the database since it is just using a SQL Server Express database just now, but I would prefer to find a way to migrate this instead of just having to drop the DB altogether, what's the best way to do this?
I have already got
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
in Global.asax.

I would expect that the generated migration is using AlterColumn to try and change the type of the field from guid to int. This is not possible, so you'll need to modify the generated migration yourself:
Assuming your table is dbo.People and the key is called Id, you probably have this at the moment:
DropPrimaryKey("dbo.People");
AlterColumn("dbo.People", "Id", c => c.Int(nullable: false, identity: true));
AddPrimaryKey("dbo.People", "Id");
Change it to:
DropPrimaryKey("dbo.People");
DropColumn("dbo.People", "Id");
AddColumn("dbo.People", "Id", c => c.Int(nullable: false, identity: true));
AddPrimaryKey("dbo.People", "Id");
Note that if you've got this key referenced elsewhere, this technique will not work if you've got any data present, as the keys are all being regenerated.

Update for EF Core generated migrations:
dotnet : System.Data.SqlClient.SqlException: Operand type clash: int is incompatible with uniqueidentifier
change
migrationBuilder.AlterColumn<Guid>(
name: "VATID",
schema: "catalogue",
table: "Products",
nullable: false,
oldClrType: typeof(int));
into
migrationBuilder.DropColumn(
name: "VATID",
schema: "catalogue",
table: "Products");
migrationBuilder.AddColumn<Guid>(
name: "VATID",
schema: "catalogue",
table: "Products",
nullable: false);
Of course, this will destroy your data for the certain column. But they obviously cannot be converted into GUID.

I am trying to add something to the selected answer since I don't have enough reputation to add comment there :
Please also add code to drop and recreate indexes if any else it will fail to drop column. e.g.
DropPrimaryKey("dbo.People");
DropIndex("IX_...") // If exists before
DropColumn("dbo.People", "Id");
AddColumn("dbo.People", "Id", c => c.Int(nullable: false,
identity: true));
AddPrimaryKey("dbo.People", "Id");
CreateIndex("IX_...", unique:(true/false)) // If existed before
The AlterColumn will Drop and Add keys (primary,foreign etc) but will not touch indexes. I have faced this.

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 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.
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.

EF 6 Code-Based Migration: Add not null property to existing entity

I want to add a not-null, foreing key column to an existing table.
Environment: EF 6,Code-First, Code-Based Migration
//Code from Migration class for new entity Currency
CreateTable("dbo.Currency",
c => new
{
CurrencyID = c.Int(nullable: false, identity: true),
Code = c.String(nullable: false, maxLength: 3, fixedLength: true, unicode: false),
Denomination = c.String(nullable: false, maxLength: 50, unicode: false),
})
.PrimaryKey(t => t.CurrencyID);
AddColumn("dbo.Collection", "CurrencyID", c => c.Int(nullable: false));
//Code from Seed() method in Configuration class
context.Currencies.AddOrUpdate(
new Currency
{
Code = "USD",
Denomination = "Dollar"
}
);
//Here i get an exception. Collection is the existing table
context.Database.ExecuteSqlCommand( "update collection set CurrencyID = 1 );
Exception message:
The UPDATE statement conflicted with the FOREIGN KEY constraint
"FK_dbo.Collection_dbo.Currency_CurrencyID". The conflict occurred in
table "dbo.Currency", column 'CurrencyID'.
Problem solved, here are enumerated by order the steps i followed:
Change the foreign key property mapping to Not Required
Seed only the primary key values
Update-Database
Change back the property to Required
Add new migration and seed the values for foreign key column
Update-Database

How to avoid DROP DEFAULT statements with Doctrine 2 Migrations diff on first run?

I had an existing PostgreSQL database with a table created like this:
CREATE TABLE product (id SERIAL PRIMARY KEY, name VARCHAR(100) DEFAULT NULL)
This table is described in a YML Doctrine2 file within a Symfony2 project:
Acme\DemoBundle\Entity\Product:
type: entity
table: product
fields:
id:
id: true
type: integer
nullable: false
generator:
strategy: SEQUENCE
name:
type: string
length: 100
nullable: true
When I run for the first time the Doctrine Migrations diff task, I should get a versioning file with no data in the up and down methods. But what I get instead is this :
// ...
class Version20120807125808 extends AbstractMigration
{
public function up(Schema $schema)
{
// this up() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != "postgresql");
$this->addSql("ALTER TABLE product ALTER id DROP DEFAULT");
}
public function down(Schema $schema)
{
// this down() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != "postgresql");
$this->addSql("CREATE SEQUENCE product_id_seq");
$this->addSql("SELECT setval('product_id_seq', (SELECT MAX(id) FROM product))");
$this->addSql("ALTER TABLE product ALTER id SET DEFAULT nextval('product_id_seq')");
}
}
Why are differences detected? How can I avoid this? I tried several sequence strategies with no success.
A little update on this question.
Using Doctrine 2.4, the solution is to use the IDENTITY generator strategy :
Acme\DemoBundle\Entity\Product:
type: entity
table: product
id:
type: integer
generator:
strategy: IDENTITY
fields:
name:
type: string
length: 100
nullable: true
To avoid DROP DEFAULT on fields that have a default value in the database, the default option on the field is the way to go. Of course this can be done with lifecycle callbacks, but it's necessary to keep the default value in the database if this database is used by other apps.
For a "DEFAULT NOW()" like default value, the solution is the following one:
Acme\DemoBundle\Entity\Product:
type: entity
table: product
id:
type: integer
generator:
strategy: IDENTITY
fields:
creation_date:
type: datetime
nullable: false
options:
default: CURRENT_TIMESTAMP
Doctrine 2.0 does not support the SQL DEFAULT keyword, and will always try to drop a postgres default value.
I have found no solution to this problem, I just let doctrine handle the sequences itself.
This is a opened bug registered here :
http://www.doctrine-project.org/jira/browse/DBAL-903

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