はじめに
改修を依頼されたソースコードの中に不要な実装を見つけたので、同じような勘違いをされている方への注意喚起の記事です。
前提条件
- .NET Core 2.2
- EntityFramework Core 2.2
ソースコードとその目的
不要な処理が実装されていたのは、タイトル通り『EntityFramework Coreを使って動的にテーブルへ追加、削除を行う。』という部分です。
実際のソースコードは載せることが出来ないので、サンプルのソースコードを作成しました。
モデルクラス
using System.ComponentModel.DataAnnotations.Schema; namespace SampleDao.Models { /// <summary> /// DbEntity interface /// </summary> public interface IDbEntity { object[] Keys { get; } } /// <summary> /// Userテーブルモデル /// </summary> [Table("user")] public class User : IDbEntity { /// <summary> /// UserID /// </summary> [Column("id")] public long Id { get; set; } /// <summary> /// ユーザー名 /// </summary> [Column("username")] public string UserName { get; set; } /// <summary> /// 自己紹介 /// </summary> [Column("introduce")] public string Introduce { get; set; } public object[] Keys { get { return new[] { (object)Id }; } } } }
コンテキストクラス
using Microsoft.EntityFrameworkCore; using SampleDao.Models; namespace SampleDao { /// <summary> /// Sample用 DBコンテキスト /// </summary> public class MyDbContext : DbContext { /// <summary> /// コンストラクタ 新規インスタンスを生成します。 /// </summary> public MyDbContext() : base(new DbContextOptionsBuilder().UseSqlServer("Data source=(localdb)\\SampleDbIns;Database=DB01SAMPLE;User ID=Test;Password=Password;", null).Options) { } /// <summary> Userテーブル </summary> public DbSet<User> User { get; set; } /// <summary> /// 引数に渡されたエンティティを該当するテーブルに対して、追加します。 /// </summary> /// <typeparam name="TSource">対象Entity型</typeparam> /// <param name="entity">対象Entity</param> public void DynamicAdd<TSource>(TSource entity) where TSource : class, IDbEntity { var ctxProp = typeof(MyDbContext).GetProperty(typeof(TSource).Name); var dbSet = (DbSet<TSource>)ctxProp.GetValue(this); dbSet.Add(entity); } /// <summary> /// 引数に渡されたエンティティを該当するテーブルから削除します。 /// </summary> /// <typeparam name="TSource">対象Entity型</typeparam> /// <param name="entity">対象Entity</param> public void DynamicRemove<TSource>(TSource entity) where TSource : class, IDbEntity { var ctxProp = typeof(MyDbContext).GetProperty(typeof(TSource).Name); var dbSet = (DbSet<TSource>)ctxProp.GetValue(this); dbSet.Remove(entity); } /// <summary> /// モデルの生成を行います。 /// </summary> /// <param name="modelBuilder">モデルビルダー</param> protected override void OnModelCreating(ModelBuilder modelBuilder) { #region Usersテーブル modelBuilder.Entity<User>() .HasKey(e => new { e.Id }) .HasName("user_tbl_pk"); modelBuilder.Entity<User>() .Property(e => e.Id) .ValueGeneratedOnAdd(); modelBuilder.Entity<User>() .Property(e => e.UserName) .IsRequired() .HasMaxLength(30); modelBuilder.Entity<User>() .Property(e => e.Introduce) .HasMaxLength(500); #endregion } } }
処理クラス
using SampleDao.Models; using System.Linq; namespace SampleDao { class Program { static void Main(string[] args) { using (var context = new MyDbContext()) { // 動的 追加処理 var user = new User() { UserName = "hogehoeg", Introduce = "sample" }; context.DynamicAdd(user); context.SaveChanges(); } using (var context = new MyDbContext()) { // 動的 削除処理 var user = context.User.Where(x => x.Id == 1).FirstOrDefault(); context.DynamicRemove(user); context.SaveChanges(); } } } }
処理クラス、コンテキストクラスから類推するに、追加・削除処理に関しては、どのようなモデルクラスであってもcontext.DynamicAdd()
メソッドを使用することで、明示的にcontext.User.Add(user)
のような処理を書かなくても良い。という実装を目指していたと思われます。
問題点
このソースコードで問題の部分は、動的処理を行っているDynamicAdd
メソッドとDynamicRemove
メソッドそのものです。
Reflectionを使っているから遅い。とかそういった問題でなはく、動的な追加・削除メソッドはすでにDbContext
に存在しています。
DbContext.Add
メソッド、DbContext.Remove
メソッドがそれに該当します。
DbContext.User.Add(user)
のように、DbSetで設定したテーブルモデルのプロパティを明示的に指定しないと追加、削除が行えないと思っている方がいますが、そんなことはありません。
これらのメソッドを使用することでDbContextがDbSetのプロパティで抱えているテーブルモデルに対して、動的に追加・削除を行うことができます。
そのため、今回実装されているDynamicAdd
メソッドとDynamicRemove
メソッドという2つのメソッドは不要ということになります。
DbContextを使った動的追加・削除
実際に修正したソースコードは下記の通りです。
(修正と言っても、ただ削除しただけですが...)
修正後 コンテキストクラス
using Microsoft.EntityFrameworkCore; using SampleDao.Models; namespace SampleDao { /// <summary> /// Sample用 DBコンテキスト /// </summary> public class MyDbContext : DbContext { /// <summary> /// コンストラクタ 新規インスタンスを生成します。 /// </summary> public MyDbContext() : base(new DbContextOptionsBuilder().UseSqlServer("Data source=(localdb)\\SampleDbIns;Database=DB01SAMPLE;User ID=Test;Password=Password;", null).Options) { } /// <summary> Userテーブル </summary> public DbSet<User> User { get; set; } /// <summary> /// モデルの生成を行います。 /// </summary> /// <param name="modelBuilder">モデルビルダー</param> protected override void OnModelCreating(ModelBuilder modelBuilder) { #region Usersテーブル modelBuilder.Entity<User>() .HasKey(e => new { e.Id }) .HasName("user_tbl_pk"); modelBuilder.Entity<User>() .Property(e => e.Id) .ValueGeneratedOnAdd(); modelBuilder.Entity<User>() .Property(e => e.UserName) .IsRequired() .HasMaxLength(30); modelBuilder.Entity<User>() .Property(e => e.Introduce) .HasMaxLength(500); #endregion } } }
修正後 処理クラス
using SampleDao.Models; using System.Linq; namespace SampleDao { class Program { static void Main(string[] args) { using (var context = new MyDbContext()) { // 動的 追加処理 var user = new User() { UserName = "hogehoeg", Introduce = "sample" }; context.Add(user); context.SaveChanges(); } using (var context = new MyDbContext()) { // 動的 削除処理 var user = context.User.Where(x => x.Id == 1).FirstOrDefault(); context.Remove(user); context.SaveChanges(); } } } }
作成したモデルクラスを直接、context.Add()
、context.Remove
に渡すことで、追加・削除が実行されます。
まとめ
- DbContextには動的追加・削除の機能が備わっている。