One Step Ahead

プログラミングやエンジニアリング全般について書いていきます

EntityFramework Coreを使って動的にテーブルへ追加、削除を行う。

はじめに


改修を依頼されたソースコードの中に不要な実装を見つけたので、同じような勘違いをされている方への注意喚起の記事です。

前提条件


  • .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には動的追加・削除の機能が備わっている。