Heim  >  Artikel  >  Backend-Entwicklung  >  Code-Case-Sharing zum Überwachen von Klassenattributänderungen in C#

Code-Case-Sharing zum Überwachen von Klassenattributänderungen in C#

黄舟
黄舟Original
2017-07-27 16:15:043421Durchsuche

 C# überwacht Änderungen in Klassenattributen (welche Spielzeuge hat die Raubkatze bewegt)

Bei Verwendung von EF zum Aktualisieren von Datenbankentitäten. Oft möchten wir ein oder mehrere Felder in der Tabelle aktualisieren. Obwohl wir es so einstellen können, dass es dem Kontext mitteilt, welche Felder wir aktualisieren möchten. Aber im Allgemeinen kapseln wir die Datenpersistenzschicht. Durch generische Operationen. Derzeit können wir nicht wissen, welche Felder auf Anwendungsebene geändert wurden.

Ich lerne kürzlich auch EF und bin zufällig auf dieses Problem gestoßen. Wenn Sie es direkt auf Anwendungsebene verwenden, können Sie natürlich einfach den IsModified-Status des Felds festlegen. Wie folgt
db.Entry(model).Property(x => x.Token).IsModified = false;
Dies beschränkt sich jedoch auf das Lernen und Demo. In der formalen Entwicklung werden solche Low-Level-Operationen im Allgemeinen nicht der Anwendungsebene ausgesetzt. Die Datenbankpersistenzschicht wird gekapselt. Das Hinzufügen, Löschen, Ändern und Abfragen erfolgt dann über die Entity Factory (Warehouse) und Entity Generics.
Einzelheiten finden Sie in Artikeln wie „Repository Pattern Design Based on Entity Framework“.
Diese Methoden haben eines gemeinsam: Beim Aktualisieren und Löschen gibt es ähnliche Codes wie folgt:


    public virtual void Update(TEntity TObject)
        {
            try
            {
                var entry = Context.Entry(TObject);
                Context.Set<TEntity>().Attach(TObject);
                entry.State = EntityState.Modified;
            }
            catch (OptimisticConcurrencyException ex)
            {
                throw ex;
            }
        }

Persönliches Verständnis: Update(TEntity TObject) übergibt eine Entität an die Methode, hängt sie dann an den Datenbankkontext an und markiert die Daten als geändert. Dann aktualisieren.
In diesem Fall werden alle Felder der Entität aktualisiert. Dann müssen wir sicherstellen, dass diese Entität in der Datenbank gefunden wird oder den Datensätzen in der Datenbank entspricht. In der C/S-Struktur ist das kein Problem, aber was ist mit der B/S-Struktur? Es ist uns nicht möglich, alle Felder der Entität zu packen und an den Client zu senden. Dann kann der Client sie ändern und an den Server zurücksenden und dann die Warehouse-Methode zum Aktualisieren aufrufen. Um es einfach auszudrücken: Um das Benutzerpasswort zu ändern, benötigen wir lediglich eine Benutzer-ID und ein neues Passwort. Oder um ein Benutzerkonto zu sperren, benötigen Sie lediglich eine Benutzer-ID, einen Sperrstatus und eine Sperrzeit. Auf diese Weise ist es uns unmöglich, die gesamte Benutzerentität zu verpacken und weiterzugeben. Einige Leute sagen, dass Sie beim Speichern zunächst die Datenbank anhand der ID überprüfen und dann die geänderten Attributwerte vor der Aktualisierung daran anhängen können. Dies bringt uns zurück zum Problem: Es gibt nur generische Typen in der Warehouse-Methode, und wenn Sie die Warehouse-Update-Methode aufrufen, übergeben Sie ihr einen Entitätstyp. Das Lager weiß nicht, welche Entität Sie sind und welche Felder aktualisiert wurden.
Natürlich wissen wir durch Trigger, dass Datenbankaktualisierungen zuerst gelöscht und dann eingefügt werden, sodass es keinen großen Unterschied zwischen der Aktualisierung einiger Felder und der Aktualisierung der gesamten Spalte gibt.

