首页 文章

NHibernate SchemaExport无法删除表...有时候

提问于
浏览
4

我正在使用NHibernate作为我的应用程序的DAL,并且在特定的NHibernate的SchemaExport函数中,在执行单元测试之前删除/重新创建我的数据库模式 . 我遇到的问题是,当我运行单元测试并执行SchemaExport时,我的一个表无法每秒都丢失 . 这将向我表明存在某种外键问题,导致SchemaExport放弃了我的表 - 但我无法弄明白 . 我的架构非常简单 - 一个人员表,一个地址表和一个PersonAddress表来支持两者之间的多对多关系 .

public class Person
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<Address> Addresses {get;set;}

    public Person()
    {
        this.Addresses = new List<Address>();
    }

}

public class Address
{
    public virtual int Id { get; set; }
    public virtual string Street1 { get; set; }
    public virtual string Street2 { get; set; }
    public virtual string Postcode { get; set; }

}

和我的NHibernate映射文件......

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
               assembly="MyHibernate"
               namespace="MyHibernate"
               >
<class name="Person" table="Person.Person">
    <id name="Id" column="Id">
        <generator class="native" ></generator>
    </id>
    <property name="Name" column="Name" length="50"></property>
    <bag name="Addresses" table="[Person].[PersonAddress]" lazy="false" cascade="all">
        <key column="PersonId" foreign-key="FK_Person_Person_Id"></key>
        <many-to-many class="Address" column="AddressId"></many-to-many>
    </bag>
</class>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
               assembly="MyHibernate"
               namespace="MyHibernate"
               >
<class name="Address" table="Person.Address">
    <id name="Id" column="Id">
        <generator class="native" ></generator>
    </id>
    <property name="Street1" column="Street1" length="50"></property>
    <property name="Street2" column="Street2" length="50"></property>
    <property name="Postcode" column="Postcode" length="50"></property>
</class>

当我运行`var cfg = new Configuration(); cfg.Configure(); cfg.AddAssembly(typeof运算(人).Assembly);

new SchemaExport(cfg).Execute(false, true, false, false)

我得到一个SQL异常说:

MyHibernate.Tests.GenerateSchemaFixture.Can_Generate_Schema:NHibernate.HibernateException:数据库中已经有一个名为“Person”的对象 . ----> System.Data.SqlClient.SqlException:数据库中已经有一个名为“Person”的对象 .

有任何想法吗?

4 回答

  • 9

    这对我来说一直是一个反复出现的问题 . 首先执行drop或使用execute方法没有解决问题(drop方法是执行Execute方法的快捷方法) .

    在查看NHibernate源代码后,我找到了问题的根源 . NHibernate使用哈希码将外键名存储在数据库中 . 然而,哈希码的问题在于它们会随着时间的推移而变化,clr-version和appdomain . 您不能依赖哈希码来实现相等性 . (ref:http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx)这就是为什么NHibernate不能总是删除外键,因此不能删除表 .

    这是来自NHibernate源代码的快照,用于创建唯一的外键名称:

    public string UniqueColumnString(IEnumerable iterator, string referencedEntityName)
    {
        // NH Different implementation (NH-1339)
        int result = 37;
        if (referencedEntityName != null)
        {
            result ^= referencedEntityName.GetHashCode();
        }
        foreach (object o in iterator)
        {
            result ^= o.GetHashCode();
        }
        return (name.GetHashCode().ToString("X") + result.GetHashCode().ToString("X"));
    }
    

    所以这个问题不会被NHibernate解决,你必须自己做 . 我在创建模式之前通过执行以下方法解决了该问题 . 该方法仅从使用NHibernate映射的表中删除所有外键:

    private static void DropAllForeignKeysFromDatabase()
    {
      var tableNamesFromMappings = Configuration.ClassMappings.Select(x => x.Table.Name);
    
      var dropAllForeignKeysSql =
        @"
          DECLARE @cmd nvarchar(1000)
          DECLARE @fk_table_name nvarchar(1000)
          DECLARE @fk_name nvarchar(1000)
    
          DECLARE cursor_fkeys CURSOR FOR
          SELECT  OBJECT_NAME(fk.parent_object_id) AS fk_table_name,
                  fk.name as fk_name
          FROM    sys.foreign_keys fk  JOIN
                  sys.tables tbl ON tbl.OBJECT_ID = fk.referenced_object_id
          WHERE OBJECT_NAME(fk.parent_object_id) in ('" + String.Join("','", tableNamesFromMappings) + @"')
    
          OPEN cursor_fkeys
          FETCH NEXT FROM cursor_fkeys
          INTO @fk_table_name, @fk_name
    
          WHILE @@FETCH_STATUS=0
          BEGIN
            SET @cmd = 'ALTER TABLE [' + @fk_table_name + '] DROP CONSTRAINT [' + @fk_name + ']'
            exec dbo.sp_executesql @cmd
    
            FETCH NEXT FROM cursor_fkeys
            INTO @fk_table_name, @fk_name
          END
          CLOSE cursor_fkeys
          DEALLOCATE cursor_fkeys
        ;";
    
        using (var connection = SessionFactory.OpenSession().Connection)
        {
          var command = connection.CreateCommand();
          command.CommandText = dropAllForeignKeysSql;
          command.ExecuteNonQuery();
        }
      }
    

    适合我的作品 . 我希望其他人也可以使用它 . 这是我 grab sql脚本删除所有外键的地方:http://mafudge.mysite.syr.edu/2010/05/07/dropping-all-the-foreign-keys-in-your-sql-server-database/

  • 12

    找到解决这个问题的方法 . 我刚刚将SchemaExport用于两次调用 . 第一个删除现有模式,第二个重新创建它 .

    var cfg = new Configuration();
     cfg.Configure();
     cfg.AddAssembly(typeof(Person).Assembly);
    
     SchemaExport se = new SchemaExport(cfg);
     //drop database 
     se.Drop(true, true);
     //re-create database
     se.Create(true, true);
    

    在我的测试类的[TestFixtureSetUp]中使用上面的代码效果很好 . 我现在有一个干净的数据库模式用于集成测试 .

  • 0

    我也收到此错误消息;然而,这是因为我删除了一个与另一个 class 有多对一关系的 class . 因此,没有任何迹象表明NHibernate在下一次测试运行时放弃了[现在的孤立但仍被引用的]表 .

    我只是手工丢弃它,一切都很顺利 .

  • 3

    我有同样的问题,我发现Execute方法似乎只能正常工作才能完全删除并重新创建,如果你这样调用它:

    new SchemaExport(cfg).Execute(true, true, false, false)
    

    我不确定脚本参数为什么需要为true,但将其设置为true也可以解决问题 .

相关问题