Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to persist a subset of an object instead of the whole object?

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.

like image 832
JuniorCoder Avatar asked Aug 11 '15 06:08

JuniorCoder


People also ask

Can you loop an object?

Object. It takes the object that you want to loop over as an argument and returns an array containing all properties names (or keys). After which you can use any of the array looping methods, such as forEach(), to iterate through the array and retrieve the value of each property.

Can sets store objects?

Sets are data objects that hold a collection of unique values. Sets can hold data of any type, but may not contain duplicate items. In the case of adding duplicate and equal items, only the first instance will be saved to the set.

Which is the correct way to remove property from an object?

The semantically correct way to remove a property from an object is to use the delete keyword.


2 Answers

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>
like image 146
Najera Avatar answered Nov 14 '22 15:11

Najera


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">

like image 1
Radim Köhler Avatar answered Nov 14 '22 15:11

Radim Köhler