I have a few classes that look like this
public class Token
{
public int Id
{
get;
set;
}
public ITokenInstance Instance
{
get;
set;
}
}
public interface ITokenInstance
{
int Id
{
get;
set;
}
Token Token
{
get;
set;
}
}
and mapping files
<class name="Token" >
<id name="Id" >
<generator class="hilo" />
</id>
<any name="Instance" meta-type="class" id-type="Int32" cascade="all-delete-orphan">
<column name="instance_type" />
<column name="instance_id" />
</any>
</class>
<class name="TokenInstanceOne" >
<id name="Id" >
<generator class="hilo" />
</id>
<many-to-one name="Token" class="Token" column="token_id"/>
</class>
I have various implementations of the ITokenInstance interface, all looking in different tables but all using the same baisc structure as shown in the mapping. The problem is that whilst i can add a new ITokenInstance to a Token that has no instance set (null) and it will update correctly I can NOT add a new Instance to a Token that has already got an instance and then Update it, NHibernate will add the new instance i provide but not delete the now un-assigned instance. For example
Token token = Session.Get<Token>(4);
var instance = Session.Get<TokenInstanceOne>(1);
Assert.AreSame(token.Instance, instance);
var newInstance = new TokenInstanceOne();
token.Instance = newInstance;
newInstance.Token = token;
instance.Token = null;
Session.Flush();
This fires SQL to insert the new TokenInstance, and updates the token table to point at it, it does NOT delete the instance that was originaly set to the token. Does anyone know how I can instruct NHibernate to delete the original TokenInstance from the database
EIDT:
I had missed something off that is now included in the code example (setting original TokenInstance's Token reference to null).
Also just to clarify this is the SQL NHibernate is producing;
INSERT INTO TokenInstanceOne (token_id, Id) VALUES (#p0, #p1); #p0 = '4', #p1 = '32768'
UPDATE Token SET instance_type = #p0, instance_id = #p1 WHERE Id = #p2; #p0 = 'ClassLibrary1.TokenInstanceOne', #p1 = '32768', #p2 = '4'
UPDATE TokenInstanceOne SET token_id = #p0 WHERE Id = #p1; #p0 = '', #p1 = '1'
Notice the last Update is setting token_id = '', what i need is for NHibernate to delete the row instead.
NHibernate does not implement a so called persistent garbage collection. There are situations where you need to remove entities explicitly. The cascade is for the case when you delete the Token.
This is your code:
var token = Session.Get<Token>(4);
Assert.IsNotNull(token.Instance);
// remove the old token
Session.Delete(token.Instance);
// assign the new token
var newInstance = new TokenInstance();
token.Instance = newInstance;
newInstance.Token = token;
// don't need to call update, the token is in the session.
// (except you turned off session flush)
// Session.Update(token);
Sorry, I misunderstood your question.
Have you tried setting inverse="true" on your any end?
Alternatively move the cascade to th the other class' mapping.
Read
Related
When I use Nhibernate 3 to update children, the removed item in children doesn't delete from databse.
Case describe below
class Parent {
string Id;
string Name;
IList Children;
}
class Child {
int ChildId;
string Name;
string Value;
Parent parent;
}
hdm Mapping file shows below
Parent.hdm.xml
<bag name="Children" table="ClientsExt" inverse ="true" cascade="all-delete-orphan" lazy="false">
<key column="ChildId"/>
<one-to-many class="XXXX.Child, XXX"/>
</bag>
Child.hdm.xml
<many-to-one name="Parent" column="Id" class="XXXX.Parent, XXX" not-null="true"/>
Let's suppose there is existing parent which associates a set of children in database
Parent Table
Id = "P1", Name = "Test"
Child Table
ChildId = 1, Id="P1", Name = "N1", Value = "V1"
ChildId = 2, Id="P1",Name = "N1", Value = "V2"
ChildId = 3, Id="P1",Name = "N2", Value = "V3"
In my case, I need to update children partially.
In the updated Parent need to update the record 2
set value of ChildId = 2, to value = "NEWVALUE"
and remove ChildId = 1, Id="P1", Name = "N1", Value = "V1"
and ChildId 3 will be keep.
so I get the Parent from database firstly,
var entity = _parentRepo.getById("P1");
var children = entity.Children;
var updatedChildren = children.ToList<Child>;
var tmpList = new List<Child>();
//biz means the business logic object which contains update info
if (biz.N1.Count > 0){
var existN1 = children.Where(x=>x.Name.Equals("N1")).Select(y=>y.ChildId);
int count = existN1.Count;
int index = 0;
biz.N1.ForEach(x=>{
if(index < count){
tmpList.Add(new Child(){ Id = existN1[index],Name="N1",Value="newValue",Parent = entity });
}else{
tmpList.Add(new Child(){ Name="N1",Value="newValue",Parent = entity });
}
});
updatedChildren.RemoveAll(x=>x.Name.Equals("N1"));
updateChildren.AddRange(tmpList);
}
entity.Children = updateChildren;
//Save the entity
However, in Database, the record 2 update the value to "NEWVALUE", but didn't remove the ChildId = 1, Id="P1", Name = "N1", Value = "V1" .
Why?
thanks in advance.
What happend, is that the above code has broken the session principle, splitted the chain. Whenever we want NHibernate to do wise decisions, we must to keep its hands on the underlying stuff - all the time. And this is not in accordance:
var children = entity.Children;
var updatedChildren = children.ToList<Child>; // a brand new NHibernate-detached coll
...
// operations out of the scope of the NHiberante session
...
entity.Children = updateChildren; // broken chain of information
Behind the scene, NHibernates places its own smart Collection into the entity.Children property. It is tracking information about changes (removed elements, changed..) so if it is asked to persist changes... NHibernate knows...
If we put brand new, disconnected collection, NHibernate can hardly find out, that there is some element missing. No way how to issue DELETE.
Solution: work with the entity.Children reference all the time. Then we'll get what we need...
I would like to know if there is a way to create a Restriction on a primitive collection of a Model in NHibernate.3.3.3?
Here's the details:
class Parent {
IEnumerable<string> ChildNames { get; set; }
}
I need to search like so:
private DetachedCriteria BuildQuery() {
var inNames = { "Bob", "Sam", "Dan" };
var query = DetachedCriteria.For<Parent>("parent");
query.Add(Restrictions.In("ChildNames", inNames));
return query;
}
I found this old question that says it's not possible, but given the fact that it's old and doesn't have a ton of upvotes, I'd like to confirm before I refactor.
If I can do it and I'm totally botching it, I'll take that help as well!
In this scenario, we can use Projection (something less type-safe, then mapped Property, but more flexible).
Let's expect the mapping like this:
<bag name="ChildNames" inverse="false" lazy="true" table="[dbo].[ChildNames]"
cascade="all"
batch-size="25">
<key column="ParentId" />
<element type="System.String" column="ChildName" />
</bag>
Then we can adjust the Build query method like this:
protected virtual DetachedCriteria BuildQuery()
{
var inNames = new [] { "Bob", "Sam", "Dan" };
// parent query reference
var query = DetachedCriteria.For<Parent>("parent");
// reference to child query
var child = query.CreateCriteria("ChildNames");
// let's project the column name of the Element, e.g. 'Name'
var columnNameProjection = Projections.SqlProjection(
"ChildName as name", null, new IType[] { NHibernateUtil.String }
);
// in clause
child.Add(Restrictions.In(
columnNameProjection, inNames
));
return query;
}
And this is what we will get:
SELECT ...
FROM Parent this_
inner join [dbo].[ChildNames] childNames3_
on this_.ParentId=childNames3_.ParentId
WHERE ChildName in (#p0, #p1, #p2)
...
#p0=N'Bob',#p1=N'Sam',#p2=N'Dan'
The caveat:
While this is in deed working... the ChildName is used without the Alias. That could be pretty tricky to fulfill... so be careful, if there are more columns with the name ChildName in this scenario
I ended up, like many, refactoring the collection into a strong type.
I have a one-to-one relationship between an Envelope and a Transaction. I mapped it using a primary key association as below:
<class name="Envelope" table="T_ENVELOPE">
<id name="EnvelopeId" column="ENVELOPE_ID"><generator class="identity"/></id>
<one-to-one class="Transaction" cascade="all" name="Transaction" />
</class>
<class name="Transaction" table="T_TRANSACTION">
<id name="TransactionID" column="TRANSACTION_ID" type="long"><generator class="foreign"><param name="property">Envelope</param></generator></id>
<one-to-one name="Envelope" class="Envelope" constrained="true" />
</class>
This works fine, but my case is complicated since T_TRANSACTION has 2 FKs that both map to T_ENVELOPE's PK (ENVELOPE_ID): TRANSACTION_ID (as shown above) and OLD_DEPRECATED_TXN_ID. So, I have 2 FKs in T_ENVELOPE that have the exact same value (the envelope's id). How can I make NHibernate save ENVELOPE_ID not just in TRANSACTION_ID but also in OLD_DEPRECATED_TXN_ID as well?
I wish I didn't have this requirement, but I'm dealing with legacy code schemas and code. I know I can also do something like:
envelope.Save // this saves Envelope and Transaction's transaction ID but not OLD_DEPRECATED_TXN_ID
envelope.transaction.old_deprecated_txn_id = envelope.id
envelope.Save
But, that results in one insert and one update. How do I do a one insert only?
Here's an example:
T_ENVELOPE
----------
ENVELOPE_ID
1
9
121
T_TRANSACTION
-------------
TRANSACTION_ID | OLD_DEPRECATED_TXN_ID
1|1
9|9
121|121
Depending on your requirements you could:
Write a stored procedure to perform the insert and configure NHibernate to use this procedure in the mappings. I have taken this approach when working with legacy DBs and historic fields that were not being used but had to be populated
Use an Interceptor. Something along the followings lines should work. Not tested though!
public class TransactionEntityInterceptor : EmptyInterceptor
{
public override boolean OnSave(object entity,
object id,
object[] state,
string[] propertyNames,
IType[] types)
{
if (entity is Transaction)
{
Object TransactionID;
for ( int i=0; i<propertyNames.Length; i++ )
{
if ( "TransactionID" == propertyNames[i] )
{
TransactionID = state[i];
return true
}
}
for ( int i=0; i<propertyNames.Length; i++ )
{
if ( "OLD_DEPRECATED_TXN_ID" == propertyNames[i] )
{
state[i] = TransactionID;
return true;
}
}
return false;
}
else
{
return true;
}
}
}
** This question has been edited to make it simpler and more focused **
Employee has an EmployeeNumberValue property which I would like to have auto-incremented by the db. To the business domain, this is a unique id assigned to employees and used to identify them on employee cards, etc. To the database however, it is an alternate id and not the primary key.
NHib has a documented ability called Generated Properties.
Per the docs, "generated properties are properties which have their values generated by the database. Typically, NHibernate applications needed to Refresh objects which contain any properties for which the database was generating values. Marking properties as generated, however, lets the application delegate this responsibility to NHibernate. Essentially, whenever NHibernate issues an SQL INSERT or UPDATE for an entity which has defined generated properties, it immediately issues a select afterwards to retrieve the generated values."
The problem I am having is that while NHib is making the additional SELECT to update the EmployeeNumberValue, it is not assigning the retrieved value to the property.
Can anyone see why this is happening what the fix is?
Cheers,
Berryl
FAILING TEST AND OUTPUT (tested w/ SQLite in memory db):
[Test]
public void Employee_OnInsert_EmployeeNumberValueIsIncremented() {
var emp1 = new Employee
{
FullName = _fullName,
Department = _department,
};
var emp2 = new Employee
{
FullName = _fullName,
Department = _department,
};
var session = _SessionFactory.GetCurrentSession();
using (var tx = session.BeginTransaction())
{
session.Save(_department);
session.Save(emp1);
session.Save(emp2);
tx.Commit();
}
Assert.That(emp1.EmployeeNumberValue, Is.EqualTo(1));
Assert.That(emp2.EmployeeNumberValue, Is.EqualTo(2));
}
NHibernate: INSERT INTO Employees (FirstName, LastName, DepartmentId, EmployeeId)
VALUES (#p0, #p1, #p2, #p3);#p0 = 'Berryl' [Type: String (0)], #p1 = 'Hesh' [Type: String (0)], #p2 = 32768 [Type: Int32 (0)], #p3 = 65536 [Type: Int32 (0)]
NHibernate: SELECT employee_.EmployeeNumberValue as Employee2_1_ FROM Employees employee_ WHERE employee_.EmployeeId=#p0;#p0 = 65536 [Type: Int32 (0)]
NHibernate: INSERT INTO Employees (FirstName, LastName, DepartmentId, EmployeeId)
VALUES (#p0, #p1, #p2, #p3);#p0 = 'Berryl' [Type: String (0)], #p1 = 'Hesh' [Type: String (0)], #p2 = 32768 [Type: Int32 (0)], #p3 = 65537 [Type: Int32 (0)]
NHibernate: SELECT employee_.EmployeeNumberValue as Employee2_1_ FROM Employees employee_ WHERE employee_.EmployeeId=#p0;#p0 = 65537 [Type: Int32 (0)]
Test failed:
Expected: 1
But was: 0
OBJECT MODEL
public class Employee : Entity, IResource
{
public virtual long EmployeeNumberValue { get; set; }
...
}
MAPPING:
<class name="Employee" table="Employees">
<id name="Id" unsaved-value="0">
<column name="EmployeeId" />
<generator class="hilo" />
</id>
<property name="EmployeeNumberValue" generated="insert" insert="false" update="false" >
<column name="EmployeeNumberValue" sql-type="int IDENTITY(1,1)" index="IDX_EmployeeNumber" />
</property>
...
create table Employees (
EmployeeId INTEGER not null,
EmployeeNumberValue int IDENTITY(1,1),
FirstName TEXT not null,
LastName TEXT not null,
DepartmentId INTEGER,
primary key (EmployeeId)
)
I suspect the way I am marking the column as IDENTITY is also suspect. I tried using database-object as below, but got a usage error in doing so
<database-object>
<create>
ALTER TABLE Employee DROP COLUMN EmployeeNumberValue
ALTER TABLE Employee ADD EmployeeNumberValue INT IDENTITY
</create>
<drop>
ALTER TABLE Employee DROP COLUMN EmployeeNumberValue
</drop>
</database-object>
SQLiteException : SQLite error "DROP": syntax error
While that's doable, it's better to do it in the DB (using identity or a trigger) and map the property as generated on insert.
Check 5.5. Generated Properties
From a design perpective I wouldn't rely on NHibernate in this case. What I mean is, that in your domain model, you want an employee to get a new employee card number.
In this case I would only allow an employee to be instantiated if there is a card number.
public class EmployeeCardNumber
{
private string id = String.Empty;
internal EmployeeCardNumber(string id)
{
this.id = id;
}
}
public class Employee
{
private EmployeeCardNumber employeeCardNumber;
public EmployeeCardNumber CardNumber { ... }
public Employee(EmployeeCardNumber employeeCardNumber)
{
this.employeeCardNumber = employeeCardNumber;
}
}
So now you have to think about how to generate a unique EmployeeCardNumber.
public class EmployeeCardNumberFactory
{
public EmployeeCardNumber CreateNew()
{
// in this example the card number will be a guid.
// but you could also implement a "EmployeeCardNumberGenerator" class which will do crazy database stuff
return new EmployeeCardNumber(Guid.NewGuid().ToString());
}
}
Then you would later do:
EmployeeCardNumber cardNumber = employeeCardNumberFactory.CreateNew();
Employee employee = new Employee(cardNumber, name, etc...);
Addition:
To generate a "EmployeeCardNumber" via database, you could just map "EmployeeCardNumber" to an extra table "EmployeeCardNumber" that will serve as your identity generator like:
<class name="EmployeeCardNumber" table="EmployeeCardNumber">
<id name="id" access="field" unsaved-value="0">
<column name="EmployeeCardNumberId" />
<generator class="identity" />
</id>
</class>
Then in the factory you could do:
public class EmployeeCardNumberFactory
{
private IEmployeeCardNumberRepository repository = new EmployeeCardNumberRepository(); // inject...
public EmployeeCardNumber CreateNew()
{
EmployeeCardNumber cardNumber = new EmployeeCardNumber();
repository.Save(cardNumber); // gets you a fresh id
return cardNumber;
}
}
I had same scenario and it works very well in production.
Here is mapping (generated by Fluent NHibernate):
<property generated="insert" name="Number" update="false" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="Number" not-null="true" />
</property>
In database this column looks like this:
ALTER TABLE [DeviceLink] ADD [Number] INT not null IDENTITY (1, 1)
I'm using NHibernate for the DAL of my application, and in particlular NHibernate's SchemaExport function to drop/recreate my database schema before the execution of unit tests. The issue I'm having is that when I run the unit tests and execute SchemaExport one of my tables fails to drop every second time. This would indicate to me that there is some kind of foreign key issue preventing SchemaExport dropping my table - but I can't figure it out. My schema is very simple - A person table, an Address table and a PersonAddress table to support the many-to-many relationship between the two.
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; }
}
and my NHibernate mapping files...
<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>
and when I run `var cfg = new Configuration();
cfg.Configure();
cfg.AddAssembly(typeof(Person).Assembly);
new SchemaExport(cfg).Execute(false, true, false, false)
I get a SQL exception saying:
MyHibernate.Tests.GenerateSchemaFixture.Can_Generate_Schema:
NHibernate.HibernateException : There is already an object named 'Person' in the database.
----> System.Data.SqlClient.SqlException : There is already an object named 'Person' in the database.
Any ideas?
This has been a recurring problem for me for a long time. The problem was not solved by executing drop first or using the execute method (the drop method is a shortcut method which executes the Execute method).
After looking in the NHibernate source code I found the source of the problem. NHibernate uses hashcodes to store foreign key names in the database. The problem with hashcodes however is that they change over time, clr-version and appdomain. You can't rely on hashcodes for equality. (ref: http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx) This is why NHibernate can't always remove the foreignkeys, hence cannot drop the tables.
This is a snapshot from the NHibernate sourcecode which is used to create unique foreign key names:
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"));
}
So this problem isn't going to be solved by NHibernate, you have to do it yourself. I solved the problem by executing the following method before the schema is created. The method removes all foreign keys only from the tables that are mapped with 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();
}
}
Works perfect for me. I hope someone else will be able to use it as well.
This is where I grabbed the sql script to drop all foreign keys: http://mafudge.mysite.syr.edu/2010/05/07/dropping-all-the-foreign-keys-in-your-sql-server-database/
Found a way around this problem. I just broke out the use of SchemaExport into two calls. The first to drop an existing schema, the second to re-create it.
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);
Using the above code in the [TestFixtureSetUp] of my test class works well. I now have a clean database schema to use for integration tests.
I too received this error message; however, it was because I had removed a class that had a many-to-one relationship with another class. As such, there was nothing telling NHibernate to drop the [now orphaned, but still referenced] table on the next test run.
I simply dropped it by hand and all was well.
I had this same problem, I found that the Execute method only seems to work properly to do the full drop and recreate if you call it like so:
new SchemaExport(cfg).Execute(true, true, false, false)
I am not sure why the script parameter needs to be true, but setting it to true also solves the problem.