【问题标题】:How to find if a referenced object can be deleted?如何查找是否可以删除引用的对象?
【发布时间】:2010-02-19 07:21:48
【问题描述】:

我有一个名为“Customer”的对象,它将在其他表中用作外键。

问题是我想知道是否可以删除“客户”(即,它没有在任何其他表中被引用)。

Nhibernate 可以做到这一点吗?

【问题讨论】:

  • 您说“其他表”中引用了Customer。是复数吗?即,Customer 是从 几个 其他实体类引用的吗? (答案会影响可能的解决方案。)
  • @Jorn,是的,它在许多其他表格中被引用。

标签: nhibernate foreign-keys cascade cascading-deletes


【解决方案1】:

您要问的是在引用的表 FK 列中找到 Customer PK 值的存在。 有很多方法可以解决这个问题:

  1. 如 kgiannakakis 所述,尝试执行删除操作,如果抛出异常则回滚。有效但丑陋且无用。这还要求您在数据库中设置 CASCADE="RESTRICT"。此解决方案的缺点是您必须尝试删除对象才能发现无法删除

  2. 将引用 Customer 的实体映射为集合,然后为每个集合映射,如果它们的 Count > 0 不允许删除。这很好,因为只要映射完成,这对架构更改是安全的。这也是一个糟糕的解决方案,因为必须进行额外的选择。

  3. 有一个方法可以执行类似bool IsReferenced(Customer cust) 的查询。很好,因为您可以有一个查询,您可以在需要时使用它。不太好,因为它可能由于架构和/或域更改而容易出错(取决于您将执行的查询类型:sql/hql/criteria)。

  4. 它自己的类的计算属性,带有<property name="IsReferenced" type="long" formula="sql-query that sums the Customer id usage in the referenced tables" /> 之类的映射元素。很好,因为它是一个快速的解决方案(至少与您的数据库一样快),没有额外的查询。不太好,因为它容易受到架构更改的影响,因此当您更改数据库时,您一定不要忘记更新此查询。

  5. 疯狂的解决方案:创建进行计算的模式绑定视图。需要时对其进行查询。很好,因为它的模式绑定并且不太容易受到模式更改的影响,很好,因为查询很快,不太好,因为您仍然需要进行额外的查询(或者您将此视图的结果映射到解决方案 4。)

2,3,4 也很好,因为您也可以将此行为投射到您的 UI(不允许删除)

我个人会选择 4,3,5 的偏好

【讨论】:

  • 这是一个很好的答案,很难比这个更具体地回答,因为你的情况的细节不是很清楚。对您的问题的简短回答是“是”。您可以想象在数据库上运行的任何查询都可以在 nHibernate 中以一种或另一种方式进行(如果您不满意,请使用 HQL)。
【解决方案2】:

我想知道是否可以删除“客户”(即,它没有在任何其他表中被引用)。

确定是否可以删除客户实际上并不是数据库的责任。它是业务逻辑的一部分

您要求检查数据库的引用完整性。

在非 OOP 世界中是可以的。 但是在处理对象时(像您一样),您最好将逻辑添加到您的对象中(对象具有状态和行为;DB - 只有状态)。

所以,我会在 Customer 类中添加一个方法来确定它是否可以被删除。这样您可以正确(单元)测试功能

例如,假设我们有一个规则客户只有在没有订单且没有参加论坛的情况下才能被删除

然后您将拥有与此类似的 Customer 对象(最简单的情况):

public class Customer
{
    public virtual ISet<Order> Orders { get; protected set; }
    public virtual ISet<ForumPost> ForumPosts { get; protected set; }

    public virtual bool CanBedeleted
    {
        get
        {
            return Orders.Count == 0 && ForumPosts.Count == 0
        }
    }
}

这是一个非常干净和简单的设计,易于使用、测试并且不严重依赖 NHibernate 或底层数据库。

你可以这样使用它:

if (myCustomer.CanBeDeleted)
    session.Delete(mycustomer)

除此之外,如果需要,您还可以微调 NHibernate 以删除相关订单和其他关联。


注意:当然,上面的示例只是最简单的说明性解决方案。您可能想制定这样的规则part of the validation,在删除对象时应该强制执行。

