在EF中更新父实体时更新子实体

时间:2020-09-28 19:37:01

标签: c# entity-framework asp.net-core

您好社区,我正在尝试更新主类中的辅助数据,主类为 body * { box-sizing: border-box !important; } .history-img{ width: 60px; height: 60px; object-fit: cover; object-position: center; } .scrolling-wrapper{ max-height: 700px !important; flex-wrap: wrap !important; } .card{ width: 20%; height: 200px !important; margin: 0; } ,其中的quotation类包含对另外两个类的引用,{{1 }}类和cart。 我现在可以更新报价类的属性,并设法在报价中插入新商品,但是当插入新产品时,购物车ID变为空,在数据库的商品购物车表中,我具有商品ID和ID购物车。

如何更新报价中的产品?

如何防止cart articles变为空?

这是我的代码:

products

这已插入购物车商品表中: enter image description here

CarritoId为空,我不希望发生这种情况

1 个答案:

答案 0 :(得分:1)

如何防止CarritoId变为空?

最终不通过在控制器,视图和返回之间传递实体。看起来您正在向视图中发送实体,并且该实体正在Put中发送回去,但是您要发送的是序列化的JSON块并将其强制转换为实体。这样做会导致类似的问题,其中您的Model变成了实体图的序列化,当您开始将其附加到DbContext上以作为实体进行跟踪时,该图可能会丢失并丢失。您可能会遇到这种情况,发送给视图的数据不完整,或者通过附加顶层,您期望所有相关实体都将被附加和跟踪,但这种情况并不常见。更改FK参考也可能导致意外结果,而不是更新可用的导航属性。它还使您的应用程序容易受到篡改,因为恶意客户端或浏览器加载项可以修改发送到服务器的数据,以调整UI甚至不显示的值。现代的浏览器调试工具使这项工作变得非常简单。

理想情况下,控制器和视图应基于加载的实体与视图模型进行通信。这也可以简化传送的数据量,以减小有效负载大小。

为帮助缓解这种情况,请像对待方法接收实体一样进行处理。无论如何,您都将再次加载该实体,但在这种情况下,除了再次分离它以尝试附加传入的JSON对象之外,您什么都不做。例如此代码:

var cotizacionoriginal = context.Cotizaciones.Where(x => x.CotizacionId == cotizacion.CotizacionId).FirstOrDefault();

if (cotizacionoriginal != null)
{
    context.Entry(cotizacionoriginal).State = EntityState.Detached;
}

...绝对不会为您做任何事情。它说:“尝试在本地缓存或数据库中找到具有此ID的对象,如果找到它,则停止跟踪它。”

您无故有效地进入了数据库。这甚至不能断言该行存在,因为您使用的是“ OrDefault”变体。

一开始,它应显示为:

var cotizacionoriginal = context.Cotizaciones
    .Where(x => x.CotizacionId == cotizacion.CotizacionId)
    .Single();

这是“从具有此CotizacionId的本地缓存或数据库中加载一行”。这断言数据库中实际上存在匹配的行。如果传递到Put的记录的ID无效,则将引发异常。我们不想要分离它。

更多细节。由于我们要在此对象中操作子集合和引用,因此我们应该急于加载它们:

var cotizacionoriginal = context.Cotizaciones
    .Include(x => x.Carriyo)
        .ThenInclude(c => c.Articulo)
            .ThenInclude(a => a.Producto)
    .Where(x => x.CotizacionId == cotizacion.CotizacionId).Single();

对于较大的对象图,这可能会变得很罗word,因为您必须向下钻取每个相关实体链。更好的方法不是立即更新“整个”对象图,而是将其分解为较小的合法操作,一次可以处理一个实体关系。

下一步将是验证Put对象中传递的值。它看起来是完整的还是有什么不合适?至少我们应该检查当前的会话用户ID,以验证他们是否有权访问此已加载的Cortizacion行并有权对其进行编辑。如果不是,则抛出异常。网站的异常处理应确保记录任何严重的异常,例如尝试访问不存在或没有权限的行,以供管理员检查,并且当前会话应结束。可能有人在篡改系统,或者您有一个错误,可能会导致数据损坏。无论哪种方式,都应该对其进行检测,报告和修复,以防当前会话终止。

最后一步是浏览传入的对象图,并更改您的“原始”数据以使其匹配。再次重要的是,您不能信任/处理作为“实体”传入的参数,只能信任看起来像实体的反序列化数据。因此,如果产品在其中一个参考项目中发生更改,我们将获取一个参考并进行更新。

foreach (ArticuloCarrito articulo in cotizacion.Carrito.Articulos)
{
    if (articulo.ArticuloId == 0)
    { // TODO: Handle adding a new article if that is supported.
    }
    else
    {
        var existingArticulo = existingCarrito.Articulos.Single(x => x.ArticuloId == articulo.ArticuloId);
        if (existingArticulo.ProductoId != articulo.Producto.ProductoId)
        {
            var producto = context.Productos.Single(x => x.ProductoId == articulo.Producto.ProductoId);
            existingArticulo.Producto = producto;
        }
    }
}
await context.SaveChangesAsync();

可选地,在上面我们可以检查Articulo以查看是否添加了新行。 (尚无ID)如果我们有ID,请检查现有的Carrito商品是否有匹配的商品。如果找不到,则将导致异常。拥有一个后,我们将检查产品ID是否已更改。如果已更改,我们将不使用传入的“ Producto”,因为这是反序列化的JSON对象,因此我们转到Context加载引用并将其设置在现有行上。

上下文。SaveChanges每次操作仅应调用一次,而不是在循环内调用。

将值从分离的反序列化实体复制到跟踪实体时,可以使用:

context.Entry(existingArticulo).CurrentValues.SetValues(articulo); 

但是,仅当传入的对象中的值经过验证时,才应执行此操作。据我所知,这仅更新值字段,而不更新FK或对象引用。

希望这能为您提供一些思路,以尝试简化更新过程。