いつもどこかでデスマーチ♪

不定期に、私の日常を書き込みしていきます。

【個人的メモ】C# .netCore3.1 と EntityFrameworkCore3.1 と SQLite3 と NLog の連携基礎

2020年07月04日 17時34分04秒 | .NET系
.netCore3.1 と EntityFrameworkCore3.1.5 と SQLite3 と NLog4.7.2 で連携する場合の基礎クラス

Myルール:
1.基本的には短いスコープで使用する
2.必ずusing を使用すること
3.処理の更新データが不要 かつ 処理が思う長い場合、継承して作ること
4.他処理のデータ更新を反映する必要がある場合は、短いスコープ範囲で処理すること
 (Context を new したタイミング(正確には違うが…)で未連携状態になるため)


機能:
1.DB接続
2.SQLログ出力
3.追加日時の自動追加
4.更新日時の自動追加
5.DBの自動作成
6.DBの再作成
7.Trancate(SQLiteなのでDelete) メソッド
8.統計情報更新機能

サンプル機能:
1.DBSetの書き方
2.PKの指定方法
3.Indexの作成方法
4.固定データの初期登録機能



SampleContext クラス
namespace Sample.Models
{
    /// <summary>
    /// DBアクセスコンテキスト
    /// </summary>
    class SampleContext : DbContext
    {
        /// <summary>
        /// ログクラス
        /// </summary>
        protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();

        /// <summary>
        /// DBファイルの保存先パス
        /// </summary>
        const string DataBase_FilePath = @"./Sample.db";

        /// <summary>
        /// Sampleテーブル
        /// </summary>
        internal DbSet<SampleEntity> Samples { get; set; }

        #region DB接続情報
        /// <summary>
        /// 接続先情報を指定します。
        /// </summary>
        /// <param name="optionsBuilder">オプション設定クラス</param>
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            // DBログ
            var loggerFactory = LoggerFactory.Create(builder =>
            {
                builder.AddProvider(new LoggerProvider())
                       .AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name &&
                                                       level == LogLevel.Information);
            });

            var connectionString = new SqliteConnectionStringBuilder { DataSource = DataBase_FilePath }.ToString();
            optionsBuilder.UseSqlite(new SqliteConnection(connectionString))
                          .EnableSensitiveDataLogging()
                          .UseLoggerFactory(loggerFactory);
        }
        #endregion DB接続情報

        /// <summary>
        /// DB反映時の事前処理
        /// </summary>
        /// <param name="acceptAllChangesOnSuccess"></param>
        /// <returns>更新数</returns>
        public override int SaveChanges(bool acceptAllChangesOnSuccess)
        {
            // 追加日時、更新日時を設定
            SetAddAndUpdateDateColumn();

            return base.SaveChanges(acceptAllChangesOnSuccess);
        }

        /// <summary>
        /// DB反映時の事前処理
        /// </summary>
        /// <param name="acceptAllChangesOnSuccess"></param>
        /// <param name="cancellationToken"></param>
        /// <returns>タスク</returns>
        public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
        {
            // 追加日時、更新日時を設定
            SetAddAndUpdateDateColumn();

            return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
        }

        /// <summary>
        /// 追加日時、更新日時を設定します
        /// </summary>
        void SetAddAndUpdateDateColumn()
        {
            try
            {
                // 追加データの一覧取得
                var AddedEntities = ChangeTracker.Entries()
                                                 .Where(e => e.State == EntityState.Added)
                                                 .ToList();
                var nowDate = DateTime.Now;
                // 簡単な処理なのでスレッド数制限しない
                Parallel.ForEach(AddedEntities, e =>
                {
                    // 追加日時を設定
                    if (e.Property("AddDate").CurrentValue == null)
                    {
                        e.Property("AddDate").CurrentValue = nowDate;
                    }
                    // 更新日時を設定
                    if (e.Property("UpdateDate").CurrentValue == null)
                    {
                        e.Property("UpdateDate").CurrentValue = nowDate;
                    }
                });

                // 更新データの一覧を取得
                var EditedEntities = ChangeTracker.Entries()
                                                  .Where(e => e.State == EntityState.Modified)
                                                  .ToList();
                // 簡単な処理なのでスレッド数制限しない
                Parallel.ForEach(EditedEntities, e =>
                {
                    // 追加日時を設定
                    if (e.Property("AddDate").CurrentValue == null)
                    {
                        e.Property("AddDate").CurrentValue = nowDate;
                    }
                    // 更新日時を設定
                    e.Property("UpdateDate").CurrentValue = nowDate;
                });
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "追加日時、更新日時の設定に失敗しました。");
            }
        }

        /// <summary>
        /// データベースファイルを初期化します。
        /// 既に初期化済みの場合は何も行いません。
        /// </summary>
        internal async void InitializeDataBase()
        {
            // テーブルを作成
            Logger.Info("データベースファイルを新規作成します。");
            await Database.EnsureCreatedAsync();

            // 初期データ投入
            InsertMasterData();
        }

        /// <summary>
        /// DBファイルを作り直します。
        /// </summary>
        internal void ReInitializeDataBase()
        {
            if (File.Exists(DataBase_FilePath))
            {
                Logger.Warn("データベースファイルを削除します。");

                Database.CloseConnection();
                File.Delete(DataBase_FilePath);
            }

            // 初期化処理を実行
            InitializeDataBase();
        }

        /// <summary>
        /// DBに作成するテーブル情報を設定します。
        /// </summary>
        /// <param name="modelBuilder">モデル作成クラス</param>
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // テーブル作成処理
            SampleEntityInfo(modelBuilder);
        }

        /// <summary>
        /// サンプルテーブルの情報を指定します
        /// </summary>
        /// <param name="modelBuilder">モデル作成クラス</param>
        private void SampleEntityInfo(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<SampleEntity>(
                e =>
                {
                    // PKの指定
                    e.HasKey(c => new { c.Id }).HasName("PK_Sample_Id");
                    // Indexの作成
                    e.HasIndex(c => new { c.SampleA, c.SampleB });
                });
        }

        /// <summary>
        /// 初期データを投入します。
        /// </summary>
        private async void InsertMasterData()
        {
            try
            {
                // トランザクション開始
                await Database.BeginTransactionAsync();

                // 初期データを作成
                InsertMasterSampleData();

                await SaveChangesAsync();
                Database.CommitTransaction();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "初期データ投入に失敗しました。");
                Database.RollbackTransaction();
            }
        }

        /// <summary>
        /// サンプルデータの初期データを作成します。
        /// </summary>
        private void InsertMasterSampleData()
        {
            var tmp = new SampleEntity();

            if (!Samples.Where(x => x.Id == tmp.Id).Any())
            {
                Samples.Add(new SampleEntity
                {
                    Id = 1,
                    SampleA = "",
                    SampleB = ""
                });
            }
        }

        /// <summary>
        /// <para>テーブルの全で他を削除します。</para>
        /// <para>SQLite は Truncateが無いため、Delete分を流します</para>
        /// <para>EntityにTable Attribute が指定されている前提です</para>
        /// <para>SQL直なのでDBSetと整合性が取れなくなるので、処理前(new直後等)のみの仕様に制限してください。</para>
        /// </summary>
        /// <param name="type">Entityのタイプ</param>
        public void Truncate(Type type)
        {
            bool isDeleted = false;

            Attribute[] attrs = Attribute.GetCustomAttributes(type);
            foreach (Attribute attr in attrs)
            {
                if (attr is TableAttribute table)
                {
                    Database.ExecuteSqlRaw($"DELETE FROM {table.Name};");
                    isDeleted = true;
                    break;
                }
            }

            if (!isDeleted)
            {
                // テーブル属性が無い場合はクラス名で削除しに行く
                Database.ExecuteSqlRaw($"DELETE FROM {type.Name};");
            }
        }

        /// <summary>
        /// <para>テーブル情報の統計情報を更新します</para>
        /// </summary>
        /// <param name="type">Entityのタイプ</param>
        public void Analyze(Type type)
        {
            bool isDeleted = false;

            Attribute[] attrs = Attribute.GetCustomAttributes(type);
            foreach (Attribute attr in attrs)
            {
                if (attr is TableAttribute table)
                {
                    Database.ExecuteSqlRaw($"Analyze {table.Name};");
                    isDeleted = true;
                    break;
                }
            }

            if (!isDeleted)
            {
                // テーブル属性が無い場合はクラス名で解析する
                Database.ExecuteSqlRaw($"Analyze {type.Name};");
            }
        }
    }
}





