« Entity Framework Core » : différence entre les versions
Ligne 236 : | Ligne 236 : | ||
1 {{boxx|Repository}} par classe (Item), 1 {{boxx|DbContext}} pour toute l'application.<br> | 1 {{boxx|Repository}} par classe (Item), 1 {{boxx|DbContext}} pour toute l'application.<br> | ||
{{boxx|Repository}} peut aussi être nommé {{boxx|Service}}. | {{boxx|Repository}} peut aussi être nommé {{boxx|Service}}. | ||
<filebox fn='Data/ | <filebox fn='Data/ItemRepository.cs' collapsed> | ||
public class ItemRepository : IItemRepository | public class ItemRepository : IItemRepository | ||
{ | { | ||
Ligne 283 : | Ligne 283 : | ||
La création d'une interface permettra d'utiliser d'autres sources de données pour faire des tests. | La création d'une interface permettra d'utiliser d'autres sources de données pour faire des tests. | ||
<filebox fn='Data\ | <filebox fn='Data\IItemRepository.cs'> | ||
public interface | public interface IItemRepository | ||
{ | { | ||
IList<Item> GetAllItems(); | IList<Item> GetAllItems(); | ||
Ligne 295 : | Ligne 295 : | ||
{ | { | ||
// Add database services. | // Add database services. | ||
services.AddDbContext< | services.AddDbContext<MyAppContext>(options => | ||
options.UseSqlServer("MyConnectionString") | options.UseSqlServer("MyConnectionString") | ||
); | ); | ||
Ligne 308 : | Ligne 308 : | ||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); | ||
services.AddScoped< | services.AddScoped<IItemRepository, ItemRepository>(); | ||
</filebox> | </filebox> | ||
<filebox fn='MyController.cs'> | <filebox fn='MyController.cs'> | ||
private readonly | private readonly IItemRepository _itemRepository; | ||
public MyController( | public MyController(IItemRepository itemRepository) | ||
{ | { | ||
_itemRepository = itemRepository; | |||
} | } | ||
public IActionResult Index() | public IActionResult Index() | ||
{ | { | ||
var model = | var model = _itemRepository.GetAllItems(); | ||
return View(model); | return View(model); | ||
} | } |
Version du 14 janvier 2020 à 21:52
Liens
- Getting Started with EF Core on .NET Core Console App with a New database
- Getting started with ASP.NET Core and Entity Framework Core using Visual Studio
- ASP.NET Core 2.0 with SQLite and Entity Framework Core
- Utilisation
- Migration
Description
- Réécriture complète d'EF
- Plus de nécessite d'utilisé des BdD relationnelles uniquement
Ajouter les packages au projet
cd MyProject dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Design |
Data Providers
Provider | Package NuGet | Connection String |
---|---|---|
SQL Server | Microsoft.EntityFrameworkCore.SqlServer | Server=(localdb)\\MSSQLLocalDB;Database=MyDb;Integrated Security=True;MultipleActiveResultSets=True; Server=myserver;Database=mydatabase;User Id=sa;Password=xxx; |
Sqlite | Microsoft.EntityFrameworkCore.Sqlite | Data Source=file.db |
MySQL | Pomelo.EntityFrameworkCore.MySql MySql.Data.EntityFrameworkCore |
server=localhost;database=MyDb;user=user;password=password |
Définition du Context et création des classes Entity
Data/MyContext.cs |
public class MyAppContext : DbContext { // permet de passer des options à la construction du DbCDbContextontext public MyAppContext(DbContextOptions<MyAppContext> options) : base(options) {} public DbSet<Item> Items { get; set; } // pour ASP.NET Core, préférer la configuration dans Startup.ConfigureServices protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings["SqlServerConnectionString"].ConnectionString); } } |
Data Annotations Attributes
Data/Entities/Item.cs |
// définit le nom de la table, par défaut le nom de la classe est utilisées [Table("Items")] public class Item { public int Id { get; set; } [Column("My_Name")] // mapping public string Name { get; set; } // définit le type SQL qui sera utilisé pour le stockage de la donnée, ici un décimal de 7 chiffres dont 2 après la virgule [Column(TypeName = "decimal(7, 2)")] public decimal Price { get; set; } [Column(TypeName = "date")] public DateTime Date { get; set; } } |
Index
Data/MyContext.cs |
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Item>() .HasIndex(i => i.Name) .HasName("Index_Name"); |
Vue/view et QueryType
Data/MyContext.cs |
public class MyAppContext : DbContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Query<MyQueryType>().ToView("MyView"); |
Entity Framework Core Tools
# tester si Entity Framework Core Tools est bien installé et fonctionnel dotnet ef # bien se mettre dans le dossier du projet # faire un dotnet restore au cas où la restoration n'aurait pas fonctionnée |
EF Core 3.x
# dotnet ef must be installed as a global or local tool dotnet tool install --global dotnet-ef # installed in ~/.dotnet/tools # Add ~/.dotnet/tools to PATH |
ASP.NET Core 2.1+
Plus besoin depuis dotnet core 2.1 (lien) |
Si le fichier de projet ne contient pas Microsoft.EntityFrameworkCore.Tools.DotNet, l'ajouter. (version)
À la sauvegarde NuGet va restaurer les paquets nécessaires.
MyProject.csproj |
<ItemGroup> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.3" /> </ItemGroup> |
CLI
# création du fichier de migration dotnet ef migrations add InitialCreate # un fichier YYYYMMDDHHMMSSS_IntialCreate.cs est créé dans le dossier Migration # supprimer la dernière migration dotnet ef migrations remove # génération et exécution du script SQL dotnet ef database update # génération d'un script SQL dotnet ef migrations script <FROM> <TO> # From: 0 par défaut # To: dernière migration par défaut |
Configuration
Startup.cs |
using Microsoft.EntityFrameworkCore; public void ConfigureServices(IServiceCollection services) { // SQL Server services.AddDbContext<AppContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("SqlServerConnectionString2")); }); // In Memory services.AddDbContext<AppContext>(options => { options.UseInMemoryDatabase("WebApplicationCoreMemoryDb"); }); |
appsettings.json |
{ "ConnectionStrings": { "SqlServerConnectionString1": "Server=(localdb)\\MSSQLLocalDB;Database=MyDb;Integrated Security=True;MultipleActiveResultSets=True", "SqlServerConnectionString2": "Server=localhost;Database=Comptes;User Id=sa;Password=ppp;", "MySqlConnectionString": "Server=localhost;database=testdb;uid=uuu;pwd=ppp;", "SqliteConnectionString": "Data Source=testdb.db" } } |
Utilisation dans le controller
MyController.cs |
private readonly MyAppContext _context; public MyController(MyAppContext context) { _context = context; } public IActionResult Index() { var model = _context.Items.ToList(); return View(model); } [HttpGet] [ProducesResponseType(200, Type = typeof(IEnumerable<Item>))] public IActionResult Get() { return Ok(_context.Items); } |
Data Seeding
Permet de remplir la bdd avec un jeu initial de données.
Data/MyAppContext.cs |
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Item>().HasData( new Item() { Name = "Item1" }, new Item() { Name = "Item2" }); } |
OLD
Data/MyAppSeeder.cs |
public class MyAppSeeder { private readonly MyAppContext _context; public MyAppSeeder(MyAppContext context) { _context = context; } public void Seed() { _context.Database.EnsureCreated(); if (!_context.Items.Any()) { _context.Items.Add(new Item() { Name = "Item 1" }); // ne pas définir l'Id _context.Items.Add(new Item() { Name = "Item 2" }); _context.SaveChanges(); } |
Sartup.cs |
public void ConfigureServices(IServiceCollection services) { services.AddTransient<MyAppSeeder>(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { using (var scope = app.ApplicationServices.CreateScope()) { var seeder = scope.ServiceProvider.GetService<MyAppSeeder>(); seeder.Seed(); } } |
Repository Pattern
1 Repository par classe (Item), 1 DbContext pour toute l'application.
Repository peut aussi être nommé Service.
Data/ItemRepository.cs |
public class ItemRepository : IItemRepository { private readonly MyAppContext _context; public ItemRepository(MyAppContext context) { _context = context; } public IEnumerable<Item> GetAllItems() { return _context.Items; } public Item Get(int id) { return _context.Find<Item>(id); } public void Add(Item item) { _context.Add(item); _context.SaveChanges(); } public void Remove(int id) { _context.Remove(GetItem(id)); _context.SaveChanges(); } public void Update(int id, Item item) { var itemToUpdate = GetItem(id); itemToUpdate.Name = item.Name; _context.SaveChanges(); } public bool SaveAll() { return _context.SaveChanges() > 0; } } |
La création d'une interface permettra d'utiliser d'autres sources de données pour faire des tests.
Data\IItemRepository.cs |
public interface IItemRepository { IList<Item> GetAllItems(); bool SaveAll(); } |
Startup.cs |
public void ConfigureServices(IServiceCollection services) { // Add database services. services.AddDbContext<MyAppContext>(options => options.UseSqlServer("MyConnectionString") ); // Add framework services. services.Configure<CookiePolicyOptions>(options => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddScoped<IItemRepository, ItemRepository>(); |
MyController.cs |
private readonly IItemRepository _itemRepository; public MyController(IItemRepository itemRepository) { _itemRepository = itemRepository; } public IActionResult Index() { var model = _itemRepository.GetAllItems(); return View(model); } |
N+1 selects problem
EF fait ses requêtes en lazy loading, ce qui veut dire que les requêtes SQL ne sont exécutées que lorsqu'on a besoin des données.
// 1 requête pour récupérer tous les contacts IEnumerable<Contact> allContacts = context.Contacts; foreach (var contact in allContacts) { // pour récupérer l'age de chaque contact il faut faire une nouvelle requête pour chaque contact // ce qui donne de mauvaises performances if (contact.age > 30) { /* ... */ } } // inclure Age lors de la récupération des contacts // ainsi tous se fait en une seule requête IEnumerable<Contact> allContacts = context.Contacts.Include(c => c.Age); |
Executing Raw SQL Queries
using System.Data.SqlClient; // pour SqlParameter var books = context.Books.FromSql("SELECT Id, Title FROM Books").ToList(); // passage de paramètre avec l'interpolation de string var book = context.Books.FromSql($"SELECT Id, Title FROM Books WHERE Title = {title}").FirstOrDefault(); // passage de paramètre avec DbParameter var p1 = new SqlParameter("@Id", id); var book = db.Books.FromSql("SELECT * FROM Books WHERE Id = @Id", p1).FirstOrDefault(); // opérations INSERT, UPDATE, DELETE var commandText = "INSERT Authors (AuthorName) VALUES (@AuthorName)"; var name = new SqlParameter("@AuthorName", "Jean-Christophe Grangé"); context.Database.ExecuteSqlCommand(commandText, name); // procédure stockée var authorId = new SqlParameter("@AuthorId", 1); var books = context.Books.FromSql("EXEC GetBooksByAuthor @AuthorId" , authorId).ToList(); // procédure stockée avec plusieurs paramètres var parameters = new List<SqlParameter>(); parameters.Add(new SqlParameter(name, SqlDbType.Int) { Value = (object)value?.ToInt32(CultureInfo.InvariantCulture) ?? DBNull.Value }); var parameterNamesList = string.Join(", ", parameters.Select(p => p.ParameterName)); var storedProcedureResultRows = await context.Database.SqlQuery<StoredProcedureResultRow>( $"{StoredProcedureName} {parameterNamesList}", parameters.Cast<object>().ToArray()).ToListAsync(cancellationToken); |
Erreurs
Cannot authenticate using Kerberos
Erreur sur Linux durant l'exécution de dotnet ef database update. Impossible de se connecter au serveur SQL.
Passer Trusted_Connection à False et ajouter le user et password à la connection string corrige le problème.
appsettings.json |
"ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=MyDb;Trusted_Connection=False;MultipleActiveResultSets=true;User=sa;Password=xxx" }, |
Specified key was too long; max key length is 767 bytes
Les index InnoDB (MySQL) ont une taille limitée. Il faut donc limité la taille des index à la création de la bdd.
Data/MyAppContext.cs |
protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); // Shorten key length for Identity builder.Entity<IdentityRole>(entity => { entity.Property(m => m.NormalizedName).HasMaxLength(127); }); builder.Entity<IdentityUser>(entity => { entity.Property(m => m.NormalizedUserName).HasMaxLength(127); }); } |
The host localhost does not support SSL connections
Ajouter SslMode=none à la ConnectionString MySQL.
OLD - No executable found matching command "dotnet-ef"
Installer les packages Nuget:
- Microsoft.EntityFrameworkCore.Tools
- Microsoft.EntityFrameworkCore.Tools.DotNet
MyProject.csproj |
<Project Sdk="Microsoft.NET.Sdk"> <!-- Ajouter le groupe suivant --> <ItemGroup> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" /> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" /> </ItemGroup> |
dotnet restore |