Legen Sie nun Informationen wie Lageraktualisierungen und andere Entitätsgenerika beiseite. Schauen Sie sich einfach an, wenn sich eine Entität ändert. Wie können wir dann wissen, welche Attribute sie geändert hat?
Normalerweise sieht eine Entität so aus


 1     /// <summary> 
 2     /// 一个具体的实体 
 3     /// </summary> 
 4     public class AccountEntity : MainEntity 
 5     { 
 6         /// <summary> 
 7         /// 文本类型 
 8         /// </summary> 
 9         public virtual string Account { get; set; }
 10         /// <summary>
 11         /// 又一个文本属性
 12         /// </summary>
 13         public virtual string Password { get; set; }
 14         /// <summary>
 15         /// 数字类型
 16         /// </summary>
 17         public virtual int Sex { get; set; }
 18         /// <summary>
 19         /// 事件类型
 20         /// </summary>
 21         public virtual DateTime Birthday { get; set; }
 22         /// <summary>
 23         /// 双精度浮点数
 24         /// </summary>
 25         public virtual double Height { get; set; }
 26         /// <summary>
 27         /// 十进制数
 28         /// </summary>
 29         public virtual decimal Monery { get; set; }
 30         /// <summary>
 31         /// 二进制
 32         /// </summary>
 33         public virtual byte[] PublicKey { get; set; }
 34         /// <summary>
 35         /// Guid类型
 36         /// </summary>
 37         public virtual Guid AreaId { get; set; }
 38     }

Code anzeigen

Wenn wir die Attribute dieser Entität ändern möchten:


var entity = new accountEntity();
entity.Id=1;
entity.Account = "给属性赋值&#39;;

Übergeben Sie diese Entität dann zur Operation an die darunter liegende Ebene.


db.Update(entity);

 Überhaupt kein Problem, aber meine Frage ist, woher weiß die unterste Ebene, welche Eigenschaften meine Anwendungsebene geändert hat? Fügen Sie eine weitere Methode hinzu, um der untersten Ebene mitzuteilen, dass ich diese Attribute geändert habe.


db.Update(entity,"Account");

Daran scheint nichts auszusetzen zu sein.

Aber was passiert, wenn ich das Konto ändere, aber das Passwort im Parameter übergeben wird? Daher sollte es eine Sammlung für die Entität geben, um zu speichern, ob das gesamte Attribut geändert wurde. Gehen Sie dann zur zugrunde liegenden Update-Methode, um die aktualisierten Felder für den nächsten Schritt zu entnehmen.
Durch diese Idee dachte ich darüber nach, der Entität ein Wörterbuch hinzuzufügen:


protected Dictionary<string, dynamic> FieldTracking = new Dictionary<string, dynamic>();

Bei der Attributzuweisung , wird es dem Wörterbuch hinzugefügt. (Natürlich erhöht dieser Vorgang den Overhead des Programms)


