Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
283 views
in Technique[技术] by (71.8m points)

c# - EF Code-First inherit a single base class to implement easy historocity

I am running into some errors implementing my plan as described below. I am not so interested at this point in resolving particular errors as I am in whether or not this is a good idea.

All history-capable objects descend from a common class AuditableObject with a single property public Guid ID { get; set; }.

A descendent might be:

public class Taco : AuditableObject { public string Seasoning { get; set; } }

Now, I would like to implement the save event handler to write to the following table (class)

public class AuditItem
{
    public Guid ID { get; set; }
    public virtual AuditableObject Object { get; set; }
    public string ObjectClassName { get; set; } //ugly
    public string OldObjectXMLData { get; set; }
    public string NewObjectXMLData { get; set; }
    public DateTime Timestamp { get; set; }
}

I am not sure if I need the ObjectClassName as I can check the type of the object at runtime but it's there just in case.

On save I would basically serialize the object before and after to the respective properties, save a timestamp, and set the object - ie map the FK.

Is this an ugly way to go about it? Are there any obvious drawbacks to descending from a single class with EF Code First as I am doing?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I think you will need the full-qualified-name of the object's type when desalinizing, so that will be mandatory.

Alternatively serializing the object will be cause you problems.
Assume that we are going to audit TacoOject1 of Taco class using the approch, the serialized data will be put in data base, later due to business changes we need to add another property to Taco, after recompilation when we need to deserilazed TacoOject1 we will get TypeMissMatchException (not sure of exception name).

Another design objection is using inheritance for audit process.
First: In reality Taco is not a AuditableObject , Its a roll played by Taco, using inheritance will violate Liskov Substitution Principle.
Second: You can not use multiple inheritance, think that if we had a TacoSupperClass, how we could audit Taco then?

If I where going to design auditing process, I would use Entity–attribute–value model
Making AuditItem a marker interface and rename it to IAuditableEntity.
Having an attribute called AuditableProperty would enhance our process.
Any entity needs to be audited will be marked by IAuditableEntity, any property of the entity needed to be partcipated in audit will be marked by AuditableProperty attribute.

public class Taco : IAuditableEntity 
{ 
  [AuditableProperty]
  public string Seasoning { get; set; } 

  [AuditableProperty]
  public string OtherProperty1 { get; set; } 

  public string OtherProperty2 { get; set; } 

}

The AuditLog table will have these columns:
1. EntityFullTypeName: (String) We are going to audit different entities, the field will be used to get meaningful reports .(mandatory)
2. ObjectIdentifier: Entity identifier that is being manipulated, primary key or business key of the entity.
3. FieldName: (String) Entity field name.
4. OldValue: (String) Entity field old value.
5. NewValue: (String) Entity field new value.
6. TransactionUser: Application user that makes the change. (mandatory)
7. TransactionID: Any operation changing the entities will need to have a unique transaction ID (like GUID) (mandatory), In case of an update on an entity changing multiple fields,these column will be the key point to trace all changes in the update(transcation)
8. ChangeDate: Transaction date. (mandatory)
9. FieldType: enumeration or text showing the field type like TEXT or Double. (mandatory)

In service layer when Taco1 is going to be updated(or inserted) we will check if Taco1 type is marked by IAuditableEntity using reflection(using a lazy chash to store reflection data), if so which properties have been changed(we need a separate DB call to fetch old values).
e.g :

Taco1 = new Taco(); 
Taco1.Seasoning = "old Seasoning value";
Taco1.OtherProperty1 = "Old Other Property1 value";
Taco1.OtherProperty2 = "Old Other Property2 value";

Saved before,now updating:

Taco1.Seasoning = "New Seasoning value";
Taco1.OtherProperty1 = "New Other Property1 value";
Taco1.OtherProperty2 = "New Other Property2 value";

We will insert two records in AuditLog with the same TransactionID:

enter image description here

Having this approach
Any entity (table) could be traced
Reports will be readable
Only changes will be logged.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...