This post discusses a Snapshot manager that I wrote which takes a snapshot of each of my changes by storing entries, and also allows me to backward and forward my changes with DbContext.
- UndoRedoPOC.zip
- UndoRedoPOC
- ConsoleClient
- Lib.DataAccess
- Lib.Global
- Lib.Model
- Library
- EntityFramework.dll
- LogicService.Contract
- LogicService
- packages
- EntityFramework.5.0.0
- Content
- App.config.transform
- Web.config.transform
- EntityFramework.5.0.0.nupkg
- lib
- net40
- net45
- tools
- repositories.config
- UndoRedoPOC.sln
- UndoRedoPOC.suo
- UndoRedoPOC.v11.suo
- UndoRedoPOC-noexe.zip
|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using System.Configuration;
using Lib.Model;
using Lib.Global;
using System.ComponentModel.Composition;
using System.Data.Objects;
using System.Data.Entity.Infrastructure;
using Lib.DataAccess.Interfaces;
using System.Data;
using System.Data.Common;
using System.Data.Linq.Mapping;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Xml;
namespace Lib.DataAccess
{
[Export(typeof(IContext))]
public class BlogContext : DbContext, IContext , ISnapshotManager
{
public BlogContext()
: base()
{
IsAuditEnabled = true;
ObjectContext.SavingChanges += OnSavingChanges;
}
public ObjectContext ObjectContext
{
get
{
return (this as IObjectContextAdapter).ObjectContext;
}
}
public DbSet<BlogPost> BlogPosts { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Audit> Audits { get; set; }
#region IContext Implementation
public ISnapshotManager SnapshotManager
{
get { return (this as ISnapshotManager); }
}
public bool IsAuditEnabled
{
get;
set;
}
public void ChangeState<T>(T entity, EntityState state) where T : class
{
Entry<T>(entity).State = state;
}
public IDbSet<T> GetEntitySet<T>()
where T : class
{
return Set<T>();
}
public virtual int Commit()
{
if (this.ChangeTracker.Entries().Any(IsChanged))
{
var result = this.SaveChanges();
this.ClearSnapshots();
return result;
}
return 0;
}
private static bool IsChanged(DbEntityEntry entity)
{
return IsStateEqual(entity, EntityState.Added) ||
IsStateEqual(entity, EntityState.Deleted) ||
IsStateEqual(entity, EntityState.Modified);
}
private static bool IsStateEqual(DbEntityEntry entity, EntityState state)
{
return (entity.State & state) == state;
}
public virtual DbTransaction BeginTransaction()
{
var connection = this.ObjectContext.Connection;
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
return connection
.BeginTransaction(IsolationLevel.ReadCommitted);
}
#endregion
#region Audit
void OnSavingChanges(object sender, EventArgs e)
{
if (IsAuditEnabled)
{
var changeEntries = this.ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Added
|| p.State == System.Data.EntityState.Deleted
|| p.State == System.Data.EntityState.Modified);
if (null != changeEntries)
{
foreach (var entity in changeEntries)
{
foreach (var audit in CreateAuditRecordsForChanges(entity))
{
this.Audits.Add(audit);
}
}
}
}
}
private List<Audit> CreateAuditRecordsForChanges(DbEntityEntry dbEntry)
{
List<Audit> result = new List<Audit>();
#region Generate Audit
//determine audit time
DateTime auditTime = DateTime.UtcNow;
// Get the Table name by attribute
TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name;
// Find Primiray key.
string keyName = dbEntry.Entity.GetType().GetProperties().Single(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Count() > 0).Name;
if (dbEntry.State == System.Data.EntityState.Added)
{
result.Add(new Audit()
{
Id = Guid.NewGuid(),
AuditDateInUTC = auditTime,
AuditState = AuditState.Added,
TableName = tableName,
RecordID = dbEntry.CurrentValues.GetValue<object>(keyName).ToString(), // Again, adjust this if you have a multi-column key
NewValue = ToXmlString(dbEntry.CurrentValues.ToObject())
}
);
}
else if (dbEntry.State == System.Data.EntityState.Deleted)
{
result.Add(new Audit()
{
Id = Guid.NewGuid(),
AuditDateInUTC = auditTime,
AuditState = AuditState.Deleted,
TableName = tableName,
RecordID = dbEntry.OriginalValues.GetValue<object>(keyName).ToString(),
NewValue = ToXmlString(dbEntry.OriginalValues.ToObject().ToString())
}
);
}
else if (dbEntry.State == System.Data.EntityState.Modified)
{
foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
{
if (!object.Equals(dbEntry.OriginalValues.GetValue<object>(propertyName), dbEntry.CurrentValues.GetValue<object>(propertyName)))
{
result.Add(new Audit()
{
Id = Guid.NewGuid(),
AuditDateInUTC = auditTime,
AuditState = AuditState.Modified,
TableName = tableName,
RecordID = dbEntry.OriginalValues.GetValue<object>(keyName).ToString(),
ColumnName = propertyName,
OriginalValue = dbEntry.OriginalValues.GetValue<object>(propertyName) == null ?
null
: dbEntry.OriginalValues.GetValue<object>(propertyName).ToString(),
NewValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null ?
null
: dbEntry.CurrentValues.GetValue<object>(propertyName).ToString()
}
);
}
}
}
return result;
#endregion
}
private static string ToXmlString(object value)
{
var serializer = new DataContractSerializer(value.GetType());
using (var backing = new System.IO.StringWriter())
using (var writer = new System.Xml.XmlTextWriter(backing))
{
serializer.WriteObject(writer, value);
return backing.ToString();
}
}
#endregion
#region ISnapshotManager Implementation
private Stack<IEnumerable<SnapData>> _unDoList = new Stack<IEnumerable<SnapData>>();
private Stack<IEnumerable<SnapData>> _redoDoList = new Stack<IEnumerable<SnapData>>();
public void TakeSnapshot()
{
_redoDoList.Clear();
if(!this.Configuration.AutoDetectChangesEnabled)
this.ChangeTracker.DetectChanges();
var entries = this.ChangeTracker.Entries().Where( e => e.State == EntityState.Added || e.State == EntityState.Modified || e.State == EntityState.Deleted );
if(null != entries)
{
var entrySnapList = new List<SnapData>();
foreach (var entry in entries)
{
if (entry.Entity != null
&& !_unDoList.Any(v => v.Any(s => s.Entity.Equals(entry.Entity))) )
{
entrySnapList.Add(new SnapData()
{
OrginalValue = (entry.State == EntityState.Deleted || entry.State == EntityState.Modified) ?
(entry.OriginalValues != null) ? entry.OriginalValues.ToObject() : entry.GetDatabaseValues()
: null,
Value = (entry.State == EntityState.Added || entry.State == EntityState.Modified) ? entry.CurrentValues.ToObject() : null,
State = entry.State,
Entity = entry.Entity
});
}
}
if (entrySnapList.Count > 0)
_unDoList.Push(entrySnapList.AsEnumerable());
}
}
public void UnDo()
{
if (CanUndo())
{
bool previousContiguration = this.Configuration.AutoDetectChangesEnabled;
this.Configuration.AutoDetectChangesEnabled = false;
var entries = _unDoList.Pop();
{
this._redoDoList.Push(entries);
foreach (var snap in entries)
{
var currentEntry = this.Entry(snap.Entity);
if (snap.State == EntityState.Modified)
{
currentEntry.CurrentValues.SetValues(snap.OrginalValue);
var dbValue = currentEntry.GetDatabaseValues();
currentEntry.OriginalValues.SetValues(dbValue);
}
else if (snap.State == EntityState.Deleted)
{
var dbValue = currentEntry.GetDatabaseValues();
currentEntry.OriginalValues.SetValues(dbValue);
}
currentEntry.State = EntityState.Unchanged;
}
}
this.Configuration.AutoDetectChangesEnabled = previousContiguration;
}
}
public void Redo()
{
if (CanRedo())
{
bool previousContiguration = this.Configuration.AutoDetectChangesEnabled;
this.Configuration.AutoDetectChangesEnabled = false;
var entries = _redoDoList.Pop();
if (null != entries && entries.Count() > 0)
{
foreach (var snap in entries)
{
var currentEntry = this.Entry(snap.Entity);
if (snap.State == EntityState.Modified)
{
currentEntry.CurrentValues.SetValues(snap.Value);
currentEntry.OriginalValues.SetValues(snap.OrginalValue);
}
else if (snap.State == EntityState.Deleted)
{
var dbValue = currentEntry.GetDatabaseValues();
currentEntry.OriginalValues.SetValues(dbValue);
}
currentEntry.State = snap.State;
}
}
this.Configuration.AutoDetectChangesEnabled = previousContiguration;
}
}
public bool CanUndo()
{
return (_unDoList.Count > 0);
}
public bool CanRedo()
{
return (_redoDoList.Count > 0);
}
public void ClearSnapshots()
{
_redoDoList.Clear();
_unDoList.Clear();
}
#endregion
}
class SnapData
{
public EntityState State;
public object Value;
public object OrginalValue;
public object Entity;
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
In my childhood, my uncle has shown me how to see the cloud in a close look and I understand that one can draw some elements of the Earth in the sky-canvas if he/she wants to. After that the cloud becomes closer to me and It teaches me one thing that, a deeper-look to something will give you some clues to draw your imagination. You can able to see that one which you have build-up in your mind.
Years past, I have started my career as a software engineer and has been looking for passion in my coding and development which I should be to enjoy my profession and has started asking myself- 'am I doing any engineering here?!' Is my code becoming that thing which I have designed in my mind? So to find that answer I have tried that old solution here... I have decided to come closer to my code and start analyzing them. And it is really working for me and at least it gives me the confidence that I can build something that I really want to. I can draw my thinking there through my code and can build-up my vision that I have designed in my mind. It also helps me to think out of the box, solve each problems by making blocks and make me careful on each steps.
• Morshed's Technical Blog site: http://morshedanwar.wordpress.com/
• Morshed's Technical articles those are published in Codeproject site: http://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=2992452
• Morshed's Linkedin profile: http://www.linkedin.com/in/morshedanwar
• Morshed's Facebook Profile : http://www.facebook.com/morshed.pulok
Beside all these I like to do - photography and music. Here is my Flickr photos : http://www.flickr.com/photos/morshed_anwar/