使用Breeze删除实体框架对象图 [英] Entity Framework object graph deletion with Breeze

查看:60
本文介绍了使用Breeze删除实体框架对象图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了一个反复出现的问题,这是没有意义的,希望有人(在Breeze团队中?)可以阐明一些想法。



以下模型说明了





如您所见,我非常严格地遵循属性名称中的Entity Framework约定,因此,如果我签入SQL,则删除规则的级联是



现在,当我尝试在其中手动删除 BusUnit 时,先由EF代码设置SQL,删除正确级联,并且相应的 BusUnitDimensions 也应删除。同样,如果我在SQL中删除 Dimension ,相应的 BusUnitDimensions 也将被删除。



但是,在我的应用程序中,如果我用Breeze和标记 BusUnit setDeleted 然后尝试 saveChanges ,出现以下错误。

 操作失败:无法更改该关系,因为一个
或多个外键属性不可为空。当对关系进行
更改时,相关的外键属性设置为空的
值。如果外键不支持空值,则必须定义新的关系
,必须为外键属性分配另一个
非空值,或者必须删除不相关的对象。

不过,奇怪的是,如果我标记维度进行删除然后保存(在Breeze内),级联删除可以正常工作,并且 Dimension 及其相应的 BusUnitDimensions



那么,为什么不一致?为什么在SQL中的级联删除规则未应用于 BusUnits ,但是它们却在 Dimensions 中起作用?我在其他地方读过,Breeze不支持级联删除,但是为什么我的 Dimensions 案例起作用?






编辑:



我已删除了之前的修改,因为它们不相关。下面的更改来自Ward的答案...



我的模型现在看起来像这样,并且 BusUnitDims 现在使用 BusUnitId DimId 作为复合键,并且我添加了 bool IsBud 用于有效负载。





我尚未为BusUnits实现删除,但是如果我尝试删除删除Dim,我得到的是相同的错误消息:

 操作失败:由于一个$不能更改关系b $ b或更多的外键属性不可为空。当对关系进行
更改时,相关的外键属性设置为空的
值。如果外键不支持空值,则必须定义新的关系
,必须为外键属性分配另一个
非空值,或者必须删除不相关的对象。

我注意到不再启用级联删除,实际上是为了获得EF来构建数据库I添加以下配置:

  modelBuilder.Entity< BusUnitDim>()
.HasRequired(bud => ; bud.BusUnit)
.WithMany(bu => bu.BusUnitDims)
.HasForeignKey(bud => bud.BusUnitId)
.WillCascadeOnDelete(false);

modelBuilder.Entity< BusUnitDim>()
.HasRequired(bud => bud.Dim)
.WithMany(d => d.BusUnitDims)
.HasForeignKey(bud => bud.DimId)
.WillCascadeOnDelete(false);

因此,由于现在没有明确地级联,我可以理解为什么会发生错误。这是否意味着在控制器中,必须在删除父Dim或BusUnit时以及在调用saveChanges之前专门标记每个要删除的映射,或者是否有某种方法可以配置EF以利用级联删除的优势,因为这将极大地简化



(PS:它变得更加复杂,因为 BusUnitDims 最终有一个进一步的联接自己的表 MetricBusUnitDims 来容纳模型中的另一个实体及其关系。这就是为什么我试图尽早使原理正确的原因






编辑:(针对公共汽车的控制器解决方案)



因此,以下该方法适用于 BusUnits

  function deleteBusUnit(busUnitVm){//请注意,您传递的是视图模型项,而不是实体
var busUnit = busUnitVm.busUnit;
var mapVms = busUnitVm.dimMapVms;
var dimHash = createBusUnitDimHash(busUnit);

mapVms.forEach(function(mapVm){
var map = dimHash [mapVm.dim.id];
if(map){
datacontext.markDeleted(地图);
}
});
datacontext.markDeleted(busUnit);
save()。then(function(){getDBoardConfig();});
}
}

这是正确的方法吗?如果是这样,我仍然需要弄清楚以下内容:




  • 如何处理点心。这些是不同的,因为已为BusUnits定义了项目视图模型。

  • 如何处理连接表向下一层的情况,例如 MetricBusUnitDIm






编辑:(用于DIMS的控制器解决方案)

  function deleteDim(dim){
返回bsDialog.deleteDialog(dim.name) ,true)
.then(function(){
vm.busUnitVms.forEach(function(busUnitVm){
busUnitVm.busUnit.busUnitDims.forEach(function(bud){
if(bud.dimId === dim.id){
datacontext.markDeleted(bud);
}
});
});
datacontext.markDeleted (dim);
save()。then(function(){getDboardConfig();});
});
}


解决方案

我相信您的问题是可以追溯的映射表 BusUnitDimension 有其自己的主键 Id 的事实,与之相比,其中 BusUnitId DimensionId FK属性一起构成 BusUnitDimension


