création du code SQL à partir des classes .NET permettant la création d'une bdd
au run-time, les classes .NET permettent la génération du Model
si les classes .NET changent, il est possible de mettre à jour la bdd
Ajout d'une classe *Context
Ajouter un project *DataModel de type Class Library
Nuget → Ajouter EF
Créer une classe *Context
ContactContext.cs
publicclassContactContext : DbContext// hérite de DbContext
{
// DbSet de Contact permet de manipuler une collection de ContactpublicDbSet<Contact> Contacts { get; set; }
}
Vérifier le Data Model
Installer l'extension Entity Framework 6 Power Tools
Permet de générer le Data Model de la même manière qu'à l’exécution afin de vérifier que le Data Model correspond bien à nos attentes.
Solution Explorer → clique-droit sur la classe *Context → Entity Framework → View Entity Data Model
Corriger le Data Model si besoin
Le projet contenant la classe *Context doit être le Startup Project
publicclassContact
{
// relation 0..1 - * entre Address et Contact// un Contact peut avoir 0,1 ou plusieurs addresses. Une adresse appartient à 0 ou 1 Contact.publicList<Address> Addresses { get; set; }
}
publicclassAddress
{
// Contact étant nullable, une adresse appartient à 0 ou 1 Contact.
[Required] // pour spécifier qu'une adresse appartient forcément à 1 ContactpublicContact Contact { get; set; }
}
// force le nom de la table
[Table("Persons")]
publicclassContact
{
// définit ContactId comme clé primaire
[Key]
publicstring ContactId { get; set; }
// ajoute un index. Nom par défaut: IX_<property name>
[Index("AgeIndex")]
publicint Age { get; set; }
// ajoute un index unique. Nom par défaut: IX_<property name>
[Index(IsUnique = true)]
// longueur max 200
[StringLength(200)]
publicstring Username { get; set; }
// Address étant nullable, rend la propriété obligatoire (not null)
[Required]
publicAddress Address { get; set; }
// propriété à ne pas prendre en compte
[NotMapped]
publicint MyNumber { get; } = 45;
// force le nom de la colonne et son type de donnée
[Column("ContactDescription", TypeName="ntext")]
publicstring Description { get; set; }
protectedoverridevoidOnModelCreating(DbModelBuildermodelBuilder)
{
// use all types that inherit from EntityTypeConfiguration and ComplexTypeConfiguration in the current assembly
modelBuilder.Configurations.AddFromAssembly(typeof(MyAppContext).Assembly);
Astuces
Ignorer une propriété
ContactContext.cs
publicclassContactContext : DbContext
{
protectedoverridevoidOnModelCreating(DbModelBuildermodelBuilder)
{
// ignore la propriété MyProperty dans tous les types
modelBuilder.Types().Configure(c => c.Ignore("MyProperty"));
base.OnModelCreating(modelBuilder);
Modifier les données avant de les sauvegarder dans la bdd
Mettre à jour le model edmx après un changement dans la bdd
Ouvrir le model edmx → clique-droit → Update Model from Database → Refresh
Utilisation
using (varcontext = newContactContext())
{
// récupérer des donnéesList<Contact> allContacts = context.Contacts.ToList();
Contactcontact = context.Contacts.FirstOrDefault(c => c.Name == "Nicolas");
// avec la primary keyContactcontact = context.Contacts.Find(contactId);
// équivalent à Contactcontact = context.Contacts.SingleOrDefault(c => c.Id == contactId);
// exécuter un procédure stockée qui renvoie des contactsList<Contact> contacts = context.Contacts.SqlQuery("exec GetContacts").ToList();
// récupérer des contacts ainsi que les numéros de téléphone associésList<Contact> allContacts = context.Contacts.Include(c => c.PhoneNumbers).ToList();
// récupérer des données en mémoireObservableCollection<Contact> allContacts = context.Contacts.Local;
// insérer des données
context.Contacts.Add(newContact() { Name = "Nicolas" });
context.SaveChanges();
// mettre à jour des donnéesContactcontact = context.Contacts.FirstOrDefault(c => c.Name == "Nicolas");
contact.Address = "New Address";
context.SaveChanges();
// insérer ou mettre à jour des données// using System.Data.Entity.Migrations;
context.Contacts.AddOrUpdate(contact);
context.SaveChanges();
// supprimer un contact
context.Contacts.Remove(contact);
// supprimer tous les contacts un par un
context.Contacts.RemoveRange(context.Contacts);
// exécuter une commande sql qui ne retourne pas de contacts
context.Database.ExecuteSqlCommand("truncate table Contacts");
context.Database.ExecuteSqlCommand($"exec DeleteContactById {Id}");
}
// context.Contacts.Contains(contact) or context.Contacts.Any(c => c == contact) raises the following exception// Unable to create a constant value of type '...'. Only primitive types or enumeration types are supported in this context.if (context.Contacts.Any(c => c.Id == contact.Id))
{
// if the contact isnot already added in the database the following exception is raised
// InvalidOperationException : The element at index 0 in the collection of objects to refresh isin the added state.
// Objectsinthis state cannot be refreshed.
context.Contacts.Refresh(RefreshMode.StoreWins, contact);
}
migre la db si besoin mais ne la créé pas si elle n'existe pas.
DropCreateDatabaseAlways<TContext>
recréé la bdd à chaque fois.
DropCreateDatabaseIfModelChanges<TContext>
recréé la bdd si le data model a changé.
NullDatabaseInitializer<TContext>
ne rien faire.
À placer avant le premier appel au context: Global.asax.cs ou App.xaml.cs
// ne rien faire à l'initialisation
Database.SetInitializer(newNullDatabaseInitializer<ContactContext>());
// équivalent
Database.SetInitializer<ContactContext>(null);
DataHelpers.cs
publicstaticclassDataHelpers
{
// recréé la bdd et la remplipublicstaticvoidNewDbWithSeed()
{
Database.SetInitializer(newDropCreateDatabaseAlways<ComptesContext>());
using (varcontext = newComptesContext())
{
context.Comptes.AddRange(newList<Compte>()
{
newCompte() { Name = "Stan" },
newCompte() { Name = "Kenny" }
});
context.SaveChanges();
Relations
classEntreprise
{
publicint Id { get; set; }
publicstring Name { get; set; }
// liaison Entreprise → Employe. Optionnel si Employe contient déjà un lien vers Entreprise.// pratique pour connaître directement la liste des employéspublicList<Employe> Employes { get; set; }
}
classEmploye
{
publicint Id { get; set; }
publicstring Name { get; set; }
// liaison Employe → Entreprise. Optionnel si Entreprise contient déjà un lien vers Employe.// pratique pour connaître directement l'entreprise d'un employé// permet aussi de rendre obligatoire la propriété Entreprise avec Required. Relation 0..1 devient 1
[Required]
publicEntreprise Entreprise { get; set; }
// déclaration de la foreign key. Optionnel mais très utile dans les applications web car déconnectées.// permet de rendre obligatoire la propriété Entreprise sans Required. Relation 0..1 devient 1publicint EntrepriseId { get; set; }
}
classAppContext : DbContext
{
publicDbSet<Entreprise> Entreprises { get; set; }
publicDbSet<Employe> Employes { get; set; }
}
// ajout de données dans la bdd
Database.SetInitializer(newDropCreateDatabaseAlways<AppContext>());
using (varcontext = newAppContext())
{
// création des entreprise sans initialiser Entreprise.Employes ni renseigner les employésvarwc = context.Entreprises.Add(newEntreprise() { Name = "WorldCompanie" });
varsb = context.Entreprises.Add(newEntreprise() { Name = "SuperBank" });
context.Employes.AddRange(newList<Employe>()
{
// la liaison entre Entreprise et Employe se fait grace à la propriété Employe.Entreprise
// lors de la récupération des entreprises, Entreprise.Employes sera renseignée
newEmploye() { Name = "Mike", Entreprise = wc },
newEmploye() { Name = "Paul", Entreprise = wc },
newEmploye() { Name = "Julie", Entreprise = sb },
newEmploye() { Name = "Rachelle", Entreprise = sb }
});
context.SaveChanges();
using (varcontext = newAppContext())
{
// sans Include entreprise.Employes serait null// une autre solution moins performante: mettre entreprise.Employes en virtualforeach (var entreprise in context.Entreprises.Include(e => e.Employes))
{
Console.WriteLine(entreprise.Name);
foreach (var employe in entreprise.Employes)
{
Console.WriteLine(employe.Name);
}
}
Log the SQL queries
// in VS Output
context.Database.Log = s => Debug.WriteLine(s);
// in Console app
context.Database.Log = Console.WriteLine;
Sans configuration, SQL Server Express est utilisé s'il est installé, sinon LocalDb.
Web.config
<!-- SQL Server --><entityFramework><defaultConnectionFactorytype="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" /><!-- LocalDB --><entityFramework><defaultConnectionFactorytype="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"><parameters><parametervalue="mssqllocaldb" /></parameters></defaultConnectionFactory>
Connection String
Si le connection string name correspond à la classe *Context (avec ou sans namespace), la connexion sera chargée automatiquement
Default database name: Namespace.ClasseContext
Dans Package Manager Console, sélectionner le projet qui contient les référence EF.
C'est le fichier de config du startup project qui est lu pour obtenir la connection string.
Initialisation
Activer la migration: View → Other Windows → Package Manager Console
Enable-Migrations# s'il y a plusieurs classe *Context: ajouter -ContextTypeName ClasseContext# un dossier Migration avec un fichier Configuration.cs est créé
Si la bdd existe a déjà été créé, un fichier YYYYMMDDHHMMSSS_IntialCreate.cs est ajouté au dossier Migration. Il représente le code C# qui permet la création de la structure de la bdd.
Si la bdd n'existe pas encore:
# Créer le fichier de migration avec la commande suivanteAdd-Migration IntialCreate
# un fichier YYYYMMDDHHMMSSS_IntialCreate.cs est créé dans le dossier Migration# génération et exécution du script T-SQL pour créer la bddUpdate-Database-Verbose
Migration
# Après avoir modifié le modèle objet.Add-Migration Migration-Name# créé un fichier YYYYMMDDHHMMSSS_Migration-Name.cs dans le dossier Migration
YYYYMMDDHHMMSSS_Migration-Name.cs
// Vérifier le contenu du fichier et apporter des modifications si besoinpublicpartialclassMigration-Name : DbMigration
{
publicoverridevoidUp()
{
// Exécuter une commande SQL Sql("UPDATE dbo.MyTable SET Column1 = 'xxx' WHERE Column1 IS NULL");
# génération et exécution du script T-SQLUpdate-Database-Verbose
Autres
# list the applied migrationsget-migrations# il est possible de retourner à n'importe quelle étape de migrationUpdate-Database-TargetMigration: Migration-Name# générer un script de la création complète de la bdd jusqu'à Migration-NameUpdate-Database-Script-SourceMigration: $InitialDatabase-TargetMigration: Migration-Name# par défaut, SourceMigration: état actuel, et TargetMigration: dernière migration
Configuration.cs
Configuration.cs
publicConfiguration()
{
// si true, permet d'appeler Update-Database pour mettre à jour la bdd sans créer de point de migration avec Add-Migration
AutomaticMigrationsEnabled = false;
ContextKey = "Namespace.ClasseContext";
}
// This method will be called after migrating to the latest version.protectedoverridevoidSeed(ConsoleTestEF.AppContextcontext)
{
vare1 = newEntreprise() { Nom = "World Companie" };
vare2 = newEntreprise() { Nom = "Super Bank" };
// ajoute les entreprises précédentes si elles n'existent pas// le test d’existence se fait sur la propriété Nom
context.Entreprises.AddOrUpdate(e => e.Nom, e1, e2);
// si on a des éléments référençant les entreprises précédemment créées, il faut appeler SaveChanges
context.SaveChanges();
// ajoute 1000 employés si leur nom n'existe pas déjàvarrnd = newRandom();
for (inti = 0; i < 1000; i++)
{
context.Employes.AddOrUpdate(e => e.Nom,
newEmploye()
{
Nom = $"Mr {i}",
// utiliser la ForeignKeyEntrepriseId, plutôt que la propriété EntrepriseEntrepriseId = rnd.Next(2) == 0 ? e1.Id : e2.Id
});
}
Entity Framework Fake ObjectContext Realization Tool is the official In Memory provider for Entity Framework Classic.
It creates a fake or mock database that allows you to test the Business Logic Layer (BLL) without worrying about your Data Access Layer (DAL).
usingEffort;
// create the fake contextvarconnection = DbConnectionFactory.CreateTransient();
varcontext = newMyAppContext(connection, true);
// fill the database
context.Database.CreateIfNotExists();
varitem = newItem();
context.Set<Item>().Add(item);
context.SaveChanges();
// create the repository and call the method to testvarrepository = newItemRepository(context);
varquery = newItemQuery();
varresult = await repository.GetAsync(query);
Erreurs
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection
L'objet a été disposé, via un using par exemple, et ne peut plus faire de requêtes.
EntityType '...' has no key defined. Define the key for this EntityType.
La classe ne contient pas de clé.
Ajouter une propriété Id de type int.
Décorer une propriété avec l'attribut [Key].
Cannot drop database "..." because it is currently in use.
Fermer SSMS.
EF6 vs EF7
EF6
EF7
Visual Designer
Oui
Non
Backward Compatible
Oui
Non
Core CLR Support
Non
Oui
Non-Relational Data
Non
Oui
EF et WPF
ConnectedRepository.cs
// instancié par la MainWindow, une seule instance pour toute la durée de vie de l'applicationpublicclassConnectedRepository
{
// utilisation d'un seul contextprivatereadonlyContactContext_context = newContactContext();
// méthode pour récupérer les donnéespublicList<Contact> GetAllContacts()
{
return_context.Contacts.ToList();
EF et ASP.NET MVC
DisconnectedRepository.cs
publicclassDisconnectedRepository
{
publicIEnumerable<Ecriture> GetEcritures()
{
using (varcontext = newComptesContext())
{
return context.Ecritures.AsNoTracking().ToList();
}
}
publicEcritureGetEcriture(intid)
{
using (varcontext = newComptesContext())
{
return context.Ecritures.Find(id);
// plus performant mais moins lisiblereturn context.Ecritures.AsNoTracking().SingleOrDefault(e => e.Id == id);
}
}
publicvoidAdd(Ecritureecriture)
{
using (varcontext = newComptesContext())
{
context.Ecritures.Add(ecriture);
context.SaveChanges();
}
}
publicvoidEdit(Ecritureecriture)
{
using (varcontext = newComptesContext())
{
context.Entry(ecriture).State = EntityState.Modified;
context.SaveChanges();
}
}
publicvoidDelete(intid)
{
Delete(GetEcriture(id));
}
publicvoidDelete(Ecritureecriture)
{
using (varcontext = newComptesContext())
{
context.Entry(ecriture).State = EntityState.Deleted;
context.SaveChanges();
}
}
Console application
add EntityFramework nuget package
Data
Data/Item.cs
classItem
{
publicint Id { get; set; }
publicstring Name { get; set; }
}
internalsealedclassConfiguration : DbMigrationsConfiguration<ItemDbContext>
{
publicConfiguration()
{
AutomaticMigrationsEnabled = false;
}
protectedoverridevoidSeed(ItemDbContextcontext)
{
context.Items.AddOrUpdate(newItem { Id = 1, Name = "Item1" });
context.Items.AddOrUpdate(newItem { Id = 2, Name = "Item2" });
}
}
# add a migrationAdd-Migration IntialCreate
# generate the sql script representing the migrationUpdate-Database-script# if it is not what you expected, update the entities and the configuration then run again the add migrationAdd-Migration IntialCreate
# apply the migration by updating the databaseUpdate-Database