この記事では、EF Core の SQLite、インメモリプロバイダー、および EF.Functions.Contains メソッドの改善点をご紹介します。
これは EF Core 6 の新機能をまとめた第4回目の記事です。
1 SQLite が DateOnly と TimeOnly をサポート
EF Core 6.0 では、SQLite プロバイダーが新しい DateOnly 型と TimeOnly 型をサポートします。これらは TEXT として保存されます。
using var context = new ExampleContext();
var query1 = context.People
.Where(p => p.Birthday < new DateOnly(2000, 1, 1))
.ToQueryString();
Console.WriteLine(query1);
// SELECT "p"."Id", "p"."Birthday", "p"."Name"
// FROM "People" AS "p"
// WHERE "p"."Birthday" < '2000-01-01'
var query2 = context.Notifications
.Where(n => n.AllowedFrom >= new TimeOnly(8, 0) && n.AllowedTo <= new TimeOnly(16, 0))
.ToQueryString();
Console.WriteLine(query2);
// SELECT "n"."Id", "n"."AllowedFrom", "n"."AllowedTo"
// FROM "Notifications" AS "n"
// WHERE("n"."AllowedFrom" >= '08:00:00') AND("n"."AllowedTo" <= '16:00:00')
class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateOnly Birthday { get; set; }
}
class Notification
{
public int Id { get; set; }
public TimeOnly AllowedFrom { get; set; }
public TimeOnly AllowedTo { get; set; }
}
class ExampleContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Notification> Notifications { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite(@"Data Source=Db\DateOnlyTimeOnly.db");
}
2 SQLite 接続のプーリング
SQLite データベースはファイルです。そのため、ほとんどの場合、接続の作成は高速です。しかし、暗号化されたデータベースへの接続を開くのは非常に遅くなります。そこで EF Core 6 では、他のデータベースプロバイダーと同様に、SQLite 接続がプールされるようになりました。
class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
class ExampleContext : DbContext
{
public DbSet<Person> People { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite("Data Source=EncryptedDb.db;Mode=ReadWriteCreate;Password=password");
}
3 SQLite でのコマンドタイムアウト
EF Core 6 の SQLite では、接続文字列に Command Timeout コマンドを追加できるようになりました。これを使用して、SQLite のデフォルトのタイムアウトを指定できます。
class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
class ExampleContext : DbContext
{
public DbSet<Person> People { get; set; }
// 接続によって作成されるコマンドのデフォルトタイムアウトを60秒に設定
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite("Data Source=Test.db;Command Timeout=60");
}
4 SQLite でのセーブポイント
EF Core 6.0 では、SQLite がセーブポイントをサポートします。セーブポイントの保存、ロールバック、および解放が可能です。
var dbPath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..\\..\\..\\Savepoints.db"));
using var connection = new SqliteConnection($"Data Source={dbPath}");
connection.Open();
using var transaction = connection.BeginTransaction();
// この挿入はデータベースにコミットされます
using (var command = connection.CreateCommand())
{
command.CommandText = @"INSERT INTO People (Name) VALUES ('Oleg')";
command.ExecuteNonQuery();
}
transaction.Save("MySavepoint");
// トランザクションをコミットする前にセーブポイントがロールバックされるため、この更新はコミットされません
using (var command = connection.CreateCommand())
{
command.CommandText = @"UPDATE People SET Name = 'Not Oleg' WHERE Id = 1";
command.ExecuteNonQuery();
}
transaction.Rollback("MySavepoint");
transaction.Commit();
5 インメモリデータベースでの必須プロパティの検証
EF Core 6.0 では、インメモリデータベースが必須プロパティを検証するようになりました。エンティティの必須プロパティに null 値を保存しようとすると、例外が発生します。必要に応じて、この検証を無効にすることもできます。
using var context = new ExampleContext();
var blog = new Blog();
context.Blogs.Add(blog);
await context.SaveChangesAsync();
// 未処理の例外: Microsoft.EntityFrameworkCore.DbUpdateException:
// エンティティ型 'Blog' のインスタンスに対して、キー値 '{Id: 1}' で必須プロパティ '{'Title'}' が欠落しています。
class Blog
{
public int Id { get; set; }
[Required]
public string Title { get; set; }
}
class ExampleContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options
.EnableSensitiveDataLogging()
.LogTo(Console.WriteLine, new[] { InMemoryEventId.ChangesSaved })
.UseInMemoryDatabase("ValidateRequiredProps");
// 検証を無効にする場合
// .UseInMemoryDatabase("ValidateRequiredProps", b => b.EnableNullChecks(false));
}
6 EF.Functions.Contains メソッド
EF Core 6.0 では、EF.Functions.Contains メソッドを使用して、値コンバーターを使用してマッピングされた列(バイナリ列も可)を処理できます。
using var context = new ExampleContext();
var query = context.People
.Where(e => EF.Functions.Contains(e.FullName, "Oleg"))
.ToQueryString();
Console.WriteLine(query);
// SELECT[p].[Id], [p].[FullName]
// FROM[People] AS[p]
// WHERE CONTAINS([p].[FullName], N'Oleg')
class Person
{
public int Id { get; set; }
public FullName FullName { get; set; }
}
public class FullName
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class ExampleContext : DbContext
{
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.Property(x => x.FullName)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<FullName>(v, (JsonSerializerOptions)null));
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFCore6Contains");
}
7 おわりに
この記事のすべてのコードサンプルは、私の GitHub にあります: https://github.com/okyrylchuk/dotnet6_features/tree/main/EF%20Core%206#miscellaneous-enhancements
原文: https://blog.okyrylchuk.dev/entity-framework-core-6-features-part-3
著者: Oleg Kyrylchuk
翻訳: 精致码农