I'm attempting to use NHibernate to serialize a moderately complex object graph*
Actual mapping was done via FNH, but I've dumped the HBM files and confirmed that the generated XML conforms to NHibernate conventions.
Here's a snippet of the HBM, just for grins:
<class xmlns="urn:nhibernate-mapping-2.2" schema="obsv" optimistic-lock="version" name="Spc.Ofp.Tubs.DAL.Entities.PurseSeineActivity, TubsDAL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="s_daylog">
<id name="Id" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="s_daylog_id" not-null="true" />
<generator class="identity" />
</id>
This mapping results in the following SQL (via SQL debug, snipped for readability):
INSERT INTO obsv.s_daylog (/* columns 0 thru 20 snipped */s_daylog_id /* <-- PK from mapping! */)
VALUES (/* parameters snipped */#p21);
select SCOPE_IDENTITY();#p21 = NULL [Type: Int32 (0)]
I believe that the presence of the "select SCOPE_IDENTITY();" text confirms that
NHibernate partially understands what should happen. I just don't understand why it's writing the PK column into the insert query.
I've been using the mappings for reading the graph just fine, so I'm fairly certain this isn't a basic mapping issue.
FWIW, Cascade is set to None (for other reasons, I need to work with these entities without
ramifications up and down the object graph).
*By moderately complex, I mean I have a object which has between 6 and 10 properties which are lists of child entities. A good number of those child entities also have child entities. In the most complex case, there are 5 generations of entities under the root entity.
I think it is because your mapping seems incorrect.
According to the NHibernate Reference, the id tag has a "column" attribute to set the column name, not a child element.
Try using dynamic-insert="true":
<class xmlns="urn:nhibernate-mapping-2.2" dynamic-insert="true" schema="obsv" optimistic-lock="version" name="Spc.Ofp.Tubs.DAL.Entities.PurseSeineActivity, TubsDAL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="s_daylog">
Related
I'm struggling with a NHibernate related problem where I could use some input.
Introduction:
I have a legacy database where the relational concepts have not really been applied.
In the database I have an OrderLine table which contains data for an order lines.
On top of that the table also contains all columns with Order specific information. This could for example be order number of a customer.
E.x. If i have 10 order lines - then I have 10 rows in my OrderLines table and each row has all the Order specific data e.g. order number or customer information.
I did not want to have the above structure in my code so a view was created for Orders so that I could map my Order in NHibernate which then has a set/bag of OrderLines which makes much more sense.
Mapping: (simplified)
<class name="Order" table="[view_Orders]">
<bag name="OrderLines">
</class>
<class name="OrderLine" table="OrderLines" />
The problem:
The complexity of the view makes it impossible to save to the view. When trying NHibernates throws this exception:
NHibernate.Exceptions.GenericADOException: could not insert: XXX ---> System.Data.SqlClient.SqlException: View or function 'view_Orders' is not updatable because the modification affects multiple base tables.
My NHibernate mapping is constructed as an Order object which has a "set or bag" of OrderLine objects. Ideally I would like NHibernate only to persist the set of OrderLine objects instead of the whole object.
Is there a way of achieving this? I have tried locking the object using different lock modes but it did not help me.
You can use mutable="false" to avoid the update and deletes as this article says:
Immutable classes, mutable="false", may not be updated or deleted by the application. This allows NHibernate to make some minor performance optimizations.
To avoid the insert you can use the following statement (Uses the proyection instead an insert command, dont forget use check="none"):
<sql-insert check="none">SELECT 1</sql-insert>
Here is a tested example:
<class name="Order" table="[view_Orders]" mutable="false">
<id name="OrderId" type="System.Guid">
<generator class="guid.comb"/> <!-- Change as you need -->
</id>
<!-- Other properties -->
<!-- <property name="GrandTotal"/> -->
<set name="OrderLines" lazy="true" inverse="true" cascade="all-delete-orphan">
<key column="OrderId"/>
<one-to-many class="OrderLine"/>
</set>
<sql-insert check="none">SELECT 1</sql-insert>
</class>
<class name="OrderLine" table="OrderLine">
<id name="OrderLineId" type="System.Guid">
<generator class="guid.comb"/> <!-- Change as you need -->
</id>
<!-- Other properties -->
<!-- <property name="OrderId"/>
<property name="GrandTotal"/>/> -->
</class>
In case I do understand your issue, the solution is surprisingly simple. We just would mark root object with dynamic-update="true"
<class name="Order" table="[view_Orders]" dynamic-update="true">
...
</class>
And then apply update="false" to every property or reference which we have in that Order class mapped to view:
...
<property name="Code" update="false"/>
...
<many-to-one name="Country" update="false />
But our collection will need the standard, even cascade mapping:
<class name="Order" table="[view_Orders]" dynamic-update="true">
<bag name="OrderLines"
lazy="true"
inverse="true"
batch-size="25"
cascade="all-delete-orphan" >
...
</bag>
... // other stuff is update="false"
</class>
And now code like this would do management of OrderLines, while not executing any updates on the root object Order
var session = ... // get ISession
// load root
var root = session.Get<Order>(123);
// if needed change existing line (pretend there is one)
root.OrderLines[0].Amount = 100;
// add new
var newOrder = ... // new order
root.OrderLines.Add(newOrder);
session.Save(root);
session.Flush();
And that is it. Cascade on the root object is doing what we need, while the update="false" is not updating it...
NOTE: Just interesting note - there is also class and collection
setting mutable="false", but it would not work here... as the
solution mentioned above (it is sad, because that would be more
elegant, but not working as expected...). See:
19.2.2. Strategy: read only
If your application needs to read but never modify instances of a persistent class, a read-only cache may be used. This is the simplest and best performing strategy. Its even perfectly safe for use in a cluster.
<class name="Eg.Immutable" mutable="false">
I have this Instrument entity:
<class name="Instrument" table="Instruments" mutable="false">
<id name="ID" column="INSTRUMENT_ID" unsaved-value="0">
<generator class="assigned" />
</id>
<property name="....." />
<property name="....." />
</class>
This entity is used in many-to-one relationship in other entity (InstrumentSelection). This is many-to-one mapping info:
<many-to-one name="Instrument" access="field.camelcase" column="Instrument_ID" update="false" class="Instrument" not-null="true" fetch="join" lazy="false" />
The issue I've it that when I save InstrumentSelection entity with Save:
Transact(() => session.Save(entity));
I get error in logs:
2012-12-20 14:09:54,607 WARN 12 NHibernate.Engine.ForeignKeys - Unable
to determine if Instrument with assigned
identifier 11457 is transient or detached; querying the database. Use
explicit Save() or Update() in session to prevent this.
A few facts about Instrument entity:
It's just a reference entity
It's immutable entity
It can not be added / inserted via application. I get rows in database from external feed.
Question (version 1): A my question is: is there a way to instruct NHibernate to always consider Instrument entity as detached? I mean - if an instance of Instrument exists in application it means that it's present in database. So there is no too much sense in quering the database.
EDIT 1: Because Question (version 1) was not answered yet, let me change it slightly:
Question (version 2): What could be the behaviour that NHibernate is still trying to work out whether entity is detached/transient? I think I have my mapping configured correctly (unsaved-value, generator).
The problem is that when you save the InstrumentSelection, NHibernate is cascading the operation to save the child Instruments. My first suggestion is to set cascade to none on the InstrumentSelect side of the relationship.
My second suggestion is to use an interceptor as shown in this answer.
My mapping (edited and dumbed down to protect the not-so-innocent):
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyAssembly, Culture=neutral"
namespace="MyAssembly" auto-import="false">
<class name="Customer" table="Customers">
<id name="Id" access="nosetter.camelcase-underscore" >
<generator class="assigned"/>
</id>
<property name="blah"/>
...
<property name="bleh"/>
<join table="Addresses" optional="true">
<key column="Id"/>
<component name="Address" class="MyAssembly.Address, MyAssembly, Culture=neutral" access="field.camelcase-underscore">
<property name="Street" />
<property name="Number" />
</component>
</join>
</class>
</hibernate-mapping>
When I do this:
Address dir = new Address();
dir.Street = "Foo";
dir.Number = 27;
//// Previously loaded customer
cli.Address = dir;
//// Save repository, commit transaction
That works fine, and automatically inserts the new address to the Address table.
BUT, if I want to delete the address:
//// Previously loaded customer with attached address
cli.Address = null;
//// Save repository, commit transaction
That, instead of deleting the row from the Addresses table NHibernate updates it, setting all its fields to null except Id.
What's wrong with my mapping?
Given your mapping, NHibernate performs correctly. The join-clause is for when a single object is to be spread over multiple tables. Typically there will always be one row in each table for the object.
Component mapping on the other hand is when you want a fine-grained object model, but store the data in the same table as the owning class. If the property is null, NHibernate can do nothing other than set all the columns used by the component to null. The fact that the component columns are the only non-key columns in the joined table is irrelevant.
Perhaps you are looking for one-to-one mapping instead?
http://nhibernate.info/doc/nh/en/index.html#mapping-declaration-onetoone
I'm trying to bulk-update entities using a StatelessSession.
Because it's stateless, NHibernate doesn't auto-cascade child entities upon save.
This is fine because I don't want to make any changes to any of the child entities.
Unfortunately, upon save, NHibernate complains:
"object references an unsaved transient instance - save the transient instance before flushing. Type: MyAssembly.MyRandomEntity, Entity: Castle.Proxies.MyRandomEntityProxy"
Of course if I try and update the child entity, I get the error:
"No persister for: Castle.Proxies.MyRandomEntityProxy"
As you can see, the child entity is a proxy because it hasn't been loaded. I don't need it, I don't want to update it... but even if I did I'm not sure how I could.
Any idea how to solve this problem, basically telling it to ignore the transient child entities?
Update
Here is the mapping for the child entity on the parent object:
<many-to-one class="MyAssembly.Flight, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="OutboundFlight">
<column name="OutboundFlightId" />
</many-to-one>
Here is the Id column on the child entity:
<id name="Id" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" unsaved-value="0">
<column name="FlightId" />
<generator class="assigned" />
</id>
its using the assigned generator which uses 'unsavedvalue' to know if the instance is peristent or transient. Maybe there really is an Flightobject with id = 0 in the database? Then it would be created as a proxy with Id = 0 which would be treated as transient instance.
I have what appears to be a simple mapping problem in NHibernate, however I have been struggling to find a solution to the problem for a number of days now, and would appreciate some assistance. I am using VB.NET under VS2005. My VS2005 solution structure is as follows:
Solution: PsalertsIP
Project (Assembly): Core
Folder Data (Namespace PsalertsIp.Core.Data)
Contains Interfaces for communication with repository classes
example: PsalertsEventRepo Implements IPsalertsEventRepo
Folder Domain (Namespace PsalertsIP.Core.Domain)
Contains all POCO domain objects and related interfaces
example: PsalertsEvent Implements IPsalertsEvent
Also underneath the assembly 'Core' are the NHibernate config file and the mapping file for the PsalertsEvent class, which is as follows:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Core"
namespace="Core.Domain">
<class name="PsalertsEvent" table="Source_table" lazy="true">
<id name="Id" column="Id" type="long" unsaved-value="0"
access="field.camelcase-underscore">
<generator class="native" >
<param name="sequence">My_Oracle_Sequence</param>
</generator>
</id>
<property name="Substation" column="Field1" />
<property name="BusbarId" column="Field2" />
<property name="PlantId" column="Field3" />
<property name="AlarmName" column="Field4" />
<property name="AlarmStatus" column="Field5" />
<property name="EventTime" column="Field6" />
</class>
</hibernate-mapping>
When I attempt to carry out a simple test of the NHibernate environment through NUnit (appreciate that this isn't unit testing, however needed a simple vehicle to test the NHibernate setup), the test fails, and I observe the following output in NUnit:
PsalertsIp.Tests.Data.PSALERTSEventRepoTests (TestFixtureSetUp):
System.TypeInitializationException : The type initializer for 'Nested' threw an exception.
----> NHibernate.MappingException : Could not compile the mapping document: PsalertsEvent.hbm.xml
----> NHibernate.MappingException : persistent class Core.Domain.PsalertsEvent, Core not found
----> System.TypeLoadException : Could not load type 'Core.Domain.PsalertsEvent' from assembly 'Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
I suspect that the problem may be to do with the structure of the solution in VS2005, however I have tested multiple different assembly/namespace permutations to no avail.
I think you need to change the namespace attribute on the hibernate-mapping element to "PsalertsIP.Core.Domain" (as you've specified above).
Also ensure the assembly attribute on the hibernate-mapping element specifies the full assembly name of your project (right-click project -> Properties -> Application tab).
hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Core"
namespace="PsalertsIP.Core.Domain">