web-dev-qa-db-fra.com

Résultats de DropCreateDatabaseIfModelChanges EF6 dans System.InvalidOperationException: le modèle sauvegardant le contexte a changé

Après la migration vers Entity Framework 6, un message d'erreur s'affiche lors de l'exécution de tests unitaires sur le serveur de génération.

J'utilise l'initialiseur DropCreateDatabaseIfModelChanges. Quand je le change en MigrateDatabaseToLatestVersion tout fonctionne, mais je veux rester avec l'ancien initializer.

L'erreur que je reçois est:

System.InvalidOperationException: System.InvalidOperationException: Le modèle sauvegardant le contexte 'AppContext' a changé depuis la création de la base de données . Envisagez d'utiliser Code First Migrations pour mettre à jour La base de données ( http://go.Microsoft.com/fwlink/?LinkId=238269 ) ..

Ce qui est correct, cela a changé, mais avec DropCreateDatabaseIfModelChanges initializer, il devrait être recréé. Des idées?

EF est configuré dans App.config. Voici la partie pertinente:

<connectionStrings>
    <add name="AppContext" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=my.app.unittest;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>
<entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
        <parameters>
            <parameter value="v11.0" />
        </parameters>
    </defaultConnectionFactory>
    <contexts>
        <context type="my.app.core.Data.AppContext, my.app.core">
            <databaseInitializer type="System.Data.Entity.DropCreateDatabaseIfModelChanges`1[[my.app.core.Data.AppContext, my.app.core]], EntityFramework" />
        </context>
    </contexts>
    <providers>
        <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
</entityFramework>
15
Chris

Eh bien, il semble que EF 6.0 introduit une nouvelle règle:

"Si DbContext utilise un initialiseur ET que les migrations sont configurées, émettez une exception lors de la construction du modèle".

Jusqu'à et y compris l'EF 6 RC, cela n'était pas appliqué. La partie ennuyeuse est que "les migrations sont configurées" est défini par la mise en œuvre d'une configuration DbMigrationsConfiguration. Il ne semble pas y avoir de moyen de désactiver par programme les migrations dans les tests - si vous avez implémenté 

J'ai travaillé autour de cela d'une manière très similaire à celle de Sebastian Piu: je devais supprimer la classe Configuration de mes tests, mais je ne pouvais pas simplement la supprimer car nous utilisions Migrations pour notre projet principal. Argh!

C'était mon code avant:

public class MyDbContext : DbDContext, IMyDbContext
{
  public IDbSet<Users> Users {get; set;}
  public IDbSet<Widgets> Widgets {get; set;}
}

// Migrations are considered configured for MyDbContext because this class implementation exists.
internal sealed class Configuration : DbMigrationsConfiguration<MyDbContext>
{
  public Configuration()
  {
    AutomaticMigrationsEnabled = false;
  }
}

// Declaring (and elsewhere registering) this DB initializer of type MyDbContext - but a DbMigrationsConfiguration already exists for that type.
public class TestDatabaseInitializer : DropCreateDatabaseAlways<MyDbContext>
{
    protected override void Seed(MyDbContext context) { }
}

J'ai rencontré l'exception System.InvalidOperationException lors de l'initialisation de DbContext dans mon code de test. Comme l'application n'utilise aucun initialiseur, il n'y a eu aucun problème à exécuter l'application comme auparavant. Cela ne fait que briser mes tests.

La solution (qui ressemble davantage à une solution de contournement des éléments manquants dans EF) consiste à segmenter l’initialiseur et la configuration DbMigrations afin qu’un seul soit visible dans un environnement d’exécution. Je veux que mes tests utilisent l'initialiseur et que mon application utilise DbMigrationsConfiguration. Cela pourrait être fait plus proprement si DbContext avait une interface, mais hélas, il implémente seulement IObjectContextAdapter.

J'ai d'abord fait mon résumé DbContext:

public abstract class MyDbContextBase : DbContext, IMyDbContext
{
      public IDbSet<Users> Users {get; set;}
      public IDbSet<Widgets> Widgets {get; set;}
}

Puis j'ai dérivé 2 classes:

public class MyDbContext : MyDbContextBase
{
  public MyDbContext(string connectionStringOrName, IDatabaseInitializer<MyDbContext> dbInitializer) 
    : base(connectionStringOrName)
  {
  }
}

public class MyTestDbContext : MyDbContextBase
{
  public MyTestDbContext(string connectionStringOrName, IDatabaseInitializer<MyDbContext> dbInitializer) 
    : base(connectionStringOrName)
  {
    Database.SetInitializer(dbInitializer);
  }
}

Un MyDbContext et un MyTestDbContext sont tous deux IMyDbContexts. Votre configuration d'injection de dépendance existante doit donc fonctionner sans modification. J'ai seulement testé Spring.NET.

Ma configuration DbMigrationsConfiguration implémente le type dérivé NON utilisé par les tests:

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

Enfin, le type de l'initialisateur a été déplacé vers le type de classe de test dérivé:

public class TestDatabaseInitializer : DropCreateDatabaseAlways<MyTestDbContext>
{
    protected override void Seed(MyTestDbContext context) { }
}

Je peux confirmer que mes tests ont réussi et que mon application (et mes migrations) fonctionne toujours comme auparavant.

19
Stefan Mohr

J'ai trouvé le même problème juste après la mise à niveau vers EF6. Après avoir lu le commentaire de Stefan et présenté les mêmes symptômes qu’il a décrits (les tests chargeaient la classe Configuration de mon projet principal) 

La solution/solution dans mon cas était de

  • créer un nouveau class TestContext: MyDataContext dans mon projet de test
  • changer l'initialiseur de DropCreateDatabaseAlways<MyDataContext> à DropCreateDatabaseAlways<TestContext>
  • mettre à jour/généraliser les endroits où j'ai créé mon real context pour utiliser le test

Je pourrais le faire parce que la plupart de mes tests s’appliquent à une classe PersistenceTest. Je comprends donc que cela pourrait être difficile à changer si vous avez un grand catalogue. Donc, dans l'attente d'autres solutions

5
Sebastian Piu

Cela est dû au fait que les migrations sont activées et que vous utilisez l'initialiseur DropCreateDatabaseIfModelChanges. Entityframework ne prend pas en charge l'utilisation de cet initialiseur avec les migrations. Vous avez deux options:

  • Désactiver l'initialiseur 

ou

  • Désactiver les migrations en supprimant la configuration des migrations
3
lukew

Il semble que ce comportement était prévu. Voici une citation de l'un des développeurs:

Ce changement de comportement était inhérent à la conception, car EF5 créerait la base de données sans utiliser les migrations définies, ce qui signifie que la base de données créée par l'initialiseur pourrait être différente de celle créée par Migrations. Cela pourrait conduire à tester sur un schéma de base de données mais à s'exécuter en production sur un schéma de base de données différent. Cependant, nous avons provisoirement décidé de modifier ce comportement, ce qui est suivi ici: https://entityframework.codeplex.com/workitem/1709

2
Chris

Ce qui fonctionne bien pour moi, c’est d’exclure les migrations à l’aide de define. Voici comment:

  • Créez une nouvelle configuration appelée Test qui définit TEST
  • Dans vos tests, générez une erreur lorsque TEST n'est pas défini.
  • Excluez vos migrations lorsque TEST est défini:
#if !TEST
internal sealed class Configuration : DbMigrationsConfiguration<Context>
{
    //...
}
#endif

Vous devrez peut-être exclure toutes vos migrations, ce qui n'est pas totalement satisfaisant non plus (mais je ne l'ai pas essayée car je n'ai pas encore de migration).

1
jgillich