FieldTracking["Account"]="给属性赋值";

  然后在底层在取出里面的集合,来区分哪些字段被修改(大花猫动了哪些小玩具)。

  改造下实体属性


        public virtual string Account
        {
            get
            { return _Account; }
            set {
                _Account = value;
                FieldTracking["Account"] = value;
            }
        }

  看过编译后的IL代码的都知道,class中的属性最终会编译成两个方法 setvalue和getvalue,那么通过修改set方法添加FieldTracking["Account"] = value;就可以让属性在赋值的时候添加到字典中。

  很简单吧。


  你以为这样就完了。如果拿房间来比喻实体、拿玩具来比作属性。我家那大花猫就是修改实体属性的方法。你知道我家有多少玩具吗?你每天回家的时候你知道大花猫动了哪个小玩具吗?给每个玩具装个GPS?哈哈哈哈,别闹,花这心思还不如再买点回来。什么?买回来的还得装,算了。研究下怎么装吧。

  一个程序可能有上百个实体类,修改现有的实体类,给每个set加一行?作为一个程序员是不可能容忍做这样的操作的。写一个工具,读取所有的实体代码,加上这一行,保存。这是个好办法。那每次添加一个实体类就得调用工具重写来一遍,每次修改属性再调用一遍,恩。没问题。能用就行。这不是一个真心养猫的人的人能容忍的。

  那怎么办?把猫打死?那玩具的存在将会没有任何意义。想到一个办法,在我离开房子的时候(程序初始化),给房子里的所有房间(实体类)创建一个同样的房间(继承),包含了与原房间所有需要监控(标记为virtual)的玩具的复制,在复制过程中加上GPS(-_~)。然后给猫玩。猫通过我给的门进到这个继承的房间中玩所有玩具的时候,GPS就能将猫的动作全部记录下来。我一回家,这猫玩了哪些玩具一看GPS记录就全知道了。哟,这小崽子,在王元鹅呢。
  

  看不懂,没关系,上马:
  1、在程序集初始化的时候,通过反射,查找所有继承自BaseEntity的实体类。遍历其中的属性。找到标记为virtual进行复制。

    刚开始对于如果找到virtual属性花了不少时间。我总只想着在属性上找,却没想到去set_value方法上去找(其实get_value方法也是)。还是太菜啊。

    注:NoMapAttribute特性是一个自定义的标记,表示不参与映射。因为不参与映射就不需要监控。与本文章代码没有太大的关系。仅供参考。


//获取实体所在的程序集(ClassLibraryDemo)
var assemblyArray = AppDomain.CurrentDomain.GetAssemblies()
        .Where(w => w.GetName().Name == "ClassLibraryDemo")
        .ToList();
//实体的基类
var baseEntityType = typeof(BaseEntity);
//循环程序集
foreach (Assembly item in assemblyArray)
{
    //找到这个程序集中继承自基类的实体
    var types = item.GetTypes().Where(t => t.IsAbstract == false
        && baseEntityType.IsAssignableFrom(t) 
        && t != baseEntityType);
    foreach (Type btItem in types){
        //遍历这个实体类中的属性
var properties = btItem.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                        .Where(w => w.CanRead && w.CanWrite
                            && w.GetCustomAttributes(typeof(NoMapAttribute), false).Any() == false
                            //TODO:要不要检查get方法?
                            && w.GetSetMethod().IsVirtual);
    }
}

  2、根据1的结果,复制一个新的房间(动态代码生成一个类,这个类继承1中的实体,并且重写了属性的set方法)

  这个过程就设计到动态代码的生成了。


//首先创建一个与实体类对应的动态类
CodeTypeDeclaration ct = new CodeTypeDeclaration(btItem.Name + "_Dynamic");
//循环实体中的所有标记为virtual的属性
foreach (PropertyInfo fiItem in properties)
{
	//创建一个属性
	var p = new CodeMemberProperty();
	//设置属性为公共、重写
	p.Attributes = MemberAttributes.Public | MemberAttributes.Override;//override
	//设置属性的类型为继承的属性的数据类型
	p.Type = new CodeTypeReference(fiItem.PropertyType);
	//属性名称与继承的一致
	p.Name = fiItem.Name;
	//包含set代码
	p.HasSet = true;
	//包含get代码
	p.HasGet = true;
	//设置get代码
	//return base.Account
	p.GetStatements.Add(new CodeMethodReturnStatement(
                new CodeFieldReferenceExpression(
                        new CodeBaseReferenceExpression(), fiItem.Name)));
	//设置set代码
	//base.Account=value;
	p.SetStatements.Add(
	new CodeAssignStatement(
                new CodeFieldReferenceExpression(
                        new CodeBaseReferenceExpression(), fiItem.Name),
	new CodePropertySetValueReferenceExpression()));
	//FieldTracking["Account"]=value;
	p.SetStatements.Add(new CodeSnippetExpression("FieldTracking[\"" + fiItem.Name + "\"] = value"));
	//将属性添加到类中
	ct.Members.Add(p);
}

  3、将刚才生成的类加到原类所在的命名空间+".Dynamic"(加后缀以示区分)


