« Entity Framework 6 » : différence entre les versions
(→Liens) |
|||
(40 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 2 : | Ligne 2 : | ||
= Liens = | = Liens = | ||
* [https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/conventions/built-in Code First Conventions] | * [https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/conventions/built-in Code First Conventions] | ||
* [https://entityframework | * [[Entity_Framework_Plus|Entity Framework Plus]] | ||
* [https://entityframework.net/entity-framework-vs-linq-to-sql Entity Framework vs LINQ to SQL] | |||
= Présentation = | = Présentation = | ||
Ligne 15 : | Ligne 16 : | ||
* 1 projet pour le data model EF, celui-ci référencera le projet des classes du domaine | * 1 projet pour le data model EF, celui-ci référencera le projet des classes du domaine | ||
* les autres projets référenceront au besoin l'un ou l'autre ou les 2. | * les autres projets référenceront au besoin l'un ou l'autre ou les 2. | ||
= [https://docs.microsoft.com/en-us/ef/ef6/get-started Setting up] = | |||
* optinally create a new Class Library project name {{boxx|*.DataAccess}} | |||
* add the {{boxx|EntityFramework}} nuget package | |||
* create the entities | |||
<filebox fn='Entities/Item.cs' collapsed> | |||
public class Item | |||
{ | |||
public int Id { get; set; } | |||
public string Name { get; set; } | |||
} | |||
</filebox> | |||
* create the {{boxx|DbContext}} | |||
<filebox fn='MyAppDbContext.cs'> | |||
public class MyAppDbContext : DbContext | |||
{ | |||
public DbSet<Item> Items { get; set; } | |||
} | |||
</filebox> | |||
* create the repository | |||
<filebox fn='Repositories/ItemRepository.cs'> | |||
public class ItemRepository : IItemRepository | |||
{ | |||
private readonly MyAppDbContext dbContext; | |||
public ItemRepository(MyAppDbContext dbContext) | |||
{ | |||
this.dbContext = dbContext; | |||
} | |||
public async Task<IEnumerable<Item>> GetAsync(CancellationToken cancellationToken) | |||
{ | |||
var items = await this.dbContext.Items.ToListAsync(cancellationToken); | |||
return items; | |||
} | |||
} | |||
</filebox> | |||
* connection string. Use the same name as the DbContext class to load automatically the connection string. | |||
<filebox fn='Web.config' lang='xml'> | |||
<configuration> | |||
<configSections></configSections> | |||
<connectionStrings> | |||
<add name="MyAppDbContext" | |||
providerName="System.Data.SqlClient" | |||
connectionString="Server={server-name};Database={db-name};Trusted_Connection=True;MultipleActiveResultSets=true;Connection Timeout=3" /> | |||
</connectionStrings> | |||
</filebox> | |||
* dependency injection of the DbContext in a web api project | |||
<filebox fn='App_Start/DryIocConfig.cs'> | |||
public static class DryIocConfig | |||
{ | |||
public static void Register(HttpConfiguration config) | |||
{ | |||
var container = new Container().WithWebApi(config, throwIfUnresolved: t => t.IsController()); | |||
container.Register<IItemRepository, ItemRepository>(); | |||
container.Register<MyAppDbContext>(Reuse.InWebRequest); | |||
} | |||
} | |||
</filebox> | |||
= Code first = | = Code first = | ||
Ligne 90 : | Ligne 155 : | ||
== Fluent API with configuration files == | == Fluent API with configuration files == | ||
<filebox fn='ContactConfiguration'> | <filebox fn='ContactConfiguration.cs'> | ||
internal sealed class ContactConfiguration : EntityTypeConfiguration<Contact> | internal sealed class ContactConfiguration : EntityTypeConfiguration<Contact> | ||
{ | { | ||
Ligne 97 : | Ligne 162 : | ||
this.HasKey(x => x.ContactId); | this.HasKey(x => x.ContactId); | ||
this.HasIndex(x => x.Age).HasName("AgeIndex"); | this.HasIndex(x => x.Age).HasName("AgeIndex"); | ||
this.HasIndex(x => x.Username).IsUnique(); | this.HasIndex(x => x.Username).IsUnique(true); | ||
this.Property(x => x.Username).HasMaxLength(200); | this.Property(x => x.Username).HasMaxLength(200); | ||
this.HasRequired(x => x.Address); | this.HasRequired(x => x.Address); | ||
Ligne 174 : | Ligne 239 : | ||
// équivalent à | // équivalent à | ||
Contact contact = context.Contacts.SingleOrDefault(c => c.Id == contactId); | Contact contact = context.Contacts.SingleOrDefault(c => c.Id == contactId); | ||
List<SqlParameter> parameters = this.BuildSqlParameters(...); | |||
string parameterNamesList = string.Join(", ", parameters.Select(p => p.ParameterName)); | |||
// exécuter un procédure stockée qui renvoie des contacts | // exécuter un procédure stockée qui renvoie des contacts | ||
var contacts = context.Contacts.SqlQuery<List<Contact>>( | |||
$"GetContacts {parameterNamesList}", | |||
parameters.Cast<object>().ToArray()).ToListAsync(cancellationToken); | |||
// exécuter un procédure stockée qui ne renvoie pas de résultat | |||
await this.context.Database.ExecuteSqlCommandAsync( | |||
$"InsertContacts {parameterNamesList}", | |||
cancellationToken, | |||
parameters.Cast<object>().ToArray()); | |||
// récupérer des contacts ainsi que les numéros de téléphone associés | // récupérer des contacts ainsi que les numéros de téléphone associés | ||
List<Contact> allContacts = context.Contacts.Include(c => c.PhoneNumbers).ToList(); | List<Contact> allContacts = context.Contacts.Include(c => c.PhoneNumbers).ToList(); | ||
Ligne 332 : | Ligne 408 : | ||
</kode> | </kode> | ||
= Log = | = Log the SQL queries = | ||
<kode lang='csharp'> | <kode lang='csharp'> | ||
// in VS Output | |||
context.Database.Log = s => Debug.WriteLine(s); | |||
// in Console app | |||
context.Database.Log = Console.WriteLine; | context.Database.Log = Console.WriteLine; | ||
</kode> | </kode> | ||
Ligne 354 : | Ligne 434 : | ||
</filebox> | </filebox> | ||
== | == Connection String == | ||
{{info | | {{info | Si le {{boxx|connection string name}} correspond à la classe {{boxx|*Context}} (avec ou sans namespace), la connexion sera chargée automatiquement<br> | ||
Default database name: {{boxx|Namespace.ClasseContext}}}} | |||
<filebox fn='Web.config' lang='xml'> | <filebox fn='Web.config' lang='xml'> | ||
<configuration> | <configuration> | ||
<connectionStrings> | <connectionStrings> | ||
<add name="ClasseContext" | <add name="ClasseContext" | ||
providerName="System.Data.SqlClient" | providerName="System.Data.SqlClient" | ||
connectionString=" | connectionString="data source=PC-NAME\SQLEXPRESS;initial catalog=Namespace.ClasseContext;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" /> | ||
</filebox> | </filebox> | ||
= [https://msdn.microsoft.com/en-us/library/jj591621(v=vs.113).aspx Migration] = | = [https://msdn.microsoft.com/en-us/library/jj591621(v=vs.113).aspx Migration] = | ||
{{warn | | |||
* Dans {{boxx|Package Manager Console}}, sélectionner le projet qui contient les référence EF. | |||
* C'est le fichier de config du {{boxx|startup project}} qui est lu pour obtenir la connection string. | |||
}} | |||
== Initialisation == | == Initialisation == | ||
* Activer la migration: View → Other Windows → {{boxx|Package Manager Console}} | * Activer la migration: View → Other Windows → {{boxx|Package Manager Console}} | ||
Ligne 409 : | Ligne 494 : | ||
== Autres == | == Autres == | ||
<kode lang='powershell'> | <kode lang='powershell'> | ||
# list the applied migrations | |||
get-migrations | |||
# il est possible de retourner à n'importe quelle étape de migration | # il est possible de retourner à n'importe quelle étape de migration | ||
Update-Database -TargetMigration: Migration-Name | Update-Database -TargetMigration: Migration-Name | ||
Ligne 457 : | Ligne 545 : | ||
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). | 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). | ||
<kode lang='cs'> | <kode lang='cs'> | ||
var connection = Effort.DbConnectionFactory.CreateTransient(); | using Effort; | ||
var context = new MyAppContext(connection); | |||
// create the fake context | |||
var connection = DbConnectionFactory.CreateTransient(); | |||
var context = new MyAppContext(connection, true); | |||
// fill the database | |||
context.Database.CreateIfNotExists(); // create the db once for all the tests | |||
using (this.Context.Database.Connection.BeginTransaction()) // then use transactions | |||
{ | |||
var item = new Item(); | |||
context.Set<Item>().Add(item); | |||
await context.SaveChangesAsync(CancellationToken.None); | |||
} | |||
// create the repository and call the method to test | |||
var repository = new ItemRepository(context); | |||
var query = new ItemQuery(); | |||
var result = await repository.GetAsync(query); | |||
</kode> | |||
{{warn | You can use the ids to link objects between themselves, but when SaveChangesAsync is called all the ids are replaced by the values they have now in the database.}} | |||
== [https://github.com/zzzprojects/EntityFramework-Effort/issues/63 Error: Incorrect number of arguments for constructor] == | |||
<kode lang='cs'> | |||
DbConnectionFactory.LargePropertyCount = 16; // set from 8 to 16 | |||
var connection = DbConnectionFactory.CreateTransient(); | |||
var context = new MyAppContext(connection, true); | |||
</kode> | </kode> | ||
Ligne 556 : | Ligne 670 : | ||
} | } | ||
} | } | ||
</filebox> | |||
= Console application = | |||
# add {{boxx|EntityFramework}} nuget package | |||
== Data == | |||
<filebox fn='Data/Item.cs' collapsed> | |||
class Item | |||
{ | |||
public int Id { get; set; } | |||
public string Name { get; set; } | |||
} | |||
</filebox> | |||
== DBContext == | |||
<filebox fn='Data/ItemDbContext.cs' collapsed> | |||
class ItemDbContext : DbContext | |||
{ | |||
public ItemDbContext() : base("name=ItemContext") | |||
{ } | |||
public DbSet<Item> Items { get; set; } | |||
} | |||
</filebox> | |||
<filebox fn='App.config' lang=xml collapsed> | |||
<configuration> | |||
<connectionStrings> | |||
<add name="ItemContext" connectionString="Server=localhost;Database=Item;Integrated Security=True" providerName="System.Data.SqlClient" /> | |||
</connectionStrings> | |||
</configuration> | |||
</filebox> | |||
== Entities configuration == | |||
<filebox fn='Data/ItemDbContext.cs' collapsed> | |||
class ItemDbContext : DbContext | |||
{ | |||
protected override void OnModelCreating(DbModelBuilder modelBuilder) | |||
{ | |||
base.OnModelCreating(modelBuilder); | |||
modelBuilder.Configurations.AddFromAssembly(typeof(ItemDbContext).Assembly); | |||
} | |||
} | |||
</filebox> | |||
<filebox fn='EntitiesConfiguration/ItemConfiguration.cs' collapsed> | |||
class ItemConfiguration : EntityTypeConfiguration<Item> | |||
{ | |||
public ItemConfiguration() | |||
{ | |||
this.Property(x => x.Name).IsRequired().HasMaxLength(50); | |||
} | |||
} | |||
</filebox> | |||
== Migration == | |||
# Visual Studio → Package Manager Console | |||
<kode lang='ps'> | |||
# create a Configuration class | |||
Enable-Migrations | |||
</kode> | |||
<filebox fn='Migrations/Configuration.cs' collapsed> | |||
internal sealed class Configuration : DbMigrationsConfiguration<ItemDbContext> | |||
{ | |||
public Configuration() | |||
{ | |||
AutomaticMigrationsEnabled = false; | |||
} | |||
protected override void Seed(ItemDbContext context) | |||
{ | |||
context.Items.AddOrUpdate(new Item { Id = 1, Name = "Item1" }); | |||
context.Items.AddOrUpdate(new Item { Id = 2, Name = "Item2" }); | |||
} | |||
} | |||
</filebox> | |||
<kode lang='ps'> | |||
# add a migration | |||
Add-Migration IntialCreate | |||
# generate the sql script representing the migration | |||
Update-Database -script | |||
# if it is not what you expected, update the entities and the configuration then run again the add migration | |||
Add-Migration IntialCreate | |||
# apply the migration by updating the database | |||
Update-Database | |||
</kode> | |||
== Program == | |||
<filebox fn='Program.cs' collapsed> | |||
static void Main(string[] args) | |||
{ | |||
using (var db = new ItemDbContext()) | |||
{ | |||
var items = db.Items.ToList(); | |||
foreach (var item in items) | |||
{ | |||
Console.WriteLine($@"{item.Id} - {item.Name}"); | |||
} | |||
} | |||
Console.ReadKey(); | |||
} | |||
</filebox> | </filebox> |
Dernière version du 14 août 2023 à 10:15
Liens
Présentation
EF est un ORM (Object Relational Mapper)
- stockage et la récupération d'objet .NET dans une bdd
- automatise les taches de connexions et requêtes, rend le code plus concis qu'avec ADO.NET
- requêtes formulées avec LINQ to Entities
Architecture
Pour éviter les références circulaires: isoler les classes du domaine dans un projet à part, ainsi le projet data model EF pourra le référencer.
- 1 projet pour les classes du domaine
- 1 projet pour le data model EF, celui-ci référencera le projet des classes du domaine
- les autres projets référenceront au besoin l'un ou l'autre ou les 2.
Setting up
- optinally create a new Class Library project name *.DataAccess
- add the EntityFramework nuget package
- create the entities
Entities/Item.cs |
public class Item { public int Id { get; set; } public string Name { get; set; } } |
- create the DbContext
MyAppDbContext.cs |
public class MyAppDbContext : DbContext { public DbSet<Item> Items { get; set; } } |
- create the repository
Repositories/ItemRepository.cs |
public class ItemRepository : IItemRepository { private readonly MyAppDbContext dbContext; public ItemRepository(MyAppDbContext dbContext) { this.dbContext = dbContext; } public async Task<IEnumerable<Item>> GetAsync(CancellationToken cancellationToken) { var items = await this.dbContext.Items.ToListAsync(cancellationToken); return items; } } |
- connection string. Use the same name as the DbContext class to load automatically the connection string.
Web.config |
<configuration> <configSections></configSections> <connectionStrings> <add name="MyAppDbContext" providerName="System.Data.SqlClient" connectionString="Server={server-name};Database={db-name};Trusted_Connection=True;MultipleActiveResultSets=true;Connection Timeout=3" /> </connectionStrings> |
- dependency injection of the DbContext in a web api project
App_Start/DryIocConfig.cs |
public static class DryIocConfig { public static void Register(HttpConfiguration config) { var container = new Container().WithWebApi(config, throwIfUnresolved: t => t.IsController()); container.Register<IItemRepository, ItemRepository>(); container.Register<MyAppDbContext>(Reuse.InWebRequest); } } |
Code first
- 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 |
public class ContactContext : DbContext // hérite de DbContext { // DbSet de Contact permet de manipuler une collection de Contact public DbSet<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 |
public class Contact { // relation 0..1 - * entre Address et Contact // un Contact peut avoir 0,1 ou plusieurs addresses. Une adresse appartient à 0 ou 1 Contact. public List<Address> Addresses { get; set; } } public class Address { // Contact étant nullable, une adresse appartient à 0 ou 1 Contact. [Required] // pour spécifier qu'une adresse appartient forcément à 1 Contact public Contact Contact { get; set; } } |
Data Annotations
Contact.cs |
// force le nom de la table [Table("Persons")] public class Contact { // définit ContactId comme clé primaire [Key] public string ContactId { get; set; } // ajoute un index. Nom par défaut: IX_<property name> [Index("AgeIndex")] public int Age { get; set; } // ajoute un index unique. Nom par défaut: IX_<property name> [Index(IsUnique = true)] // longueur max 200 [StringLength(200)] public string Username { get; set; } // Address étant nullable, rend la propriété obligatoire (not null) [Required] public Address Address { get; set; } // propriété à ne pas prendre en compte [NotMapped] public int MyNumber { get; } = 45; // force le nom de la colonne et son type de donnée [Column("ContactDescription", TypeName="ntext")] public string Description { get; set; } |
Fluent API with configuration files
ContactConfiguration.cs |
internal sealed class ContactConfiguration : EntityTypeConfiguration<Contact> { public ContactConfiguration() { this.HasKey(x => x.ContactId); this.HasIndex(x => x.Age).HasName("AgeIndex"); this.HasIndex(x => x.Username).IsUnique(true); this.Property(x => x.Username).HasMaxLength(200); this.HasRequired(x => x.Address); this.Ignore(x => x.MyNumber); this.Property(x => x.Description).HasColumnName("ContactDescription").HasColumnType("ntype"); } } |
MyAppContext.cs |
protected override void OnModelCreating(DbModelBuilder modelBuilder) { // 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 |
public class ContactContext : DbContext { protected override void OnModelCreating(DbModelBuilder modelBuilder) { // 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
ContactContext.cs |
public class ContactContext : DbContext { public override int SaveChanges() { foreach (var contact in ChangeTracker.Entries() .Where(e => e.Entity is Contact && (e.State == EntityState.Added || e.State == EntityState.Modified)) .Select(e => e.Entity as Contact)) { contact.DateModified = DateTime.Now; } return base.SaveChanges(); |
DB first
- génération d'un fichier *.edmx à partir d'une bdd existante
- au run-time, le fichier *.edmx permet la génération du Model
- si la bdd change, il est possible de mettre à jour le fichier *.edmx
Ajouter un nouveau Entity Data Model
Clique-droit dans le projet → Add → New Item → Data → ADO.NET Entity Data Model → EF Designer from database
Connection String dynamique
var entityBuilder = new EntityConnectionStringBuilder(); entityBuilder.Provider = "System.Data.SqlClient"; // Ma Connection String dynamique entityBuilder.ProviderConnectionString = "Data Source=MonServeur;Initial Catalog=MaBdd;Integrated Security=True"; entityBuilder.Metadata = "res://*/MonModel.csdl|res://*/MonModel.ssdl|res://*/MonModel.msl"; Entities entities = new Entities(entityBuilder.ToString()); |
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 (var context = new ContactContext()) { // récupérer des données List<Contact> allContacts = context.Contacts.ToList(); Contact contact = context.Contacts.FirstOrDefault(c => c.Name == "Nicolas"); // avec la primary key Contact contact = context.Contacts.Find(contactId); // équivalent à Contact contact = context.Contacts.SingleOrDefault(c => c.Id == contactId); List<SqlParameter> parameters = this.BuildSqlParameters(...); string parameterNamesList = string.Join(", ", parameters.Select(p => p.ParameterName)); // exécuter un procédure stockée qui renvoie des contacts var contacts = context.Contacts.SqlQuery<List<Contact>>( $"GetContacts {parameterNamesList}", parameters.Cast<object>().ToArray()).ToListAsync(cancellationToken); // exécuter un procédure stockée qui ne renvoie pas de résultat await this.context.Database.ExecuteSqlCommandAsync( $"InsertContacts {parameterNamesList}", cancellationToken, parameters.Cast<object>().ToArray()); // récupérer des contacts ainsi que les numéros de téléphone associés List<Contact> allContacts = context.Contacts.Include(c => c.PhoneNumbers).ToList(); // récupérer des données en mémoire ObservableCollection<Contact> allContacts = context.Contacts.Local; // insérer des données context.Contacts.Add(new Contact() { Name = "Nicolas" }); context.SaveChanges(); // mettre à jour des données Contact contact = 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 is not already added in the database the following exception is raised // InvalidOperationException : The element at index 0 in the collection of objects to refresh is in the added state. // Objects in this state cannot be refreshed. context.Contacts.Refresh(RefreshMode.StoreWins, contact); } |
Initialisation de la bdd
Titre colonne 1 | Titre colonne 2 |
---|---|
CreateDatabaseIfNotExists<TContext> | créé la bdd si elle n'existe pas. Option par défaut. |
MigrateDatabaseToLatestVersion<TContext, Configuration> | 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(new NullDatabaseInitializer<ContactContext>()); // équivalent Database.SetInitializer<ContactContext>(null); |
DataHelpers.cs |
public static class DataHelpers { // recréé la bdd et la rempli public static void NewDbWithSeed() { Database.SetInitializer(new DropCreateDatabaseAlways<ComptesContext>()); using (var context = new ComptesContext()) { context.Comptes.AddRange(new List<Compte>() { new Compte() { Name = "Stan" }, new Compte() { Name = "Kenny" } }); context.SaveChanges(); |
Relations
class Entreprise { public int Id { get; set; } public string 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és public List<Employe> Employes { get; set; } } class Employe { public int Id { get; set; } public string 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] public Entreprise 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 1 public int EntrepriseId { get; set; } } class AppContext : DbContext { public DbSet<Entreprise> Entreprises { get; set; } public DbSet<Employe> Employes { get; set; } } |
// ajout de données dans la bdd Database.SetInitializer(new DropCreateDatabaseAlways<AppContext>()); using (var context = new AppContext()) { // création des entreprise sans initialiser Entreprise.Employes ni renseigner les employés var wc = context.Entreprises.Add(new Entreprise() { Name = "World Companie" }); var sb = context.Entreprises.Add(new Entreprise() { Name = "Super Bank" }); context.Employes.AddRange(new List<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 new Employe() { Name = "Mike", Entreprise = wc }, new Employe() { Name = "Paul", Entreprise = wc }, new Employe() { Name = "Julie", Entreprise = sb }, new Employe() { Name = "Rachelle", Entreprise = sb } }); context.SaveChanges(); |
using (var context = new AppContext()) { // sans Include entreprise.Employes serait null // une autre solution moins performante: mettre entreprise.Employes en virtual foreach (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; |
Configuration
SQL Server Express ou LocalDb
Sans configuration, SQL Server Express est utilisé s'il est installé, sinon LocalDb. |
Web.config |
<!-- SQL Server --> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" /> <!-- LocalDB --> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="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 |
Web.config |
<configuration> <connectionStrings> <add name="ClasseContext" providerName="System.Data.SqlClient" connectionString="data source=PC-NAME\SQLEXPRESS;initial catalog=Namespace.ClasseContext;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" /> |
Migration
|
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 suivante Add-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 bdd Update-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 besoin public partial class Migration-Name : DbMigration { public override void Up() { // 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-SQL Update-Database -Verbose |
Autres
# list the applied migrations get-migrations # il est possible de retourner à n'importe quelle étape de migration Update-Database -TargetMigration: Migration-Name # générer un script de la création complète de la bdd jusqu'à Migration-Name Update-Database -Script -SourceMigration: $InitialDatabase -TargetMigration: Migration-Name # par défaut, SourceMigration: état actuel, et TargetMigration: dernière migration |
Configuration.cs
Configuration.cs |
public Configuration() { // 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. protected override void Seed(ConsoleTestEF.AppContext context) { var e1 = new Entreprise() { Nom = "World Companie" }; var e2 = new Entreprise() { 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à var rnd = new Random(); for (int i = 0; i < 1000; i++) { context.Employes.AddOrUpdate(e => e.Nom, new Employe() { Nom = $"Mr {i}", // utiliser la ForeignKey EntrepriseId, plutôt que la propriété Entreprise EntrepriseId = rnd.Next(2) == 0 ? e1.Id : e2.Id }); } |
Unit tests with EFFORT
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).
using Effort; // create the fake context var connection = DbConnectionFactory.CreateTransient(); var context = new MyAppContext(connection, true); // fill the database context.Database.CreateIfNotExists(); // create the db once for all the tests using (this.Context.Database.Connection.BeginTransaction()) // then use transactions { var item = new Item(); context.Set<Item>().Add(item); await context.SaveChangesAsync(CancellationToken.None); } // create the repository and call the method to test var repository = new ItemRepository(context); var query = new ItemQuery(); var result = await repository.GetAsync(query); |
You can use the ids to link objects between themselves, but when SaveChangesAsync is called all the ids are replaced by the values they have now in the database. |
Error: Incorrect number of arguments for constructor
DbConnectionFactory.LargePropertyCount = 16; // set from 8 to 16 var connection = DbConnectionFactory.CreateTransient(); var context = new MyAppContext(connection, true); |
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'application public class ConnectedRepository { // utilisation d'un seul context private readonly ContactContext _context = new ContactContext(); // méthode pour récupérer les données public List<Contact> GetAllContacts() { return _context.Contacts.ToList(); |
EF et ASP.NET MVC
DisconnectedRepository.cs |
public class DisconnectedRepository { public IEnumerable<Ecriture> GetEcritures() { using (var context = new ComptesContext()) { return context.Ecritures.AsNoTracking().ToList(); } } public Ecriture GetEcriture(int id) { using (var context = new ComptesContext()) { return context.Ecritures.Find(id); // plus performant mais moins lisible return context.Ecritures.AsNoTracking().SingleOrDefault(e => e.Id == id); } } public void Add(Ecriture ecriture) { using (var context = new ComptesContext()) { context.Ecritures.Add(ecriture); context.SaveChanges(); } } public void Edit(Ecriture ecriture) { using (var context = new ComptesContext()) { context.Entry(ecriture).State = EntityState.Modified; context.SaveChanges(); } } public void Delete(int id) { Delete(GetEcriture(id)); } public void Delete(Ecriture ecriture) { using (var context = new ComptesContext()) { context.Entry(ecriture).State = EntityState.Deleted; context.SaveChanges(); } } |
Console application
- add EntityFramework nuget package
Data
Data/Item.cs |
class Item { public int Id { get; set; } public string Name { get; set; } } |
DBContext
Data/ItemDbContext.cs |
class ItemDbContext : DbContext { public ItemDbContext() : base("name=ItemContext") { } public DbSet<Item> Items { get; set; } } |
App.config |
<configuration> <connectionStrings> <add name="ItemContext" connectionString="Server=localhost;Database=Item;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings> </configuration> |
Entities configuration
Data/ItemDbContext.cs |
class ItemDbContext : DbContext { protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Configurations.AddFromAssembly(typeof(ItemDbContext).Assembly); } } |
EntitiesConfiguration/ItemConfiguration.cs |
class ItemConfiguration : EntityTypeConfiguration<Item> { public ItemConfiguration() { this.Property(x => x.Name).IsRequired().HasMaxLength(50); } } |
Migration
- Visual Studio → Package Manager Console
# create a Configuration class Enable-Migrations |
Migrations/Configuration.cs |
internal sealed class Configuration : DbMigrationsConfiguration<ItemDbContext> { public Configuration() { AutomaticMigrationsEnabled = false; } protected override void Seed(ItemDbContext context) { context.Items.AddOrUpdate(new Item { Id = 1, Name = "Item1" }); context.Items.AddOrUpdate(new Item { Id = 2, Name = "Item2" }); } } |
# add a migration Add-Migration IntialCreate # generate the sql script representing the migration Update-Database -script # if it is not what you expected, update the entities and the configuration then run again the add migration Add-Migration IntialCreate # apply the migration by updating the database Update-Database |
Program
Program.cs |
static void Main(string[] args) { using (var db = new ItemDbContext()) { var items = db.Items.ToList(); foreach (var item in items) { Console.WriteLine($@"{item.Id} - {item.Name}"); } } Console.ReadKey(); } |