我有一个网页接受费用,并且必须插入6行,分布在4个表中。我不得不将INSERTS分为两个单独的SaveChanges,但是我需要它们都位于同一个数据库事务中,以便所有INSERTS和UPDATES在出现问题时都可以回滚。
我在SPA模板中的Entity Framework 6.2上,Oracle Mgd Data Access 12.2上使用Breeze 1.6。
4个表是A,B,C和D。表IB,C和D是A的子级,每个都带有A的PK作为外键。我最初按照应用程序A1,B1,C1,C2,C3,D1的要求按此顺序对INSERTS进行编码,然后再进行一次Breeze SaveChanges。 C3具有Oracle触发器,可以在插入C3时更新A1和B1中的几列。我的问题是,实体框架未按我编码的顺序插入(而且我知道我无法控制该顺序)。我实际上得到了以下序列:A1,C1,C2,C3,B1,D1,并且由于C3具有更新A和B的触发器,因此遇到了问题,因为尚未插入B。
所以我找到了这个问题:What logic determines the insert order of Entity Framework 6,它建议使用多个SaveChanges获得控制权。如下工作:
但是Breeze / Entity Framework将每个SaveChanges都视为事务,因此,如果第二个SaveChanges中出现错误,我现在无法回退这两个部分。万一第2部分失败,我是否必须自己编写第1部分回滚的代码?还是有一个我不知道的秘密?
我熟悉BeginTransaction,Commit和Rollback,但不确定如何/在哪里将其与Breeze和Entity Framework配合使用。 Breeze抓取每个SaveChanges并将其自动包装在事务中。
我需要将多个SaveChanges分组到一个事务中。
感谢您的帮助。
我认为在这种情况下,最好的做法是让Breeze client将其视为一次保存,而server则将保存束分开以正确的顺序保存。] >
让Breeze ContextProvider
反序列化保存包,然后使用BeforeSaveEntities钩子手动进行更改。最后,将实体标记为Unchanged
,以便ContextProvider不再尝试再次保存它们。
这是示例实现。
[在服务器上的Breeze Controller中,创建一个新的保存方法,该方法添加一个BeforeSaveEntitiesDelegate仅用于此特殊保存:
[HttpPost] public SaveResult SaveFeePayment(JObject saveBundle) { { contextProvider.BeforeSaveEntitiesDelegate += BeforeSaveFeePayment; return contextProvider.SaveChanges(saveBundle); } private Dictionary<Type, List<EntityInfo>> BeforeSaveFeePayment(Dictionary<Type, List<EntityInfo>> entities) { var context = contextProvider.Context; // Get the list of EntityInfo for each type. Throws exception if not found. // A fee payment save bundle must have A, B, C, and D or it is invalid. var ainfos = entities[typeof(A)]; var binfos = entities[typeof(B)]; var cinfos = entities[typeof(C)]; var dinfos = entities[typeof(D)]; // Save A and B Attach(context, ainfos); Attach(context, binfos); context.SaveChanges(); // Save C and D Attach(context, cinfos); Attach(context, dinfos); context.SaveChanges(); // Set all states to Unchanged, so Breeze won't try to save them again ainfos.ForEach(info => info.EntityState = Breeze.ContextProvider.EntityState.Unchanged); binfos.ForEach(info => info.EntityState = Breeze.ContextProvider.EntityState.Unchanged); cinfos.ForEach(info => info.EntityState = Breeze.ContextProvider.EntityState.Unchanged); dinfos.ForEach(info => info.EntityState = Breeze.ContextProvider.EntityState.Unchanged); // Return the entities, so Breeze will return them back to the client return entities; } // Map Breeze EntityState to EF EntityState private static Dictionary<Breeze.ContextProvider.EntityState, System.Data.Entity.EntityState> entityStateMap = new Dictionary<Breeze.ContextProvider.EntityState, System.Data.Entity.EntityState> { { Breeze.ContextProvider.EntityState.Added, System.Data.Entity.EntityState.Added }, { Breeze.ContextProvider.EntityState.Deleted, System.Data.Entity.EntityState.Deleted }, { Breeze.ContextProvider.EntityState.Detached, System.Data.Entity.EntityState.Detached }, { Breeze.ContextProvider.EntityState.Modified, System.Data.Entity.EntityState.Modified }, { Breeze.ContextProvider.EntityState.Unchanged, System.Data.Entity.EntityState.Unchanged } }; // Attach entities to the DbContext in the correct entity state private static void Attach(DbContext context, List<EntityInfo> infos) { foreach(var info in infos) { var efState = entityStateMap[info.EntityState]; context.Entry(info.Entity).State = efState; } }
客户
在客户端上,您需要使用SaveFeePayment
调用named save端点。您将执行仅在节省费用时才使用。继续使用普通的SaveChanges
端点进行其他保存。
通过指定resourceName
调用特殊端点:
var saveOptions = new SaveOptions({ resourceName: "SaveFeePayment" }); return myEntityManager.saveChanges(null, saveOptions);
交易?
请注意,我实际上没有对交易做任何事情!我不确定如何使用EF + Oracle处理它们。希望您先前的解决方案可以应用于上面的BeforeSaveFeePayment
方法。
希望这会有所帮助。