EF 4.3.1 Миграция Заполнение не работает должным образом

Я использую миграции EF 4.3.1, и у меня есть класс конфигурации, в котором у меня есть следующий код:

internal sealed class Configuration : DbMigrationsConfiguration<DbContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }

    protected override void Seed(PayByPhoneDbContext context)
    {
        context.Roles.AddOrUpdate(r => r.Name, new Role { Name = "A" }, new Role { Name = "B" });
        context.Administrators.AddOrUpdate(a => a.Email, new Administrator { Email = "[email protected]" Name = "A", Role = context.Roles.Local.SingleOrDefault(role => role.Name == "A") });
    }
}

Теперь, когда я запускаю команду migrate (часть сценария MSBuild), когда БД не существует, создаются таблицы, и заполнение происходит, как и ожидалось. Но когда я запускаю команду migrate для существующей базы данных без каких-либо миграций и все данные уже заполнены (когда вместо вставки необходимо выполнить обновление), я получаю сообщение об ошибке при выполнении команды migrate:

Нет ожидающих явных миграций.

Метод бегущей семени.

System.Data.Entity.Infrastructure.DbUpdateException: произошла ошибка при обновлении записей. Подробности смотрите во внутреннем исключении. ---> System.Data.UpdateException: Произошла ошибка при обновлении записей. Подробности смотрите во внутреннем исключении. ---> System.Data.SqlClient.SqlException: оператор UPDATE конфликтует с ограничением FOREIGN KEY «FK_Administrators_Roles_RoleId». Конфликт произошел в базе данных "xxxDB", таблице "dbo.Roles", столбце "Id".

Заявление было прекращено.

Трассировки стека:

at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at System.Data.Mapping.Update.Internal.DynamicUpdateCommand.Execute(UpdateTranslator translator, EntityConnection connection, Dictionary`2 identifierValues, List`1 generatedValues)
   at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter)
   --- End of inner exception stack trace ---
   at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter)
   at System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache)
   at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options)
   at System.Data.Entity.Internal.InternalContext.SaveChanges()
   --- End of inner exception stack trace ---
   at System.Data.Entity.Internal.InternalContext.SaveChanges()
   at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
   at System.Data.Entity.DbContext.SaveChanges()
   at System.Data.Entity.Migrations.DbMigrator.SeedDatabase()
   at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.SeedDatabase()
   at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
   at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
   at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
   at System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
   at System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.RunCore()
   at System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run()

Запустив профилировщик SQL Server, я обнаружил, что при выполнении произошла ошибка:

exec sp_executesql N'update [dbo].[Administrators]
set [RoleId] = @0
where ([Id] = @1)
',N'@0 int,@1 bigint',@0=0,@1=1

Каков правильный способ заполнения данных внешними ключами в методе Seed?


person Achinth Gurkhi    schedule 06.05.2012    source источник
comment
Вы проверили, возвращает ли context.Roles.Local.SingleOrDefault(role => role.Name == "A") правильную роль, а не null?   -  person Slauma    schedule 07.05.2012
comment
Это сложно отладить, так как он выполняется в консоли Nuget или в командной строке (при использовании migrate.exe).   -  person Achinth Gurkhi    schedule 07.05.2012
comment
Я удалил свой ответ, он был бесполезен. Возможно, обновление отношений не поддерживается. По крайней мере, Ладислав упомянул об этом: AddOrUpdate никоим образом не поддерживает изменение отношений, поэтому вы не можете использовать его для добавления или удаления отношений при следующей миграции. (отсюда: stackoverflow.com/a/8551511/270591. Ответ получен на этапе предварительного просмотра EF Migrations, но, возможно, он все еще действителен.)   -  person Slauma    schedule 07.05.2012


Ответы (1)


AddOrUpdate сбросит столбцы, которые вы не укажете, на null (или ноль в случае int).

Насколько я понимаю, это поведение (которое, кстати, ужасно) не будет применяться к столбцам, которые оно распознает как ключи или как автоинкремент, но, по-видимому, в вашем случае это так.

Я бы посоветовал сделать проверку, чтобы убедиться, что роль не существует в базе данных, а затем выполнить стандартный add(), если это не так.

Я делаю это для всех своих начальных данных, потому что вы никогда не знаете, когда позже вы можете предоставить интерфейс для обновления данных, которые вы запустили, и вы не хотите, чтобы метод Seed() перезаписывал ваши изменения в этих обстоятельствах.

Также имейте в виду, что если вы когда-либо решите использовать инициализатор базы данных MigrateDatabaseToLatestVersion, то Seed() будет срабатывать каждый раз при перезапуске вашего приложения (развертывание dll, обновление web.config и т. д.).

Лучше всего использовать защитный код в методе Seed().

Обновление: у Джули Лерман есть хороший пост в блоге. об этом поведении.

person Roger    schedule 31.05.2012