在Northwind和<$ c中观察 OrderDetails 在Breeze多对多示例中,$ c> HeroPoweMap 具有复合键。


您的选择将创建



首先,可以创建表示 BusUnitDimension 实体> BusUnit 和 Dimension (即它们都具有相同的一对FK)。数据库也许可以防止这种情况发生(自从我查看以来已经很长时间了),但是无论是否允许,它都不会阻止您在Breeze中创建这些重复项……也许在EF中也是如此。 / p>

其次,它使您了解当前面临的问题。如果执行删除操作时这些映射实体位于 DbContext 中,则EF可能(显然确实)尝试将其FK属性设置为空,因为它设置了 BusUnit 维度到已删除状态。



建议将 BusUnitId DimensionId FK属性都设置为空。但这与语义相反,因为 BusUnitDimension 必须将真实的 BusUnit 链接到真实的维度;它们不是可选的。实际的结果可能是,如果您这样做,从EF角度来看不会得到级联删除(不确定数据库是否会强制执行该操作)。这意味着您将在数据库中孤立了 BusUnitDimension 行,其中一个或两个FK为空。我推测是因为我不习惯遇到这种麻烦。



另一种方法是将其FK值设置为零(我认为Breeze会为您做到这一点)。当然,这意味着存在 BusUnit Dimension 表行,其中 Id == 0 ,如果仅在删除操作期间。


Btw,您实际上可以在数据库中拥有这样的前哨实体 。


您必须确保这些 BusUnitDimension 处于删除状态,或者EF(和数据库)将拒绝它们(参照完整性约束)或孤立它们(数据库中的 BusUnitDimension 行,其中一个或两个FK均为零)。



或者,如果您知道数据库将级联删除它们,则只需将它们从 DbContext (从 EFContextProvider 中的 EntityInfoMap 中删除​​)。但是现在,如果碰巧它们挂在身上,您必须告诉Breeze客户也摆脱它们。



已经足够了!



这些徘徊的想法应该告诉您,您在这里陷入了困境,簿记太多了……这都是因为您给了 BusUnitDimension 自己的 Id 主键。



如果提供 BusUnitDimension 复合键{ BusUnitId DimensionId }。您还必须给它提供有效负载属性(可以做任何事情),以防止EF将其隐藏在多对多实现中,因为Breeze不会处理它。添加任何废话属性都可以解决问题。



HTH


I am encountering a recurring problem that just makes no sense, and hoping someone (in the Breeze team?) can shed some light.

The following model illustrates the entities in question.

As you can see, I'm adhering pretty strictly to Entity Framework conventions in my property names, and as a result, if I check in SQL the cascade on delete rules are set by EF code first when it creates the db.

Now, when I try to delete a BusUnit manually in SQL, the delete cascades correctly and the corresponding BusUnitDimensions are also deleted, as it should be. Likewise, if I delete a Dimension in SQL, the corresponding BusUnitDimensions are also deleted.

However, in my application, if I mark a BusUnit as setDeleted with Breeze and then try saveChanges, I get the following error.

The operation failed: The relationship could not be changed because one
or more of the foreign-key properties is non-nullable. When a change is
made to a relationship, the related foreign-key property is set to a null
value. If the foreign-key does not support null values, a new relationship
must be defined, the foreign-key property must be assigned another
non-null value, or the unrelated object must be deleted.

Strangely though, if I mark a Dimension for deletion and then save (within Breeze), the cascaded delete works correctly and both the Dimension and its corresponding BusUnitDimensions are deleted.

So, why the inconsistency? Why are the cascaded delete rules in SQL not being applied for BusUnits but yet they're working for Dimensions? I've read elsewhere that Breeze does not support cascaded deletes, but then why is my Dimensions case working?


EDIT:

I've removed my previous edits as they weren't relevant. The changes below follow on from Ward's answer...

My model now looks like this, and BusUnitDims now uses BusUnitId and DimId as a compound key, and I've added a bool, IsBud for the purposes of payload.

I haven't yet implemented deletes for BusUnits, but already if I try delete a Dim, I'm getting the same error message:

The operation failed: The relationship could not be changed because one
or more of the foreign-key properties is non-nullable. When a change is
made to a relationship, the related foreign-key property is set to a null
value. If the foreign-key does not support null values, a new relationship
must be defined, the foreign-key property must be assigned another
non-null value, or the unrelated object must be deleted.

I have noticed that cascaded deletes is no longer enabled, and in fact, to get EF to build the database I to add the following configuration:

modelBuilder.Entity<BusUnitDim>()
    .HasRequired(bud => bud.BusUnit)
        .WithMany(bu => bu.BusUnitDims)
        .HasForeignKey(bud => bud.BusUnitId)
        .WillCascadeOnDelete(false);

