EF Audit

需求比较复杂,真心不想多说。每次数据库操作都要记录原始值和新值到Audit表。而且其中一些外键字段不能存外键值而必须存其业务对应值,也就是其对应的导航属性的某个值

既然用了EF,哪个属性是普通属性,哪个是导航属性,哪个属性能对应到导航属性,都是可以得到的不是问题。但最终记录的是哪个导航属性的哪个值就不好说了,只能上配置信息。另外就是app规定不需要延迟加载,于是需要手动加载导航属性值

先定义一下配置信息相关类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EFAuditComponet
{
    public class AuditConfigItem
    {
        public string EntityTypeName { set; get; }

        public string EntityPropertyName { set; get; }        

        public string TargetNavigationPropertyPath { set; get; }

        public bool NeedToRecordAudit { set; get; }
        
    }

    public static class AuditConfigExtention
    {
        public static bool CheckContains(this List<AuditConfigItem> list,string entityTypeFullName, string propertyName)
        {
            var result = list.GetConfigItem(entityTypeFullName, propertyName);
            if (result == null)
                return false;
            else
                return true;
        }

        public static AuditConfigItem GetConfigItem(this List<AuditConfigItem> list, string entityTypeFullName, string propertyName)
        {
            return list.Find(p => p.EntityTypeName == entityTypeFullName && p.EntityPropertyName == propertyName);
        }
    }
}
View Code

没啥好说的,POCO+扩展方法,简单好用。需要一提的是TargetNavigationPropertyPath的格式,是属性名.属性名.属性名....属性名。前面的都是导航属性的属性名,最后是实际值的属性名。具体用法后面示例中有。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EFAuditComponet
{
    public class EFAuditConfig
    {
        public static List<AuditConfigItem> ConfigData = new List<AuditConfigItem>();
    }
}
View Code

将配置信息存于一个静态List中,写个类是为了方便将来扩展

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EFAuditComponet
{
    public class AuditRecord
    {
        public string PropertyName { set; get; }

        public string OldValue { set; get; }

        public string NewValue { set; get; }
    }
}
View Code

这个也没啥好说的,Audit记录POCO

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

namespace EFAuditComponet
{
    public class EFAuditManager
    {
        public void GetNavigationProperty<T>(DbContext context) where T : class
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            ObjectContext objContext = ((IObjectContextAdapter)context).ObjectContext;
            var set = objContext.CreateObjectSet<T>();
            var entitySet = set.EntitySet;

            Console.WriteLine("类型名称:");
            Console.WriteLine(entitySet.ElementType.FullName);
            Console.WriteLine(entitySet.ElementType.Name);

            Console.WriteLine("属性名称:");
            foreach (var pro in entitySet.ElementType.Properties)
            {
                Console.WriteLine(pro.Name);
            }

            Console.WriteLine("导航属性名称:");
            foreach (var pro in entitySet.ElementType.NavigationProperties)
            {
                Console.WriteLine(pro.Name);
            }

            Console.WriteLine("成员名称:");
            foreach (var pro in entitySet.ElementType.Members)
            {
                Console.WriteLine(pro.Name);
            }

            Console.WriteLine("属性与对应导航属性:");
            foreach (var pro in entitySet.ElementType.NavigationProperties)
            {
                foreach (var fpro in pro.GetDependentProperties())
                {
                    Console.WriteLine(fpro.Name + ":" + pro.ToEndMember.Name);
                }
            }

            Console.ReadKey();
        }

        public List<AuditRecord> GetObjectDataAndIncludeDataByConfig<T>(DbContext context, T objInstance) where T : class
        {
            List<AuditRecord> auditList = new List<AuditRecord>();
            ObjectContext objContext = ((IObjectContextAdapter)context).ObjectContext;
            var set = objContext.CreateObjectSet<T>();
            var entitySet = set.EntitySet;
            Dictionary<string, NavigationProperty> naviCollection = new Dictionary<string, NavigationProperty>();
            foreach (var pro in entitySet.ElementType.NavigationProperties)
            {
                foreach (var fpro in pro.GetDependentProperties())
                {
                    naviCollection.Add(fpro.Name, pro);
                }
            }

            foreach (var pro in entitySet.ElementType.Properties)
            {
                string propertyName = pro.Name;
                AuditConfigItem configItem = EFAuditConfig.ConfigData.GetConfigItem(entitySet.ElementType.Name, propertyName);

                if (configItem != null && !configItem.NeedToRecordAudit)
                {
                    continue;
                }
                AuditRecord record = new AuditRecord();
                
                DbEntityEntry<T> entry = Attach<T>(context, objInstance);
                //DbPropertyValues dbValues = entry.GetDatabaseValues();
                object originalValue = entry.Property(propertyName).OriginalValue;
                object currentValue = entry.Property(propertyName).CurrentValue;
                if (configItem == null || string.IsNullOrEmpty(configItem.TargetNavigationPropertyPath))
                {
                    record.PropertyName = propertyName;
                    record.OldValue = originalValue.ToString();
                    record.NewValue = currentValue.ToString();
                }
                else
                {
                    record.PropertyName = propertyName;
                    entry.Property(propertyName).CurrentValue = originalValue;
                    record.OldValue = LoadReferenceValue(context,entry, configItem.TargetNavigationPropertyPath);
                    entry.Property(propertyName).CurrentValue = currentValue;
                    record.NewValue = LoadReferenceValue(context,entry, configItem.TargetNavigationPropertyPath);
                }

                auditList.Add(record);
            }

            return auditList;
        }

