2018年4月4日 星期三

Entity Framework 資料異動寫入Log

Log寫得好,晚上睡覺睡得好
因為目前都在用Entity Framework來處理DB,想很久Log要怎麼寫比較好
在每個Method SaveChange前多一行寫Log的動作?
這樣做非常累也不是一勞永逸的方法,只要有新的CRUD,你都必須要每個都加上去
那麼新進人員進來,不知道要加這一行來寫Log不就糗了

所以我目前想到最好的方式就是覆寫EF的SaveChange的方法
加在裡面的好處就是,只要你是用EF來CRUD,我就一定會處理的到
新進人員也不需要處理寫Log這一塊,因為已經包在底層了

所以開了3個資料表
LogEntityRef 是存放什麼資料表需要寫入Log
LogEntityField 是存放資料表的那些欄位需要寫入Log
LogEntityValue 是寫入Log的資料

三個資料表可參考下圖


那麼怎麼覆寫EF的SaveChange呢,創建一個新的Class要與EF的名稱同名

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SportBook.Entity
{
    public partial class sbkEntities : DbContext
    {
        private string sysName = "SYSTEM";
        public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            SetValuesOnBeforeSaveChanges();
            return PrivateSaveChangesAsync(cancellationToken);
        }

        public override Task<int> SaveChangesAsync()
        {
            SetValuesOnBeforeSaveChanges();
            return PrivateSaveChangesAsync(CancellationToken.None);
        }

        public override int SaveChanges()
        {
            SetValuesOnBeforeSaveChanges();
            return PrivateSaveChangesAsync(CancellationToken.None).Result;
        }

        /// <summary>
        /// SaveChange之前把必要欄位寫入
        /// </summary>
        private void SetValuesOnBeforeSaveChanges()
        {            
            var trackerEntries = this.ChangeTracker.Entries()
                    .Where(x => x.State == EntityState.Modified || x.State == EntityState.Added).ToList();
            foreach (var entry in trackerEntries)
            {
                switch (entry.State)
                {
                    case EntityState.Modified:
                        entry.CurrentValues.SetValues(new { UserUpdated = sysName, DateUpdated = DateTime.UtcNow });
                        break;
                    case EntityState.Added:
                        entry.CurrentValues.SetValues(new { UserCreated = sysName, DateCreated = DateTime.UtcNow });
                        break;
                }
            }
        }

        /// <summary>
        /// SaveChange之後開始寫Log
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<int> PrivateSaveChangesAsync(CancellationToken cancellationToken)
        {
            ObjectContext context = ((IObjectContextAdapter)this).ObjectContext;
            await context.SaveChangesAsync(SaveOptions.DetectChangesBeforeSave, cancellationToken).ConfigureAwait(false);

            var trackerEntries = this.ChangeTracker.Entries()
                .Where(x => x.State == EntityState.Modified || x.State == EntityState.Added).ToList();

            var LogValue = new List<Entity.LogEntityValue>();
            foreach (var entry in trackerEntries)
            {
                var entityName = entry.Entity.GetType().Name;
                //判斷entityName是否需要寫入Log
                var LogEntityRef = this.LogEntityRef.AsNoTracking().Where(x => x.Code == entityName).FirstOrDefault();
                if (LogEntityRef != null)
                {
                    //取出Table那些欄位需要更新
                    var LogEntityField = this.LogEntityField.AsNoTracking().Where(x => x.LogEntityRefId == LogEntityRef.Id)
                        .Select(x => new
                        {
                            Id = x.Id,
                            FieldName = x.FieldName
                        }).ToList();

                    var primaryKey = entry.CurrentValues.GetValue<object>("Id");
                    foreach (var logfield in LogEntityField)
                    {
                        var originalValue = (entry.State == EntityState.Added) ? string.Empty : entry.OriginalValues.GetValue<object>(logfield.FieldName);
                        var currentValue = entry.CurrentValues.GetValue<object>(logfield.FieldName);
                        if (entry.State == EntityState.Modified)
                        {
                            if (originalValue.ToString() == currentValue.ToString())
                                continue;
                        }
                        LogValue.Add(new Entity.LogEntityValue
                        {
                            DateCreated = DateTime.UtcNow,
                            UserCreated = sysName,
                            LogEntityRefId = LogEntityRef.Id,
                            RefObjectId = (int?)primaryKey,
                            LogEntityFieldId = logfield.Id,
                            OldValue = originalValue.ToString(),
                            NewValue = currentValue.ToString()
                        });
                    }                    
                }
            }
            context.AcceptAllChanges();
            LogEntityValue.AddRange(LogValue);
            int result = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
            return result;
        }
    }
}

以上就是寫Log的原始碼