zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Dreamweaver采用utf8制作页面,到.net显示乱码问题解决
    看不完的风景,走不完的路
    整个世界都在返利
    Google 地图小工具:让别人找到你
    开心网
    提取国家地理图片总结
    [脚本收集]:在线词典
    提取国家地理图片总结之二
    [脚本收集]提取国家地理图片
    若我离去,后会无期
  • 原文地址:https://www.cnblogs.com/vincentsun1234/p/3102892.html
Copyright © 2011-2022 走看看