Liens
Description
- Réécriture complète d'EF
- Plus de nécessite d'utilisé des BdD relationnelles uniquement
|
cd MyProject
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
|
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
|
Data/MyAppContext.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/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; }
}
|
Data/MyContext.cs
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Item>()
.HasIndex(i => i.Name)
.HasName("Index_Name");
|
Data/MyAppContext.cs
|
public class MyAppContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query<MyQueryType>().ToView("MyView");
|
|
# 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
# ajouter le paquet nuget suivant
dotnet add package Microsoft.EntityFrameworkCore.Design
|
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);
}
|
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();
}
}
|
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);
|
|
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
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"
},
|
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.
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
|