【讨论】:

  • 您正在(至少)通过调用 [collection].Count 属性进行 2 次额外选择。此外,格式不正确的单元测试(或不存在的单元测试)将通过一个条件。这可以通过应用数据库约束来保存!后端不依赖前端的业务逻辑数据完整性始终是一个好习惯
  • 您可以通过使用eager fetch或其他方式微调映射中的SQL,目的是为了展示这个想法。至于“这可以通过应用数据库约束来保存”——我提到它也应该是验证的一部分。
  • 我认为检查数据库引用完整性不是业务逻辑的一部分 - 我认为只有当您的程序是数据库程序时才会出现这种情况。
【解决方案3】:

考虑实体和关系而不是表和外键,有这些不同的情况:

  • 客户有一个一对多的关系,它建立了客户的一部分,例如他的电话号码。它们也应该通过级联的方式删除。
  • 客户具有不属于客户的一对多或多对多关系,但客户已知/可访问它们。
  • 其他一些实体与客户有关系。它也可以是任何类型(它不是数据库中的外键)。例如客户的订单。客户不知道订单。这是最难的情况。

据我所知,NHibernate 没有直接的解决方案。有元数据 API,它允许您在运行时探索映射定义。恕我直言,这是错误的做法。

在我看来,业务逻辑有责任验证实体是否可以删除。 (即使有确保数据库完整性的外键和约束,它仍然是业务逻辑)。

我们实现了在删除实体之前调用的服务。软件的其他部分注册某些类型。他们可以否决删除(例如通过抛出异常)。

例如,订单系统注册删除客户。如果要删除一个客户,订单系统会搜索该客户的订单,如果找到则抛出。

【讨论】:

  • 你让我想到这是在业务逻辑中
【解决方案4】:

这是不可能的。大概您的域模型包括客户的相关对象,例如地址、订单等。您应该为此使用specification pattern

public class CustomerCanBeDeleted
{

    public bool IsSatisfiedBy(Customer customer)
    {
        // Check that related objects are null and related collections are empty
        // Plus any business logic that determines if a Customer can be deleted
    }
}

编辑添加:

也许最直接的方法是创建一个存储过程来执行此检查并在删除之前调用它。您可以从 NHibernate (ISession.Connection.CreateCommand()) 访问 IDbCommand,以便调用与数据库无关。

另请参阅对this question 的回复。

【讨论】:

    【解决方案5】:

    可能值得查看 cascade 属性,尤其是 hbm.xml 文件中的 all-delete-orphan ,这可能会为您解决问题。

    See here, 16.3 - Cascading Lifecycle

    【讨论】:

      【解决方案6】:

      一个天真的解决方案是使用事务。启动事务并删除对象。异常会通知您无法删除该对象。无论如何,做一个回滚。

      【讨论】:

        【解决方案7】:

        将引用 Customer 的实体映射为集合。用特定的后缀命名您的 Customer 类中的每个集合。例如,如果您的 Customer 实体有一些 Orders,则将 Orders 集合命名如下:

        public virtual ISet<Order> Orders_NHBSet { get; set; } // add "_NHBSet" at the end 
        

        现在通过使用反射,您可以在运行时获取客户的所有属性,并获取其名称以您定义的后缀结尾的那些属性(在本例中为“_NHBSet”)然后检查每个集合是否包含任何元素,如果是,请避免删除客户。

        public static void DeleteCustomer(Customer customer)
        {
           using (var session = sessions.OpenSession())
           {
               using (var transaction = session.BeginTransaction())
               {
        
                   var listOfProperties =typeof(Customer).GetProperties();
                   foreach (var classProperty in listOfProperties )
                   {
                        if (classProperty.Name.EndsWith("_NHBSet"))
                        {
                            PropertyInfo myPropInfo = typeof(Customer).GetProperty(classProperty.Name);
                            dynamic Collection =  myPropInfo.GetValue(customer, null);
                            if (Enumerable.FirstOrDefault(Collection) !=null)// Check if collection contains any element
                            {
                               MessageBox.Show("Customer Cannot be deleted");
                               return;
                            }   
                        }  
                    }
                    session.Delete(customer);
                    transaction.Commit();
              }
           }
        }
        

        这种方法的优点是,如果您将新集合添加到客户类中,您以后不必更改代码。并且您不需要将 sql 查询更改为 Jaguar suggested。 您唯一需要关心的是将特定后缀添加到新添加的集合中。

        【讨论】:

          猜你喜欢
          • 2023-03-30
          • 2018-09-09
          • 2020-04-10
          • 1970-01-01
          • 1970-01-01
          • 2011-02-25
          • 1970-01-01
          • 1970-01-01
          • 2021-12-30
          相关资源
          最近更新 更多