我正在使用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”的对象。 ----&GT; System.Data.SqlClient.SqlException:数据库中已经有一个名为“Person”的对象。
有什么想法吗?
答案 0 :(得分:12)
这对我来说一直是一个反复出现的问题。首先执行drop或使用execute方法没有解决问题(drop方法是执行Execute方法的快捷方法)。
查看NHibernate源代码后,我找到了问题的根源。 NHibernate使用哈希码将外键名存储在数据库中。然而,哈希码的问题在于它们会随着时间的推移而变化,clr-version和appdomain。您不能依赖哈希码来实现相等性。 (参考: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();
}
}
适合我的作品。我希望其他人也可以使用它。 这是我抓取sql脚本删除所有外键的地方:http://mafudge.mysite.syr.edu/2010/05/07/dropping-all-the-foreign-keys-in-your-sql-server-database/
答案 1 :(得分:9)
找到解决此问题的方法。我刚刚将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]中使用上面的代码效果很好。我现在有一个干净的数据库模式用于集成测试。
答案 2 :(得分:3)
我也收到此错误消息;然而,这是因为我删除了一个与另一个班级有多对一关系的班级。因此,没有什么可以告诉NHibernate在下一次测试运行中放弃[现在的孤立但仍然引用的]表。
我只是手工丢弃它,一切都很好。
答案 3 :(得分:0)
我有同样的问题,我发现Execute方法似乎只能正常工作才能完全删除并重新创建,如果你这样称呼它:
new SchemaExport(cfg).Execute(true, true, false, false)
我不确定脚本参数为什么需要为true,但将其设置为true也可以解决问题。