我需要在一个事务中执行多个 Breeze SaveChanges
I need to do Multiple Breeze SaveChanges in one transaction
我有一个接受费用支付的网页,它必须插入 6 行,分布在 4 个表中。我不得不将 INSERTS 分成两个单独的 SaveChanges,但我需要它们都在同一个数据库 t运行saction 中,以便所有 INSERTS 和 UPDATES 都将在出现问题时回滚。
我在 Entity Framework 6.2 之上,在 SPA 模板中的 Oracle Mgd Data Access 12.2 之上使用 Breeze 1.6。
A、B、C、D 4张表,B、C、D表是A的子表,分别携带A的PK作为外键。我最初按照我的应用程序 A1、B1、C1、C2、C3、D1 的要求按此顺序编码插入,然后是单个 Breeze SaveChanges。 C3 具有 Oracle 触发器,可在插入 C3 时更新 A1 和 B1 中的一些列。我的问题是 Entity Framework 没有在我编码的序列中插入(我知道我无法控制序列)。我实际上得到了这个序列:A1、C1、C2、C3、B1、D1,因为 C3 有一个更新 A 和 B 的触发器,它 运行 变成了一个问题,因为 B 还没有被插入。
所以我发现了这个问题: 它建议使用多个 SaveChanges 来获得一些控制权。得到的工作如下:
- A1、B1 保存更改
- C1、C2、C3、D1 保存更改
所有触发器,包括 A 和 B 的 C3 更新,现在都运行良好。
但是 Breeze/Entity 框架将每个 SaveChanges 视为一个 t运行saction,所以现在我无法在第二个 SaveChanges 出现错误时回滚这两个部分。如果第 2 部分失败,我是否必须自己编写第 1 部分回滚代码?还是有什么我不知道的秘密?
我熟悉 BeginT运行saction、Commit 和 Rollback,但不确定 how/where 是否适合 Breeze 和 Entity Framework 的组合。 Breeze 抓取每个 SaveChanges 并自动将其包装在 t运行saction 中。
我需要将多个 SaveChanges 组合成一个 T运行saction。
感谢您的帮助。
我认为在这种情况下最好的做法是让 Breeze 客户端 将其视为单个保存,而 服务器 拆分保存包以按正确的顺序保存它。
让 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)];
using (var tx = context.Database.BeginTransaction())
{
// Save A and B
Attach(context, ainfos);
Attach(context, binfos);
context.SaveChanges();
// Save C and D
Attach(context, cinfos);
Attach(context, dinfos);
context.SaveChanges();
tx.Commit();
}
// 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;
}
}
客户
在客户端上,您需要使用 named save 调用 SaveFeePayment
端点。您 只有在保存费用支付 时才会这样做。继续使用正常的 SaveChanges
端点进行其他保存。
通过指定 resourceName
:
调用特殊端点
var saveOptions = new SaveOptions({ resourceName: "SaveFeePayment" });
return myEntityManager.saveChanges(null, saveOptions);
交易?
我还没有测试这个例子是 100% 关于交易行为。我不确定我们是应该使用现有的 contextProvider.Context
还是在方法开始时创建新的 DbContext。区别在于数据库连接的处理方式。参见 Microsoft's guidance,希望它与 Oracle 一样工作。
希望您之前的事务管理解决方案可以应用于上面的BeforeSaveFeePayment
方法。
希望这对您有所帮助。
就我而言,在 .NET core 3.1 上使用 Breeze,我必须设置 IsTemporary=true
,如以下代码示例所示:
private static void Attach(BreezeContext context, List<EntityInfo> infos)
{
foreach (var info in infos)
{
var efState = entityStateMap[info.EntityState];
context.Entry(info.Entity).Property("Id").IsTemporary = true; //Explicitly set to true
context.Entry(info.Entity).State = efState;
}
}
默认设置为 false
,因此 SaveChanges()
抛出以下错误:
"cannot insert explicit value for identity column"
这可能是由于 Breeze 中的一个错误。与此同时,将 IsTemporary
设置为 true
至少可以解决这个问题。
我有一个接受费用支付的网页,它必须插入 6 行,分布在 4 个表中。我不得不将 INSERTS 分成两个单独的 SaveChanges,但我需要它们都在同一个数据库 t运行saction 中,以便所有 INSERTS 和 UPDATES 都将在出现问题时回滚。
我在 Entity Framework 6.2 之上,在 SPA 模板中的 Oracle Mgd Data Access 12.2 之上使用 Breeze 1.6。
A、B、C、D 4张表,B、C、D表是A的子表,分别携带A的PK作为外键。我最初按照我的应用程序 A1、B1、C1、C2、C3、D1 的要求按此顺序编码插入,然后是单个 Breeze SaveChanges。 C3 具有 Oracle 触发器,可在插入 C3 时更新 A1 和 B1 中的一些列。我的问题是 Entity Framework 没有在我编码的序列中插入(我知道我无法控制序列)。我实际上得到了这个序列:A1、C1、C2、C3、B1、D1,因为 C3 有一个更新 A 和 B 的触发器,它 运行 变成了一个问题,因为 B 还没有被插入。
所以我发现了这个问题:
- A1、B1 保存更改
- C1、C2、C3、D1 保存更改 所有触发器,包括 A 和 B 的 C3 更新,现在都运行良好。
但是 Breeze/Entity 框架将每个 SaveChanges 视为一个 t运行saction,所以现在我无法在第二个 SaveChanges 出现错误时回滚这两个部分。如果第 2 部分失败,我是否必须自己编写第 1 部分回滚代码?还是有什么我不知道的秘密?
我熟悉 BeginT运行saction、Commit 和 Rollback,但不确定 how/where 是否适合 Breeze 和 Entity Framework 的组合。 Breeze 抓取每个 SaveChanges 并自动将其包装在 t运行saction 中。
我需要将多个 SaveChanges 组合成一个 T运行saction。
感谢您的帮助。
我认为在这种情况下最好的做法是让 Breeze 客户端 将其视为单个保存,而 服务器 拆分保存包以按正确的顺序保存它。
让 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)];
using (var tx = context.Database.BeginTransaction())
{
// Save A and B
Attach(context, ainfos);
Attach(context, binfos);
context.SaveChanges();
// Save C and D
Attach(context, cinfos);
Attach(context, dinfos);
context.SaveChanges();
tx.Commit();
}
// 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;
}
}
客户
在客户端上,您需要使用 named save 调用 SaveFeePayment
端点。您 只有在保存费用支付 时才会这样做。继续使用正常的 SaveChanges
端点进行其他保存。
通过指定 resourceName
:
var saveOptions = new SaveOptions({ resourceName: "SaveFeePayment" });
return myEntityManager.saveChanges(null, saveOptions);
交易?
我还没有测试这个例子是 100% 关于交易行为。我不确定我们是应该使用现有的 contextProvider.Context
还是在方法开始时创建新的 DbContext。区别在于数据库连接的处理方式。参见 Microsoft's guidance,希望它与 Oracle 一样工作。
希望您之前的事务管理解决方案可以应用于上面的BeforeSaveFeePayment
方法。
希望这对您有所帮助。
就我而言,在 .NET core 3.1 上使用 Breeze,我必须设置 IsTemporary=true
,如以下代码示例所示:
private static void Attach(BreezeContext context, List<EntityInfo> infos)
{
foreach (var info in infos)
{
var efState = entityStateMap[info.EntityState];
context.Entry(info.Entity).Property("Id").IsTemporary = true; //Explicitly set to true
context.Entry(info.Entity).State = efState;
}
}
默认设置为 false
,因此 SaveChanges()
抛出以下错误:
"cannot insert explicit value for identity column"
这可能是由于 Breeze 中的一个错误。与此同时,将 IsTemporary
设置为 true
至少可以解决这个问题。