2012-06-18 36 views
4

我已更改实体中的主键。当我尝试在SQl Azure上运行迁移时,它失败,“此版本的SQL Server不支持”没有聚簇索引的表“,请创建聚簇索引并重试。在Sql Azure中使用EF迁移时更改主键

public override void Up() 
{ 
    DropPrimaryKey("User", new[] { "Id" }); 
    AddPrimaryKey("User", "Username"); 
} 

我该如何解决这个问题?我唯一能想到的就是创建我自己的ChangePrimaryKey方法,该方法以某种方式默默地创建一个临时表并使用正确的主键,复制数据,然后替换原始数据。

回答

1

基于解决方法的Avkash建议。从https://stackoverflow.com/a/317864/53970得到一些想法,我想出了一个存储过程,将改变桌子上的主键。有些表结构可能存在一些问题(目前为止我工作正常),它不处理触发器。但它会复制结构,数据,约束(主要,外部,检查)和索引。 张贴在这里,以防别人碰到同样的砖墙我做过。

create procedure dbo.ChangePK 
    @src sysname, 
    @pklist nvarchar(4000), --comma list of primary key fields 
    @skipfinalrename bit = 1 --set to not perform anything destructive on src table or related tables (for testing) 
as 

set nocount on 

declare @tmpPrefix nvarchar(10) 
set @tmpPrefix = 'tmp_' 

declare @dest sysname 
set @dest = 'tmpCopy' 