        private string LoadReferenceValue(DbContext context,DbEntityEntry entry,string targetNavigationPropertyPath)
        {
            string returnValue = null;
            string[] propertyNames = targetNavigationPropertyPath.Split('.');
            for (int i = 0; i < propertyNames.Length; i++)
            {
                if (i != propertyNames.Length - 1)
                {
                    if (!entry.Reference(propertyNames[i]).IsLoaded)
                        entry.Reference(propertyNames[i]).Load();
                    entry = context.Entry(entry.Reference(propertyNames[i]).CurrentValue);
                }
                else
                    returnValue = entry.Property(propertyNames[i]).CurrentValue == null ? null : entry.Property(propertyNames[i]).CurrentValue.ToString();
            }

            return returnValue;
        }

        public DbEntityEntry<T> Attach<T>(DbContext context, T obj) where T : class
        {

            if (context == null)
            {
                throw new ArgumentNullException("context");
            }


            DbEntityEntry<T> entry;
            try
            {
                entry =context.Entry<T>(obj);
                entry.State = System.Data.EntityState.Modified;
            }
            catch
            {
                var set = ((IObjectContextAdapter)context).ObjectContext.CreateObjectSet<T>();
                var entitySet = set.EntitySet;
                string[] keyNames = entitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray();
                string precidate = string.Empty;
                foreach (string filter in keyNames)
                {
                    precidate += "it." + filter + "=" + context.Entry<T>(obj).Property(filter).CurrentValue.ToString();
                }
                var entity = set.Where(precidate).First();
                //context.ChangeTracker.Entries<Patron>().ToList().Remove(GetEntityKeyNames(context, pE);
                entry = context.Entry<T>(entity);
                entry.CurrentValues.SetValues(obj);
            }

            return entry;

        }


    }


}
View Code

这个是真正的核心类,提供了所有我们需要的方法。方法名乱起了,其实第一个方法教你如何获得普通属性,如何获得导航属性,如何获得普通属性与导航属性的对应。

可以对照代码看看结果图,用PatronIdentification对象做的示例:

GetObjectDataAndIncludeDataByConfig则是真正的能将一个对象与Context中的对象比较,并得到AduitRecord集合。

这是修改了一个查询得到的对象后进行比对得到结果:

using (BE50Beta_AdminEntities1 context = new BE50Beta_AdminEntities1())
            {
                PatronIdentification result = context.PatronIdentification.Where(p => p.IdentificatinNumber == "aaaa").First();
                result.CountryID = 2;
                result.IdentificatinNumber = "1234567";
                EFAuditManager manager = new EFAuditManager();
                List<AuditRecord> list = manager.GetObjectDataAndIncludeDataByConfig<PatronIdentification>(context, result);

                foreach (var record in list)
                {
                    Console.WriteLine(record.PropertyName + " OldValue:" + record.OldValue + " NewValue:" + record.NewValue);
                }

                Console.ReadKey();

            }
View Code

思路其实也很简单,遍历一个实体的普通属性,如果针对每一个属性,有一个配置信息存在且该配置信息的TargetNavigationPropertyPath不为空。那么就需要记录TargetNavigationPropertyPath所指的属性值而不是本身值

记录本身值无难度,难点在记录导航属性的值,可能会导航多次。

于是用了LoadReferenceValue,遍历TargetNavigationPropertyPath路径,在最后一个路径点去取出真正的值,之前只是将导航属性对象手动Load。

以下是模拟了有配置数据的情况,注意TargetNavigationPropertyPath的用法

EFAuditConfig.ConfigData.Add(new AuditConfigItem() { EntityTypeName = "PatronIdentification", EntityPropertyName = "CountryID", NeedToRecordAudit = true, TargetNavigationPropertyPath = "Country.CountryName" });
            using (BE50Beta_AdminEntities1 context = new BE50Beta_AdminEntities1())
            {
                PatronIdentification result = context.PatronIdentification.Where(p => p.IdentificatinNumber == "aaaa").First();
                result.CountryID = 2;
                result.IdentificatinNumber = "1234567";
                EFAuditManager manager = new EFAuditManager();
                List<AuditRecord> list = manager.GetObjectDataAndIncludeDataByConfig<PatronIdentification>(context, result);

                foreach (var record in list)
                {
                    Console.WriteLine(record.PropertyName + " OldValue:" + record.OldValue + " NewValue:" + record.NewValue);
                }

                Console.ReadKey();

            }
View Code

可以看到CountryID被成功替换成CountryName

原文地址:https://www.cnblogs.com/vincentsun1234/p/3102892.html