如何删除与Entity Framework的一对多关系中的子记录?

时间:2013-03-13 14:59:06

标签: entity-framework parent-child

这个问题已被讨论过很多次,但我仍然无法找到这个问题的结论性答案。我为了冗长的解释而道歉,这对我来说意味着你花时间阅读它并理解我的问题。

我正在为基于预订的多层商业企业实体开发一个停车场管理系统,客户打电话给系统管理员,让他们知道他们希望在什么日期和时间段停车场可供他们使用。

如果有人在系统中注册新的停车场,他们首先必须注册并选择一个楼层。所以楼层和停车场之间存在一对多的关系。一层可以有很多停车场。这就是我的课程的实现方式:

场地类

public class Nivel
{
    #region Campos

    int _IdNivel;

    #endregion

    #region Propiedades

    public int IdNivel
    {
        get
        {
            return _IdNivel;
        }

        private set
        {
            _IdNivel = value;
        }
    }

    public string NombreNivel { get; set; }
    public int CantidadParqueos { get; set; }
    public List<Parqueo> Parqueos { get; set; }

    #endregion

    #region Constructores

    public Nivel()
    {
        Parqueos = new List<Parqueo>();
    }

    #endregion
}

停车场等级

public class Parqueo
{
    #region Campos

    int _IdParqueo;
    int _IdNivel;

    #endregion

    #region Propiedades

    public int IdParqueo
    {
        get
        {
            return _IdParqueo;
        }

        private set
        {
            _IdParqueo = value;
        }
    }

    public int IdNivel
    {
        get
        {
            return _IdNivel;
        }
        private set
        {
            _IdNivel = value;
        }
    }

    public string NombreParqueo { get; set; }
    public string EstadoParqueo { get; set; }
    public Nivel Nivel { get; set; }

    #endregion

    #region Constructores

    public Parqueo()
    {

    }

    public Parqueo(string NombreParqueo, string EstadoParqueo)
    {            
        this.NombreParqueo = NombreParqueo;
        this.EstadoParqueo = EstadoParqueo;
    }

    #endregion
}

现在这一切都很好,花花公子。正如标题所说,我的主要问题与删除特定楼层的特定停车场有关。我已经解决了这个问题,但我想要一个比我已经拥有的解决方案更优雅的解决方案。这就是我在停车场登记表格中的表格加载事件:

    private void FrmRegistroParqueos_Load(object sender, EventArgs e)
    {
        this.groupBox3.Left = (this.Parent.Width / 2) - (this.groupBox3.Width / 2);
        this.groupBox3.Top = (this.Parent.Height / 2) - (this.groupBox3.Height / 2);            

        contexto.Niveles.Include("Parqueos").Load();            

        bindingSourceNiveles.DataSource = contexto.Niveles.Local.ToBindingList<Nivel>();

        bindingSourceParqueos.DataSource = bindingSourceNiveles;

        bindingSourceParqueos.DataMember = "Parqueos";

        this.DgvNiveles.DataSource = bindingSourceNiveles;
        this.DgvParqueos.DataSource = bindingSourceParqueos;            

        if (this.DgvNiveles.Rows.Count > 0)
        {
            this.DgvNiveles.ClearSelection();
        }
    }

简而言之,这就是我正在做的事情:我正在实例化上下文,以及两个绑定源,这样我就可以实现一个包含两个datagridviews的主/详细信息表单,以便在其中一个datagridviews上选择一个特定的楼层使用所选楼层上的停车场填充其他数据网格视图。然后,我对上下文运行查询,以便我可以加载我的数据库中可用的所有楼层,包括停车场,以填充绑定源并将信息反映到datagridviews。

这是我删除选定停车场的代码:

    private void BtnEliminarParqueo_Click(object sender, EventArgs e)
    {
        ((Nivel)bindingSourceNiveles.Current).CantidadParqueos -= 1;

        bindingSourceNiveles.ResetCurrentItem();            

        Parqueo parqueoEliminado = new Parqueo();

        parqueoEliminado = (Parqueo)bindingSourceParqueos.Current;

        bindingSourceParqueos.RemoveCurrent();

        contexto.Parqueos.Remove(parqueoEliminado);

        contexto.SaveChanges();
    }