declare @sql nvarchar(max) 
set @sql = '' 
--create table script 
select @sql = @sql + ' IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N''' + @dest + ''') AND OBJECTPROPERTY(id, N''IsTable'') = 1) drop table [' + @dest + ']; create table [' + @dest + '] (' + o.list + ')' + CASE WHEN tc.Constraint_Name IS NULL THEN '' ELSE 'ALTER TABLE ' + @dest + ' ADD CONSTRAINT ' + @tmpPrefix + tc.Constraint_Name + ' PRIMARY KEY ' + ' (' + @pklist + ')' END + ';' 
from sysobjects so 
cross apply 
    (SELECT 
     ' ['+column_name+'] ' + 
     data_type + case data_type 
       when 'sql_variant' then '' 
       when 'text' then '' 
       when 'decimal' then '(' + cast(numeric_precision_radix as varchar) + ', ' + cast(numeric_scale as varchar) + ')' 
       else coalesce('('+case when character_maximum_length = -1 then 'MAX' else cast(character_maximum_length as varchar) end +')','') end + ' ' + 
     case when exists ( 
     select id from syscolumns 
     where object_name(id)=so.name 
     and name=column_name 
     and columnproperty(id,name,'IsIdentity') = 1 
     ) then 
     'IDENTITY(' + 
     cast(ident_seed(so.name) as varchar) + ',' + 
     cast(ident_incr(so.name) as varchar) + ')' 
     else '' 
     end + ' ' + 
     (case when IS_NULLABLE = 'No' then 'NOT ' else '' end) + 'NULL ' + 
      case when information_schema.columns.COLUMN_DEFAULT IS NOT NULL THEN 'DEFAULT '+ information_schema.columns.COLUMN_DEFAULT ELSE '' END + ', ' 

    from information_schema.columns where table_name = so.name 
    order by ordinal_position 
    FOR XML PATH('')) o (list) 
cross apply 
    (SELECT 
     ', ['+column_name+'] ' 

    from information_schema.columns where table_name = so.name 
    order by ordinal_position 
    FOR XML PATH('')) c (columnlist) 
left join 
    information_schema.table_constraints tc 
on tc.Table_name    = so.Name 
AND tc.Constraint_Type = 'PRIMARY KEY' 
cross apply 
    (select '[' + Column_Name + '], ' 
    FROM  information_schema.key_column_usage kcu 
    WHERE  kcu.Constraint_Name  = tc.Constraint_Name 
    ORDER BY 
     ORDINAL_POSITION 
    FOR XML PATH('')) j (list) 
where xtype = 'U' 
AND name  NOT IN ('dtproperties') 
and name = @src 

print 'create' 
print @sql 
exec sp_executesql @sql 

--now the inserts 
set @sql = '' 
select @sql = @sql + ' set identity_insert [' + @dest + '] on; insert into [' + @dest + '] (' + STUFF(c.columnlist,1,2,'') + ') select ' + STUFF(c.columnlist,1,2,'') + ' from [' + @src + '] ; set identity_insert [' + @dest + '] off;' 
from sysobjects so 
cross apply 
    (SELECT 
     ', ['+column_name+'] ' 

    from information_schema.columns where table_name = so.name 
    order by ordinal_position 
    FOR XML PATH('')) c (columnlist) 
left join 
    information_schema.table_constraints tc 
on tc.Table_name    = so.Name 
AND tc.Constraint_Type = 'PRIMARY KEY' 
cross apply 
    (select '[' + Column_Name + '], ' 
    FROM  information_schema.key_column_usage kcu 
    WHERE  kcu.Constraint_Name  = tc.Constraint_Name 
    ORDER BY 
     ORDINAL_POSITION 
    FOR XML PATH('')) j (list) 
where xtype = 'U' 
AND name  NOT IN ('dtproperties') 
and name = @src 

print 'data' 
print @sql 
exec sp_executesql @sql 

--now the foreign keys 
set @sql = '' 
select @sql = @sql + case when tc.Constraint_Name is null then '--no foreign keys' else 'ALTER TABLE ' + @dest + ' WITH CHECK ADD CONSTRAINT ' + @tmpPrefix + tc.Constraint_Name + ' FOREIGN KEY ' + ' (' + STUFF(fk1.list,1,2,'') + ') REFERENCES [' + rctc.table_name + '] (' + STUFF(fk2.list,1,2,'') + ');' end 
from sysobjects so 
left join 
    information_schema.table_constraints tc on tc.Table_name = so.Name 
           AND tc.Constraint_Type = 'FOREIGN KEY' 
left join INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rcc on tc.constraint_name = rcc.constraint_name 
left join INFORMATION_SCHEMA.table_constraints rctc on rcc.unique_constraint_name = rctc.constraint_name 
cross apply 
    (select ', [' + Column_Name + ']' 
    FROM  INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu 
    WHERE  kcu.Constraint_Name  = tc.Constraint_Name 
    ORDER BY 
     ORDINAL_POSITION 
    FOR XML PATH('')) fk1 (list) 
cross apply 
    (select ', [' + kcu.Column_Name + ']' 
    FROM  INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc 
    JOIN  INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu on rc.unique_constraint_name = kcu.constraint_name 
    WHERE  rc.Constraint_Name  = tc.Constraint_Name 
    ORDER BY 
     ORDINAL_POSITION 
    FOR XML PATH('')) fk2 (list) 

where xtype = 'U' 
and name = @src 

print 'foreign keys' 
print @sql 
exec sp_executesql @sql 

--now the unique keys 
set @sql = '' 
select @sql = @sql + case when tc.Constraint_Name is null then '--no unique keys' else 'ALTER TABLE ' + @dest + ' WITH CHECK ADD CONSTRAINT ' + @tmpPrefix + tc.Constraint_Name + ' UNIQUE NONCLUSTERED ' + ' (' + STUFF(fk1.list,1,2,'') + ');' end 
from sysobjects so 
left join 
    information_schema.table_constraints tc on tc.Table_name = so.Name 
           AND tc.Constraint_Type = 'UNIQUE' 
cross apply 
    (select ', [' + Column_Name + ']' 
    FROM  INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu 
    WHERE  kcu.Constraint_Name  = tc.Constraint_Name 
    ORDER BY 
     ORDINAL_POSITION 
    FOR XML PATH('')) fk1 (list) 

where xtype = 'U' 
and name = @src 

print 'unique keys' 
print @sql 
exec sp_executesql @sql 


--now check constraints 
set @sql = '' 
select @sql = @sql + case when tc.Constraint_Name is null then '--no check constraints' else 'ALTER TABLE ' + @dest + ' WITH CHECK ADD CONSTRAINT ' + @tmpPrefix + tc.Constraint_Name + ' CHECK ' + ' (' + cc.check_clause + ');' end 
from sysobjects so 
left join 
    information_schema.table_constraints tc on tc.Table_name = so.Name 
           AND tc.Constraint_Type = 'CHECK' 
left join INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc on  cc.Constraint_Name  = tc.Constraint_Name 

where xtype = 'U' 
and name = @src 

print 'check constraints' 
print @sql 
exec sp_executesql @sql 

if (@skipfinalrename = 1) 
    return 

set xact_abort on 

--now we start affecting the src table 
begin tran 
--drop fk constraints on src referencing current primary key 
set @sql = '' 
select @sql = @sql + case when tc.Constraint_Name is null then '--no fk constraints to drop' else 'ALTER TABLE ' + rctc.table_name + ' DROP CONSTRAINT ' + rc.Constraint_Name + ';' end 
from sysobjects so 
left join information_schema.table_constraints tc on tc.Table_name = so.Name 
        AND tc.constraint_type = 'PRIMARY KEY' 
left join INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc on tc.constraint_name = rc.unique_constraint_name 
left join information_schema.table_constraints rctc on rc.constraint_name = rctc.constraint_name 
where xtype = 'U' 
and name = @src 

--create fk constraints on dest referencing new primary key 
declare @sql2 nvarchar(max) 
set @sql2 = '' 
select @sql2 = @sql2 + case when tc.Constraint_Name is null then '--no fk constraints to drop' else 'ALTER TABLE [' + rctc.table_name + '] WITH CHECK ADD CONSTRAINT ' + @tmpPrefix + rc.Constraint_Name + ' FOREIGN KEY ' + ' (' + STUFF(fk1.list,1,2,'') + ') REFERENCES [' + @dest + '] (' + STUFF(fk2.list,1,2,'') + ');' end 
from sysobjects so 
left join information_schema.table_constraints tc on tc.Table_name = so.Name 
        AND tc.constraint_type = 'PRIMARY KEY' 
left join INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc on tc.constraint_name = rc.unique_constraint_name 
left join information_schema.table_constraints rctc on rc.constraint_name = rctc.constraint_name 
cross apply 
    (select ', [' + Column_Name + ']' 
    FROM  INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu 
    WHERE  kcu.Constraint_Name  = rc.Constraint_Name 
    ORDER BY 
     ORDINAL_POSITION 
    FOR XML PATH('')) fk1 (list) 
cross apply 
    (select ', [' + kcu.Column_Name + ']' 
    FROM  INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc1 
    JOIN  INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu on rc1.unique_constraint_name = kcu.constraint_name 
    WHERE  rc1.Constraint_Name  = rc.Constraint_Name 
    ORDER BY 
     ORDINAL_POSITION 
    FOR XML PATH('')) fk2 (list) 
where xtype = 'U' 
and name = @src 


---- 

print 'create new ref fk' 
print @sql2 
exec sp_executesql @sql2 

print 'drop original ref fk' 
print @sql 
exec sp_executesql @sql 

--now we can create the index sql 
set @sql2 = '' 
select @sql2 = @sql2 + ' IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N''' + @src + ''') AND name = N''' + i.name + ''') 
CREATE ' + i.type_desc COLLATE Latin1_General_CS_AS + ' INDEX ' + i.name + ' ON [' + @src + '] (
    ' + STUFF(ix.list,1,2,'') + ' 
) 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]; 
' 
from sys.tables as t 
inner join sys.indexes as i on t.[object_id] = i.[object_id] 
cross apply 
    (select ', [' + ac.name + ']' 
    FROM  sys.index_columns ic 
    inner join sys.all_columns as ac on ic.[object_id] = ac.[object_id] and ic.[column_id] = ac.[column_id] 
    WHERE  ic.[object_id] = i.[object_id] and ic.[index_id] = I.[index_id] 
    ORDER BY 
     key_ordinal 
    FOR XML PATH('')) ix (list) 
where 
t.name = @src 

--now drop the original table 
set @sql = 'drop table [' + @src + '];' 
print 'drop original original' 
print @sql 
exec sp_executesql @sql 

--now rename the constraints (remove leading tmp_ in effect) 
set @sql='' 
select @sql = @sql + ' exec sp_rename ''' + tc.constraint_name + ''', ''' + substring(tc.constraint_name, 5, len(tc.constraint_name)-4) + ''';' 
from sysobjects so 
left join 
    information_schema.table_constraints tc on tc.Table_name = so.Name 
where xtype = 'U' 
and name = @dest 

print 'rename constraints to original names' 
print @sql 
exec sp_executesql @sql 

--now rename the table back to the original 
exec sp_rename @dest, @src 

print 'finally apply the indexes' 
print @sql2 
exec sp_executesql @sql2 

print 'finished' 

commit tran 
1

SQL Azure不支持没有聚集索引的表。这意味着你不能创建新的表格而没有隐藏的索引,并且你不能在现有的表格上删除现有的聚集索引。 Windows Azure在每个表上都需要一个聚集索引。

根据Cihan Biyikoglu(MSFT),一种解决方法是用您喜欢的索引结构创建一个新表,将数据移动并在单个事务中重命名表。

随着存储过程,你可以这样做如下:

begin 
tran 
exec 
sp_rename 'db1','db1_old' 
exec 
sp_rename 'db1_new','db1' 
commit 
tran 
+1

这是我得出的可怕结论。这种方式意味着如果您使用SQL Azure,则EF迁移会中断。希望他们可以做一些事情来整理一下。现在我正在编写自己的存储过程来完成这项工作(非常重要)。 – Ian1971

+0

我同意你的陈述,你的反馈是共享的。我已经添加了更多信息,因为您可以使用SP。谢谢!! – AvkashChauhan