nhibernate alternate id's using generated properties - nhibernate

** 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)

Related

NHibernate 2 FKs to same PK in one-to-one relationship

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;
}
}
}

Mapping issue: correct number of results, however every result is duplicate of first resut

I'm new to NHibernate (using latest version) and I am having an issue mapping an object in our generic dataloading application to a database.
The database is third party so we cant make changes there.
Our object is:
public class GenericObjectValue
{
public string ObjectId { get; set; }
public string ObjectTypeId { get; set; }
public string measurementId { get; set; }
public DateTime timestamp { get; set; }
public Double Value { get; set; }
}
Datasource tables we use:
Table t_data_point
(
id (PK, int, not null)
object_id (FK, Varchar(30), not null)
object_type_id (FK, Varchar(30), not null);
measurement_id (FK, Varchar(30), not null);
)
Table t_data_point_Value
(
data_point_id (PK, FK, int, not null)
timestamp (PK, FK, datetime, not null)
version (PK, FK, int, not null)
value (numeric(18,6), not null);
)
The mapping i have configured is:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Phoenix.Model" assembly="Phoenix.Common">
<class name="MeasValue" table="t_data_point_Value">
<id column="data_point_id" type="int" />
<property name="Timestamp" column="timestamp "/>
<property name="Value" column="value"/>
<join table="t_data_point">
<key column="id" />
<property name="measurementId" column="measurement_id" />
<property name="ObjectId" column="object_id" />
<property name="ObjectTypeId" column="object_type" />
</join>
</class>
</hibernate-mapping>
Not sure if i'm doing something stupid or something that just isn't possible but when i run the code for this i get back the correct number of results but the results are just duplications of the first result returned i.e. timestamp and value are the same.
If you need more information please let me know.
I've seen this when your id is mapped incorrectly, ie to a column that is not unique. Nhibernate will reuse the 1st object it finds in cache with the same id.
There is a free mapping tool that could help point you in the right direction. It wont create the join but it will create the basic columns and id http://nmg.codeplex.com.

Querying a many-to-many collection or how to include a many-to-many table in a criteria query?

I am quite new to the world of NHibernate and I can't seem to get this to work with the use of a criteria query: query a many-to-many relationship or query a collection (set/bag) on an entity. I've searched the internet and checked all the NHibernate books we have, but I can't find a specific answer to my "challenge".
I have made a simplified example of the problem I'm trying to solve. I have a table with books, a table with categories and a many-to-many table with the categories per book. Here are some of the technicalities:
data structure:
create table tableBook
(
BkId integer not null default autoincrement,
BkTitle char(40) not null,
BkWriter char(40) not null,
primary key (BkId)
);
create table tableCategory
(
CatId integer not null default autoincrement,
CatCode char(3) not null,
CatDesc char(40),
primary key (CatId)
);
create table tableCategoriesPerBook
(
CpbId integer not null default autoincrement,
CpbBkId integer not null, /*foreign key to tableBook*/
CpbCatId integer not null, /*foreign key to tableCategory*/
primary key (CpbId)
);
alter table tableCategoriesPerBook add foreign key FK_CpbBkId (CpbBkId) references tableBook (BkId) on update Restrict on delete Cascade;
alter table tableCategoriesPerBook add foreign key FK_CpbCatId (CpbCatId) references tableCategory (CatId) on update Restrict on delete Cascade;
create unique index idx_CpbCatId_CpbBkId on tableCategoriesPerBook (CpbCatId, CpbBkId);
C# classes:
public class BookEntity
{
public virtual Int32 BookId { get; set; }
public virtual string BookTitle { get; set; }
public virtual string BookWriter { get; set; }
private readonly IEnumerable<CategoryEntity> _categories = new ObservableCollection<CategoryEntity>();
public virtual IEnumerable<CategoryEntity> Categories
{
get { return _categories; }
}
}
public class CategoryEntity
{
public virtual Int32 CategoryId { get; set; }
public virtual string CategoryCode { get; set; }
public virtual string CategoryDesc { get; set; }
}
NHibernate mappings:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping namespace="Domain" assembly="Domain" xmlns="urn:nhibernate-mapping-2.2">
<class name="Domain.BookEntity" table="tableBook">
<id name="BookId" column="BkId" type="Int32">
<generator class="native" />
</id>
<property name="BookTitle" column="BkTitle" type="string" length="40"/>
<property name="BookWriter" column="BkWriter" type="string" length="40"/>
<idbag name="_categories" access="field" table="tableCategoriesPerBook">
<collection-id type="Int32" column="CpbId">
<generator class="native"/>
</collection-id>
<key column="CpbBkId" property-ref="BkId"/>
<many-to-many column="CpbCatId" class="Domain.CategoryEntity, Domain" />
</idbag>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping namespace="Domain" assembly="Domain" xmlns="urn:nhibernate-mapping-2.2">
<class name="Domain.CategoryEntity" table="tableCategory">
<id name="CategoryId" column="CatId" type="Int32">
<generator class="native" />
</id>
<property name="CategoryCode" column="CatCode" type="string" length="3" />
<property name="CategoryDesc" column="CatDesc" type="string" length="40" />
</class>
</hibernate-mapping>
My question: is it possible to query (using ICriteria and/or detached criterias) the database in such a way that I get the books which is in one of the categories I specify (for instance: in catA or catB, could be "and" as well)? I want to optimize this in the query, not in C# (as I need to read all books from the database before I can filter the objects based on their collection of tags). If I'd write the SQL by hand, I would produce something like this:
SELECT * FROM tableBook
WHERE EXISTS
(
SELECT 1
FROM tableCategoriesPerBook
INNER JOIN tableCategory on (CpbCatId = CatId and CpbBkId = BkId)
WHERE CatCode in ('001', '002')
)
Since I don't have an entity for tableCategoriesPerBook, I don't see a way to get to this table with a criteria query. And I'd rather not add some handwritten piece of SQL expressions using:
criteria.Add(Expression.Sql("exists(.....)");
One last important factor: I am using a brownfield database, so I can't change the structure! this is what I'll have to work with database-wise.
This is pretty straight forward. You can use a detached criteria.
DetachedCriteria bookCategoryCriteria = DetachedCriteria.For<BookEntity>("bookCat");
bookCategoryCriteria
.CreateAlias("Categories", "cat", JointType.LeftOuterJoin)
.Add(Restrictions.In("cat.CategoryCode", categories)
.Add(Restrictions.Eq("bookCat.BookId", "book.BookId")
.SetProjection(Projections.Id());
Session.CreateCriteria<BookEntity>("book")
.Add(Subqueries.Exists(bookCategoryCriteria));

NHibernate won't delete orphaned object

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

NHibernate SchemaExport failing to drop a table .... sometimes

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.