您知道有关实体框架的一些背景信息。从其中一个导航对象集合中删除相关的子对象并不会实际从数据库中删除该对象:它只删除父对象与子对象之间的关系。现在,在我的代码中,地板和停车场之间建立的关系是严格的 - 您无法添加停车场而不会将其与特定楼层相关联。这意味着停车场桌子上的外键是不可空的。因此,通过将其从地板的停车场集合中删除而删除我的程序中的特定停车场违反了此规则,然后上下文抛出异常,说我在调用SaveChanges方法时尝试将空值设置为外键在上下文中。

由于这种繁琐的行为,我在尝试解决之前尝试了一种解决方法:如果实体框架不允许我通过程序中的对象删除子对象,那么我会通过删除它来删除它上下文本身,然后这些变化反映回我的停车场&#39; datagridview和用户可以看到停车场确实已被删除。现在,这种方法可以解决问题。从某种意义上说它确实从数据库中删除了子记录,但随后停车场datagridview抛出索引越界异常,说明删除的停车场所在的位置没有任何内容。这是一个例外的图片,我为此无法插入图片道歉(我没有足够的声誉点,这是我第一次在这里提问):

https://www.dropbox.com/s/vank28utf7bpzdg/Exception.png?m

这个失败的解决方法的代码给了我一个讨厌的异常(顺便说一句,点击消息框上的ok按钮后触发了3次以上):

    private void BtnEliminarParqueo_Click(object sender, EventArgs e)
    {
        ((Nivel)bindingSourceNiveles.Current).CantidadParqueos -= 1;

        bindingSourceNiveles.ResetCurrentItem();            

        contexto.Parqueos.Remove((Parqueo)bindingSourceParqueos.Current);

        contexto.SaveChanges();
    }

虽然它失败了,但这是我想在我的程序中实现的,在我看来,它应该是事情的运作方式。通过对象集合删除当前选定的对象,并将其反射回数据库或从数据库中删除子记录,并让它反映对datagridviews的更改 - 这就是我想要的。但是没有一个工作,第一个解决方案不起作用,因为我的外键中没有空值而第二个因为datagridview抛出的异常。我只是希望能够以一种用户可以在datagridview中实际看到的方式删除我的datagridview中的记录,并将其从数据库中删除。

那么我就达到了目前的解决方案:

1 - 实例化新的停车场

2 - 将其设置为停车场datagridview上当前选定的停车场

3 - 通过停车场的绑定源RemoveCurrent()方法删除当前选定的对象,以便用户可以看到停车场已从datagridview中删除。此时,通过将此对象的外键属性设置为null,将关系设置为在上下文中删除

4 - 通过上下文的Remove()方法从数据库中删除已删除的记录

5 - 在上下文中调用SaveChanges()方法,以便从数据库中删除子记录

基本上我正在做的是删除对象两次,一次以便用户可以在datagridview中看到停车场已被删除,并且再一次从数据库中删除不相关的子对象(空外键)。这样,当我调用SaveChanges时,我可以保持上下文不抱怨子记录具有空外键。

在我看来,这是一个丑陋的问题解决方案,我想对你可能有的任何其他方法提出一些反馈意见。提前感谢你抽出时间阅读这篇长篇文章的墙壁,并为我如此冗长而道歉,但我觉得有必要了解到底发生了什么。

有个好日子。

1 个答案:

答案 0 :(得分:0)

您可以尝试将一对多关系设为识别关系。您可以通过将Parqueo.IdNivelParqueo的外键Nivel添加到主键中来完成此操作。 Parqueo的主键成为复合键,然后由(IdParqueo, IdNivel)组成。

线索是,在识别关系中,为了删除依赖项,您只需从Parqueo集合中删除Nivel.Parqueos即可。当您调用SaveChanges时,将自动从数据库中删除已删除的Parqueo,从而避免外键约束违规。您不必调用contexto.Parqueos.Remove(parqueo)将其从数据库中删除。

有关识别关系的更多细节和示例如下:

https://stackoverflow.com/a/11033988/270591