LoggerProvider クラス
using Microsoft.Extensions.Logging;

namespace ChannelAssist.Models.Logger
{
    /// <summary>
    /// ログ出力プロバイダ
    /// </summary>
    class LoggerProvider : ILoggerProvider
    {
        /// <summary>
        /// ログクラス作成
        /// </summary>
        /// <param name="className"></param>
        /// <returns>Microsoft用ログクラス</returns>
        public ILogger CreateLogger(string className)
        {
            return new LoggerSQL();
        }

        /// <summary>
        /// オブジェクトの破棄
        /// </summary>
        public void Dispose() { 
        }
    }
}





LoggerSQL クラス
using Microsoft.Extensions.Logging;
using System;

namespace ChannelAssist.Models.Logger
{
    /// <summary>
    /// SQLログを出力するクラス
    /// </summary>
    class LoggerSQL : ILogger
    {
        /// <summary>
        /// NLogクラス
        /// </summary>
        private readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TState"></typeparam>
        /// <param name="state"></param>
        /// <returns></returns>
        public IDisposable BeginScope<TState>(TState state)
        {
            return null;
        }

        /// <summary>
        /// ログ出力可否
        /// </summary>
        /// <param name="logLevel"></param>
        /// <returns>true:ログ出力可能、false:ログ出力不可</returns>
        public bool IsEnabled(LogLevel logLevel)
        {
            bool result = false;

            if (Logger.IsDebugEnabled)
            {
                // SQLログはデバッグの時のみ出力したい
                result = true;
            }

            return result;
        }

        /// <summary>
        /// ログ出力
        /// </summary>
        /// <typeparam name="TState"></typeparam>
        /// <param name="logLevel"></param>
        /// <param name="eventId"></param>
        /// <param name="state"></param>
        /// <param name="exception"></param>
        /// <param name="formatter"></param>
        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            // SQLはDebugとして出力する
            Logger.Debug(exception, formatter(state, exception));
        }
    }
}




検索用:C# .net Core 3.1 Entity Framework Core 3.1 SQLite3 NLog 連携 ログ出力 やり方 作り方 共通化
コメント    この記事についてブログを書く
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする
« .net Core 3.1 は まだ早かっ... | トップ | iPhone と +メッセージ »

コメントを投稿

ブログ作成者から承認されるまでコメントは反映されません。

.NET系」カテゴリの最新記事