如何查找是否可以删除引用的对象?

时间:2010-02-19 07:21:48

标签: nhibernate foreign-keys cascade cascading-deletes

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

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

Nhibernate有可能吗?

7 个答案:

答案 0 :(得分:5)

您要问的是在引用的表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上。)

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

    就个人而言,我会选择4,3,5这个偏好

答案 1 :(得分:4)

  

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

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

您要求检查数据库的参照完整性。

在非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

答案 2 :(得分:2)

在实体和关系中思考而不是表和外键,有以下不同的情况:

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

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

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

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

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

答案 3 :(得分:1)

直接不可能。据推测,您的域模型包括客户的相关对象,例如地址,订单等。您应该使用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的回复。

答案 4 :(得分:0)

可能值得查看cascade属性,特别是hbm.xml文件中的all-delete-orphan,这可能会为您处理。

See here, 16.3 - Cascading Lifecycle

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

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

现在,通过使用反射,您可以在运行时获取Customer的所有属性,并获取其名称以您定义的后缀结尾的那些属性(在这种情况下为“ _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。 您唯一需要关心的就是将特定的后缀添加到新添加的集合中。