//声明一个命名空间(与当前实体类同名+后缀)
CodeNamespace ns = new CodeNamespace(btItem.Namespace + ".Dynamic");
ns.Types.Add(ct);

  4、编辑生成代码所在的程序集


    //要动态生成代码的程序集
    CodeCompileUnit program = new CodeCompileUnit();
    //添加引用
    program.ReferencedAssemblies.Add("mscorlib.dll");
    program.ReferencedAssemblies.Add("System.dll");
    program.ReferencedAssemblies.Add("System.Core.dll");

    //定义代码工厂
    CSharpCodeProvider provider = new CSharpCodeProvider();
    //编译程序集
    var cr = provider.CompileAssemblyFromDom(new System.CodeDom.Compiler.CompilerParameters();
    //看编译是否通过
    var error = cr.Errors;
    if (error.HasErrors)
    {
        Console.WriteLine("错误列表:");
        //编译不通过
        foreach (dynamic item in error)
        {
            Console.WriteLine("ErrorNumber:{0};Line:{1};ErrorText{2}",
                item.ErrorNumber,
                item.Line, 
                item.ErrorText);
        }
        return;
    }
    else
    {
        Console.WriteLine("编译成功。");
    }

 

   查看生成的代码


//查看生成的代码
var codeText = new StringBuilder();
using (var codeWriter = new StringWriter(codeText))
{
    CodeDomProvider.CreateProvider("CSharp").GenerateCodeFromNamespace(ns,
        codeWriter,
        new CodeGeneratorOptions()
        {
            BlankLinesBetweenMembers = true
        });
}
Console.WriteLine(codeText);

 

  5、将复制的新类与原类建立映射关系。


foreach (Type item in ts)
{
    //注册(模拟实现,通过字典实现的,也可以通过IOC注入方式处理)
    Mapping.Map(item.BaseType, item);
}

  6、获得这个复制的实体对象


//创建一个指定的实体对象
AccountEntity ae = Mapping.GetMap<AccountEntity>();

  7、对这个实体对象的属性进行赋值


//主键赋值不会修改属性更新
ae.BaseEntity_Id = 1;//不会变(未标记为virtual)
ae.MainEntity_Name = "大花猫";
ae.MainEntity_UpdateTime = DateTime.Now;
//修改某个属性
ae.Account = "admin";
ae.Account = "以最后一次的修改为准";

  8、调用底层方法,底层根据这个实体属性获得被修改的属性名称


//调用基类中的方法 获取变动的属性
var up = ae.GetFieldTracking();
Console.WriteLine("有修改的字段:");
up.ForEach(fe =>
{
    Console.WriteLine(fe + ":" + ae[fe]);
});

  9、完美

  

 

  就这样,在底层就能知道哪些实体被赋值过了。

  当然,有些实体我们只是需要用来计算,则可以调用方法将赋值过的属性进行删除


//删除变更字段
ae.RemoveChanges("Account");

  这只是一个简单的实现,还有一种比较复杂的情况,在第6步,获得这个复制的实体对象时,怎么用一个现有的new出来的实体对象去创建建并监控呢。就像,别人送我一房间现成的玩具,给我的时候猫就在里面玩了。嗷,把猫打死吧。

  总结:

再次认识到反射的强大。
也第一次实现了代码生成代码并使用的经历。
对字段和属性的区别有了更深的认识。
对访问修饰符和虚virtual方法有了更好的认识。

 

Das obige ist der detaillierte Inhalt vonCode-Case-Sharing zum Überwachen von Klassenattributänderungen in C#. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn