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
Related
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.
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.
I am trying to initially create a sql server db schema for my azure application with code first migrations (as described here).
The basic creation of my tables works, however the azure (entitydata) specific things don't. There is no default value for CreatedAt, no UpdatedAt Trigger and also a problem with a clustered index. This happens to all of my tables. Thats what i am doing (shows the problem for 1 Table Account):
public Configuration()
{
AutomaticMigrationsEnabled = false;
SetSqlGenerator("System.Data.SqlClient", new EntityTableSqlGenerator());
}
var migrator = new DbMigrator(new Configuration());
migrator.Update();
The up-Method in my DBMigration-Class looks as follows:
CreateTable(
"dbo.Accounts",
c => new
{
Id = c.String(nullable: false, maxLength: 128),
Username = c.String(nullable: false, maxLength: 30),
Salt = c.Binary(),
SaltedAndHashedPassword = c.Binary(),
MailAddress = c.String(),
FacebookId = c.String(),
Version = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
CreatedAt = c.DateTimeOffset(nullable: false, precision: 7),
UpdatedAt = c.DateTimeOffset(precision: 7),
Deleted = c.Boolean(nullable: false),
})
.PrimaryKey(t => t.Id)
.Index(t => t.CreatedAt, clustered: true);
I thought, that the EntityTableSqlGenerator should do these azure specific things, however it seems that it changes nothing.
I had the same issue few days ago and solved it temporarily as described below: I think Microsoft EF team should solve it permanently.
According to my temporary solution, you need to make some modifications before execute your migration file.
First, you need to cancel clustered index decalarations for CreatedAt properties for all entities which is inheriting from EntityData base class.
Id, CreatedAt and Deleted fields need to be decorated with default value declarations.
You can add following lines below each create commands in your migration files.
You can get additional ideas from here
Want to create index by EmployeeCardNumber (text) in descending order.
So the sql script should be like:
ALTER TABLE dbo.Employees ADD CONSTRAINT
IX_Employees_EmplyeeCardNumber UNIQUE CLUSTERED
(
EmployeeCardNumber DESC
)
In IndexAttribute, there is an Order property, which is an integer???
Here is my fluent api:
var indexAttr = new IndexAttribute("IX_Employees_EmplyeeCardNumber")
{
IsClustered = true,
IsUnique = true,
Order = 1 // probably should be removed since we are using a composite key.
};
Property(c => c.EmployeeCardNumber)
.HasColumnAnnotation("Index", new IndexAnnotation(indexAttr))
.HasMaxLength(8)
.IsRequired();
So the question is, how to define "DESC" in this code first approach?
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");