modelBuilder.Entity<BusUnitDim>()
    .HasRequired(bud => bud.Dim)
        .WithMany(d => d.BusUnitDims)
        .HasForeignKey(bud => bud.DimId)
        .WillCascadeOnDelete(false);

So, with cascading now explicitly not in place, I can understand why the error occurs. Does that imply that in the controller, one has to specifically mark each map for deletion when deleting a parent Dim or BusUnit and before saveChanges is called, or is there some way to configure EF to take advantage of cascaded deletes as this would hugely simplify the code in my controller?

(PS: it gets even more complex, because BusUnitDims ends up having a further join table of its own, MetricBusUnitDims to accommodate yet another entity in the model and their relationships. This is why I'm trying to get the principles right early on)


EDIT: (A CONTROLLER SOLUTION FOR BUSUNITS)

So, the following approach works for BusUnits:

function deleteBusUnit(busUnitVm) { // note that you pass in the item viewmodel, not the entity
    var busUnit = busUnitVm.busUnit;
    var mapVms = busUnitVm.dimMapVms;
    var dimHash = createBusUnitDimHash(busUnit);

    mapVms.forEach(function (mapVm) {
        var map = dimHash[mapVm.dim.id];
        if (map) {
            datacontext.markDeleted(map);
        }
    });
    datacontext.markDeleted(busUnit);
    save().then(function() { getDBoardConfig(); });
    }
}

Is this the correct approach? if so, I'll still have to figure out the following:

  • How to approach Dims. These are different becuase the item viewmodel is defined for BusUnits.
  • How to approach the situation where there is a join tabel one level down, e.g. MetricBusUnitDIm.

EDIT: (A CONTROLLER SOLUTION FOR DIMS)

function deleteDim(dim) {
    return bsDialog.deleteDialog(dim.name, true)
        .then(function () {
            vm.busUnitVms.forEach(function (busUnitVm) {
                busUnitVm.busUnit.busUnitDims.forEach(function (bud) {
                    if (bud.dimId === dim.id) {
                        datacontext.markDeleted(bud);
                    }
                });
            });
            datacontext.markDeleted(dim);
            save().then(function () { getDboardConfig(); });
        });
}

解决方案

I believe your problems are traceable to the fact that your mapping table BusUnitDimension has its own primary key, Id, as opposed to the more typical approach in which the BusUnitId and DimensionId FK properties together comprise the compound primary key of BusUnitDimension.

Observe that OrderDetails in Northwind and the HeroPoweMap in the Breeze many-to-many example have compound keys.

Your choice creates complications.

First, it becomes possible to create multiple BusUnitDimension entities representing the same association between BusUnit and Dimension (i.e., they all have the same pair of FKs). The database may be able to prevent this (it's been a long time since I looked) but whether it does or doesn't, it won't prevent you from creating those duplicates in Breeze ... and maybe not in EF either.

Secondly, it opens you up to the problem you're currently facing. If those mapping entities are in the DbContext when you perform the delete, EF may (apparently does) try to null their FK properties as it sets either BusUnit or Dimension to the deleted state.

You can get around this, as has been suggested, by making both the BusUnitId and DimensionId FK properties nullable. But that is contrary to the semantics as a BusUnitDimension must link a real BusUnit to a real Dimension; they aren't optional. The practical consequence may be that you don't get cascade delete from the EF perspective if you do this (not sure if the DB will enforce that either). That means you'd have orphaned BusUnitDimension rows in your database with one or both FKs being null. I speculate because I'm not used to getting into this kind of trouble.

Another approach would be to set their FK values to zero (I think Breeze does this for you). Of course this implies the existence of BusUnit and Dimension table rows with Id == 0, if only during the delete operation.

Btw, you could actually have such "sentinel entities" in your DB.

You must make sure that these BusUnitDimension are in the deleted state or EF (and the DB) will either reject them (referential integrity constraint) or orphan them (you'll have BusUnitDimension rows in your database with one or both FKs being zero).

Alternatively, if you know that the DB will cascade delete them, you can simply remove them from the DbContext (remove from the EntityInfoMap in the EFContextProvider). But now you have to tell the Breeze client to get rid of them too if it happens to have them hanging around.

Enough Already!

These wandering thoughts should tell you that you've got yourself in a jam here with way too much bookkeeping ... and all because you gave BusUnitDimension its own Id primary key.

It gets a lot easier if you give BusUnitDimension the compound key, {BusUnitId, DimensionId}. You must also give it a payload property (anything will do) to prevent EF from hiding it in its "many-to-many" implementation because Breeze doesn't handle that. Adding any nonsense property will do the trick.

HTH

这篇关于使用Breeze删除实体框架对象图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