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=localhost;Database=MyDb;User=sa;Password=pwd;
|
Sqlite |
Microsoft.EntityFrameworkCore.Sqlite |
Data Source=file.db
|
MySQL / MariaDB |
Pomelo.EntityFrameworkCore.MySql |
Server=localhost;Database=MyDb;User=root;Password=pwd
|
Data/MyAppContext.cs
|
public class MyAppContext : DbContext
{
public MyAppContext(DbContextOptions<MyAppContext> options) : base(options)
{}
public DbSet<Item> Items { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings["SqlServerConnectionString"].ConnectionString);
}
}
|
Data/Entities/Item.cs
|
[Table("Items")]
public class Item
{
public int Id { get; set; }
[Column("My_Name")]
public string Name { get; set; }
[Column(TypeName = "decimal(7, 2)")]
public decimal Price { get; set; }
[Column(TypeName = "date")]
public DateTime Date { get; set; }
}
|
Nécessite le package nuget System.ComponentModel.DataAnnotations
EntityTypeConfiguration
DataAccess/DbModelConfiguration/ItemConfiguration.cs
|
internal sealed class ItemConfiguration : IEntityTypeConfiguration<Item>
{
public void Configure(EntityTypeBuilder<Item> builder)
{
builder.HasIndex(e => e.Name)
.HasName("Index_Name");
builder.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50);
|
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");
|
|
dotnet ef
|
EF Core 3.x
|
dotnet tool install --global dotnet-ef
dotnet tool update --global dotnet-ef
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>
|
Configuration
Startup.cs
|
using Microsoft.EntityFrameworkCore;
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SqlServerConnectionString2"))
);
services.AddDbContext<AppContext>(options =>
options.UseInMemoryDatabase("WebApplicationCoreMemoryDb");
);
services.AddDbContext<AppDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("MySqlConnectionString"), mySqlOptions => mySqlOptions.ServerVersion(
new ServerVersion(new Version(10, 4, 11),
ServerType.MariaDb)))
);
|
appsettings.json
|
{
"ConnectionStrings": {
"SqlServerConnectionString1": "Server=(localdb)\\MSSQLLocalDB;Database=MyDb;Integrated Security=True;MultipleActiveResultSets=True",
"SqlServerConnectionString2": "Server=localhost;Database=MyDb;User=sa;Password=pwd;",
"MySqlConnectionString": "Server=localhost;Database=MyDb;User=root;Password=pwd;",
"SqliteConnectionString": "Data Source=MyDb.db"
}
}
|
Startup.cs
|
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyDbContext context)
{
if (env.IsDevelopment())
{
context.Database.EnsureCreated();
context.Database.Migrate();
}
}
|
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" });
_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();
}
}
|
CLI
Code first
|
dotnet ef migrations add InitialCreate
dotnet ef migrations remove
dotnet ef migrations script <FROM> <TO>
dotnet ef database update
|
Db first
|
dotnet ef dbcontext scaffold "Server=localhost;Database=Item;User=sa;Password=pwd" Microsoft.EntityFrameworkCore.SqlServer -o Model
|
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);
}
|
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> GetAll()
{
return _context.Items;
}
public Item Get(int id)
{
return _context.Find<Item>(id);
}
public Item Add(Item item)
{
var addedEntity = _context.Add(item);
_context.SaveChanges();
return addedEntity.Entity;
}
public void Remove(int id)
{
var itemToRemove = Get(id);
if (itemToRemove == null) return;
_context.Remove(itemToRemove);
_context.SaveChanges();
}
public void Update(int id, Item item)
{
var itemToUpdate = Get(id);
if (itemToUpdate == null) return null;
itemToUpdate.Name = item.Name;
_context.SaveChanges();
return itemToUpdate;
}
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
{
IEnumerable<Item> GetAll();
Item Get(int id);
Item Add(Item item);
void Remove(int id);
Item Update(int id, Item item);
bool SaveAll();
}
|
Startup.cs
|
public void ConfigureServices(IServiceCollection services)
{
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.
|
IEnumerable<Contact> allContacts = context.Contacts;
foreach (var contact in allContacts)
{
if (contact.age > 30) { }
}
IEnumerable<Contact> allContacts = context.Contacts.Include(c => c.Age);
|
|
using System.Data.SqlClient;
var books = context.Books.FromSql("SELECT Id, Title FROM Books").ToList();
var book = context.Books.FromSql($"SELECT Id, Title FROM Books WHERE Title = {title}").FirstOrDefault();
var p1 = new SqlParameter("@Id", id);
var book = db.Books.FromSql("SELECT * FROM Books WHERE Id = @Id", p1).FirstOrDefault();
var commandText = "INSERT Authors (AuthorName) VALUES (@AuthorName)";
var name = new SqlParameter("@AuthorName", "Jean-Christophe Grangé");
context.Database.ExecuteSqlCommand(commandText, name);
var authorId = new SqlParameter("@AuthorId", 1);
var books = context.Books.FromSql("EXEC GetBooksByAuthor @AuthorId" , authorId).ToList();
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);
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">
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>
|
|
dotnet restore
|