C#对象的事务?
只是好奇,是否支持普通C#对象上的事务? 喜欢
using (var transaction = new ObjectTransaction(obj)) { try { obj.Prop1 = value; obj.Prop2 = value; obj.Recalculate(); // may fire exception transaction.Commit(); // now obj is saved } except { transaction.Rollback(); // now obj properties are restored } }
只是为了让答案更有用;-)其他语言有什么相似之处吗?
STM更新:这是它声称的内容:
atomic { x++; y--; throw; }
将保持x / y不变,包括链式方法调用。 看起来像我要求的。 至少它非常有趣。 我认为这已足够接近了。 此外,其他语言也有类似的东西,例如Haskell STM。 注意我不说它应该用于生产;-)
微软正在努力。 阅读有关软件事务内存的信息。
STM.NET
STM.NET团队博客
频道9video: STM.NET:谁。 什么。 为什么。
关于STM的论文
他们使用了一些不同的语法:
// For those who like arrows Atomic.Do(() => { obj.Prop1 = value; obj.Prop2 = value; obj.Recalculate(); }); // For others who prefer exceptions try { obj.Prop1 = value; obj.Prop2 = value; obj.Recalculate(); } catch (AtomicMarker) { } // we may get this in C#: atomic { obj.Prop1 = value; obj.Prop2 = value; obj.Recalculate(); }
对于它的价值,一个成熟的STM是一个小小的出路,我强烈建议不要滚动你自己。
幸运的是,您可以通过仔细设计类来获得所需的function。 特别是,不可变类支持开箱即用的类似事务的行为。 由于每次设置属性时,不可变对象都会返回自己的新副本,因此如果需要,您始终可以进行完全历史记录更改以进行回滚。
Juval Lowy写了这篇文章。 这是他原来的MSDN文章的链接(我在他出色的WCF书中首次听到了这个想法)。 这是来自MSDN的代码示例,它看起来像您想要实现的:
public class MyClass { Transactional m_Number = new Transactional (3); public void MyMethod() { Transactional city = new Transactional ("New York"); using(TransactionScope scope = new TransactionScope()) { city.Value = "London"; m_Number.Value = 4; m_Number.Value++; Debug.Assert(m_Number.Value == 5); //No call to scope.Complete(), transaction will abort } }
您可以在执行方法和设置属性之前制作对象的副本。 然后,如果您不喜欢结果,则只需“回滚”到副本即可。 当然,假设你没有副作用来应对。
不,目前没有内置到.net或C#中的任何内容。
但是,根据您的使用要求,您可以实施适合您的工作。
您的ObjectTransaction
类将序列化(或仅复制)对象并在事务期间保留副本。 如果您调用了commit
则可以删除该副本,但如果您调用了rollback,则可以从该副本还原原始对象上的所有属性。
这个建议有很多警告。
如果你必须使用reflection来获取属性(因为你希望它处理任何对象类型),它将非常慢。 同样用于序列化。
如果你有一个对象树而不仅仅是一个具有一些属性的简单对象,那么对所有对象类型一般处理类似的东西可能会非常棘手。
作为数据列表的属性也很棘手。
如果任何属性获取/设置方法触发具有效果的更改(或事件),则可能会在应用程序的其他位置导致实际问题。
它只对公共财产有效。
总而言之,几年前我工作的一个项目确实做了类似的事情。 在非常严格的限制下,它可以很好地工作。 我们只支持内部业务层数据对象。 他们都必须从一个基本接口inheritance,该接口提供了一些关于属性类型的额外元数据,并且有关于可以从属性设置器触发哪些事件的规则。 我们将启动事务,然后将对象绑定到GUI。 如果用户点击ok,则事务刚刚关闭,但如果他们点击取消,则事务管理器从GUI取消绑定并回滚对象上的所有更改。
不,对于vanilla托管对象,今天不存在这种类型的支持。
而这里再一次简单的解决方案是不允许您的对象首先进入无效状态。 然后你不需要回滚任何东西,你不需要调用“validation”等。如果你删除你的setter并开始考虑向对象发送消息以对内部数据做某事,而不是设置属性,你’ ll会发现关于你域名的微妙内容,否则你就不会。
这是我刚才写的解决方案:)也应该使用数组和任何引用类型。
public sealed class ObjectTransaction:IDisposable { bool m_isDisposed; Dictionary sourceObjRefHolder; object m_backup; object m_original; public ObjectTransaction(object obj) { sourceObjRefHolder = new Dictionary(); m_backup = processRecursive(obj,sourceObjRefHolder,new CreateNewInstanceResolver()); m_original = obj; } public void Dispose() { Rollback(); } public void Rollback() { if (m_isDisposed) return; var processRefHolder = new Dictionary(); var targetObjRefHolder = sourceObjRefHolder.ToDictionary(x=>x.Value,x=>x.Key); var originalRefResolver = new DictionaryRefResolver(targetObjRefHolder); processRecursive(m_backup, processRefHolder, originalRefResolver); dispose(); } public void Commit() { if (m_isDisposed) return; //do nothing dispose(); } void dispose() { sourceObjRefHolder = null; m_backup = null; m_original = null; m_isDisposed = true; } object processRecursive(object objSource, Dictionary processRefHolder, ITargetObjectResolver targetResolver) { if (objSource == null) return null; if (objSource.GetType()==typeof(string) || objSource.GetType().IsClass == false) return objSource; if (processRefHolder.ContainsKey(objSource)) return processRefHolder[objSource]; Type type = objSource.GetType(); object objTarget = targetResolver.Resolve(objSource); processRefHolder.Add(objSource, objTarget); if (type.IsArray) { Array objSourceArray = (Array)objSource; Array objTargetArray = (Array)objTarget; for(int i=0;i fieldsInfo = FieldInfoEnumerable.Create(type); foreach(FieldInfo f in fieldsInfo) { if (f.FieldType==typeof(ObjectTransaction)) continue; object objSourceField = f.GetValue(objSource); object objTargetField = processRecursive(objSourceField, processRefHolder, targetResolver); f.SetValue(objTarget,objTargetField); } } return objTarget; } interface ITargetObjectResolver { object Resolve(object objSource); } class CreateNewInstanceResolver:ITargetObjectResolver { public object Resolve(object sourceObj) { object newObject=null; if (sourceObj.GetType().IsArray) { var length = ((Array)sourceObj).Length; newObject = Activator.CreateInstance(sourceObj.GetType(),length); } else { //no constructor calling, so no side effects during instantiation newObject = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(sourceObj.GetType()); //newObject = Activator.CreateInstance(sourceObj.GetType()); } return newObject; } } class DictionaryRefResolver:ITargetObjectResolver { readonly Dictionary m_refHolder; public DictionaryRefResolver(Dictionary refHolder) { m_refHolder = refHolder; } public object Resolve(object sourceObj) { if (!m_refHolder.ContainsKey(sourceObj)) throw new Exception("Unknown object reference"); return m_refHolder[sourceObj]; } } } class FieldInfoEnumerable { public static IEnumerable Create(Type type) { while(type!=null) { var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach(FieldInfo fi in fields) { yield return fi; } type = type.BaseType; } } }