NHibernate: sometimes I want one item, other times I want them all - nhibernate

I have a database schema that stores one "Page" with many "Revisions". Like a simple wiki.
90% of the time when I load a page, I am just interested in the latest revision. However, sometimes I want all revisions.
With NHibernate I can map the Page to the Revisions, and tell it to lazy-load. However, when I access the latest revision, it will load all other revisions - a big waste of I/O.
My page class currently resembles:
public class Page
{
public Page()
{
Revisions = new HashedSet<Revision>();
}
public virtual ISet<Revision> Revisions { get; private set; }
public virtual Revision LatestRevision
{
get { return Revisions.OrderByDescending(x => x.Revised).FirstOrDefault(); }
}
public virtual Revision Revise()
{
var revision = new Revision();
// ...
revision.Entry = this;
revision.Revised = DateTime.UtcNow;
Revisions.Add(revision);
return revision;
}
}
How would I model this such that the LatestRevision is automatically loaded when the Page is loaded, but the other revisions are lazy-loaded if, for instance, I attempted to iterate them?
I would particularly like a solution that works with LINQ to NHibernate, but using ICriteria (or even SQL if I have to) is good enough.

I'm tackling a similar problem as well.
What about mapping exactly as you have it there. The LatestRevision property could be mapped as a one-to-one mapping to the revisions table and the revisions would be as you've already got it. You would have to have a setter (probably make it private) and manage the relationship in the revise method.
One problem would be that the the LatestRevision would still be in the set of revisions.
I've also come across a post by Ayende which uses the formula attribute for the property, I've never used it but looks like it might fit the bill.

You could use a derived property in your mapping file (rather than performing the logic in the property). It might look something like this:
<property name="LatestRevision"
forumla="select top r.f1, r.f2, r.etc from Revisions order by revised desc"
type="Revision" />
For more info on this approach search for 'nhibernate derived properties'.
https://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html_single/

Add a LatestRevision column (maintain it) and map to that.
It will save you a lot of headaches.

I ended up going with the solution from here:
Partially Populate Child Collection with NHibernate
My page now has these properties:
public virtual Revision CurrentRevision
{
get
{
return _revision ?? Revisions.OrderByDescending(x => x.Revised).FirstOrDefault();
}
set { _revision = value; }
}
public virtual ISet<Revision> Revisions { get; private set; }
The loading code is:
public Page GetPage(string name)
{
var entryHash = (Hashtable)_session.CreateCriteria<Page>("page")
.Add(Restrictions.Eq("page.Name", name))
.CreateCriteria("Revisions", "rev")
.AddOrder(Order.Desc("rev.Revised"))
.SetMaxResults(1)
.SetResultTransformer(Transformers.AliasToEntityMap)
.UniqueResult();
var page = (Page)entryHash["page"];
page.LatestRevision = (Revision)entryHash["rev"];
return page;
}
NHProf shows this as the only query being executed now, which is perfect:
SELECT top 1 this_.Id as Id3_1_,
this_.Name as Name3_1_,
this_.Title as Title3_1_,
rev1_.Id as Id0_0_,
rev1_.Body as Body0_0_,
rev1_.Revised as Revised0_0_,
....
FROM [Page] this_
inner join [Revision] rev1_
on this_.Id = rev1_.PageId
WHERE this_.Name = 'foo' /* #p0 */
ORDER BY rev1_.Revised desc

What the problem to have LatestRevision property and corresponding column in Page table?
public class Page
{
public Page()
{
Revisions = new HashedSet<Revision>();
}
public virtual ISet<Revision> Revisions { get; private set; } // lazy="true"
public virtual Revision LatestRevision { get; private set; } // lazy="false"
public virtual Revision Revise()
{
var revision = new Revision();
// ...
revision.Entry = this;
revision.Revised = DateTime.UtcNow;
Revisions.Add(revision);
LatestRevision = revision; // <- there you have latest revision
return revision;
}
}

Related

NHiberate session.Get() gets the cached entity. How to get the latest entity from the DB?

I'm using the CQRS pattern to get the data from the DB using NHibernate.
Here is the CommittingTransactionCommandHandler which calls the UpdateProductHandler
class CommittingTransactionCommandHandler
{
private readonly ISession session;
private readonly ICommandHandler<TCommand> _inner; //Generic Command Handlers
public CommittingTransactionCommandHandler(ISession session)
{
this.session = session;
}
public async Task Execute(TCommand command)
{
using (var txn = session.BeginTransaction(IsolationLevel.Unspecified))
{
try
{
await _inner.Update(command); // This calls the UpdateProducthandler's Update method
txn.Commit();
}
catch (Exception ex)
{
throw;
}
}
}
}
Here is the Command Handler for the Update.
class UpdateProductHandler : ICommand
{
private readonly ISession session;
public UpdateProductHandler(ISession session)
{
this.session = session;
}
public async Task Update(int id, ProductIdentity productIdentity)
{
var product = session.Get(id);
product.Update(productIdentity);
}
}
Here is the Query Handler for the Get
class GetProductHandler
{
private readonly ISession session;
public GetProductHandler(ISession session)
{
this.session = session;
}
public async Task<Product> Get(int id)
{
var product = session.Get(id);
if (product == null)
throw new Exception("Entity not found");
return Task.FromResult(product);
}
}
Here is the code for the Product entity
class Product
{
public virtual int Id { get; protected set; }
public virtual string Name { get; protected set; }
public virtual string Description { get; protected set; }
public virtual int? Version { get; protected set; }
public virtual void Update(ProductIdentity productIdentity)
{
Name = productIdentity.Name;
Description = productIdentity.Description;
}
}
The flow is
CommittingTransactionCommandHandler is a generic command handler. This is called from the API, which internally invokes the UpdateProductHandler. The transaction is opened in this and committed here.
The scenario is that
I get a Product from the DB using the GetProductHandler. (In this case, the version number of the Product is 10.)
I'm updating the Product using the UpdateProductHandler and commits the session which is under the transaction. (Here version number of the Product is 11)
Immediately after the Update Product, I query the same Product using the GetProductHandler and loads it in the Edit mode in the UI. (But the Product fetched using the GetProductHandler has a version number 10 and not 11.)
Here is the issue, instead of getting the latest update from the DB, the above GetProductHandler, gets the previous state of the object.(Found using the version number)
Now, if I try to update the Product, I get a Stale Object State Exception since the version number is 10 which is not the latest version of the Product.
I've tried with session.Refresh(product) but all in vain as it affects the other transactions.
How can I resolve this?
As Ayende explains, Get will NOT always hit the database. It will only hit the database if entity not found in first level cache.
You have not explained how you are updating the entity; what is inside product.Update method. If you are updating using API that does not update underlying first level cache (SQL/HQL/Linq), then the problem you stated in question is obvious. In that case,
You load entity in session cache using Get (Version 10). This hits the database.
Then, you call Update (database hit) which will not update the first level cache (Version changed to 11 in database; NOT in cache).
Then you call Get again. NHibernate looks if entity is already loaded in session cache. Yes; it is (Version 10). It just returns it. No database hit.
Solution 1:
Not sure why that Update method is needed. You can directly update the entity like below:
public async Task Update(int id, ProductIdentity productIdentity)
{
var product = session.Get(id);
product.Version = 11;
//Update other properties here
...
...
//That's it. You are already calling `Flush` somewhere somehow
}
Solution 2:
As Ayende NOT recommended (and I am not recommending either), don't use Get and write a query that will hit the database. Use Linq/HQL/SQL API instead.
select customer from s.Linq<Customer>()
where customer.Id = customerId
select customer
).FirstOrDefault();
Every time that I see something like that, I wince a little inside. The reason for that is quite simple. This is doing a query by primary key. The key word here is a query.
This means that we have to hit the database in order to get a result for this query. Unless you are using the query cache (which by default you won’t), this force a query on the database, bypassing both the first level identity map and the second level cache.
Get and Load are here for a reason, they provide a way to get an entity by primary key. That is important for several aspects, most importantly, it means that NHibernate can apply quite a few optimizations for this process.

I cannot turn off lazy loading in NHibernate

I'm aware that lazy-loading 'on' is the default setting in NHibernate. I switched lazy-loading off, using mapping-by-code, for the entity (Student) and the collection (Comments) contained within an entity. However a test including the use of SQL-Profiler shows that it does not load the collection from the database when the entity is accessed via a Session.Get(). I see only a 'Select' to get the entity (Student) from the Db. No 'Join' or 'Selects' to the collection table (Comments). Am I missing something? I'm using NH version 5.
Mapping:
using NHibernate.Mapping.ByCode.Conformist;
using NHibernate.Mapping.ByCode;
namespace Infrastructure.Repository.NH.Tests
{
public class StudentSubclassMapping: JoinedSubclassMapping<Student>
{
public StudentSubclassMapping()
{
Lazy(false);
Property(student => student.EnrollmentDate);
List(student => student.Comments,
listMapper =>
{ listMapper.Lazy(CollectionLazy.NoLazy);},
relationMapper =>
relationMapper.Element());
}
}
}
Domain:
public class Student : Contact
{
public virtual DateTime? EnrollmentDate { get; set; }
public virtual IList<string> Comments { get; set; }
}
Test:
public void Get_TestToCheckIfLazyLoadingIsTurnedOff()
{
using (var session = SessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var student = session.Get<Student>(2);
transaction.Commit();
}
}
}
Just tested this with NHibernate 5.0.3 and it seems to be working properly:
NHibernate: SELECT student0_.student_key as id1_0_0_, student0_.EnrollmentDate as enrollmentdate2_1_0_ FROM Student student0_ inner join Contact student0_1_ on student0_.student_key=student0_1_.Id WHERE student0_.student_key=#p0;#p0 = 1 [Type: Int32 (0:0:0)]
NHibernate: SELECT comments0_.student_key as student1_2_0_, comments0_.id as id2_2_0_, comments0_.idx as idx3_0_ FROM Comments comments0_ WHERE comments0_.student_key=#p0;#p0 = 1 [Type: Int32 (0:0:0)]
The listMapper.Lazy(CollectionLazy.NoLazy) that you already have there should do the trick.
I think that maybe you don't really have a Student with ID 2 in the database?
If that's the case, you will only see NHibernate issuing the first query (over Contact/Student) and it will not issue a query over Comments as the Student does not exist.
If you have a Student with ID 2 you should see the query to the Comments table right after the initial one.
If you like, you can try adding listMapper.Fetch(CollectionFetchMode.Join) to bring both the Student and Comments in the same query, although I wouldn't generally recommend this.

NHibernate JoinAlias on collection multiple times

I'm using NHibernate 3.33 and QueryOver with Postgre 9.2.
I've got two entities:
public class User {
public virtual string Name { get; set; }
public virtual IList<Reports> Reports { get; set; }
}
and
public class Report {
public virtual string Type { get; set; }
public virtual DateTime ReportDate { get; set; }
public virtual User Author { get; set; }
}
with association - one-to-many (I didn't append additional fields to entities like Id or Name to snippets above). Some report's types are avaliable - month, day.
My goal is to get summary for user - find out whether user has day-report and month-report for current day.
Note: month-report's ReportDate looks like first day of month. Also I want to get it as one row (if it was an SQL) to transform to dto:
public class UserSummaryDto {
public bool HasDayReport { get; set; }
public bool HasMonthReport { get; set; }
}
To achieve my goal I've tried following:
Report dayReport = null;
Report monthReport = null;
var currentDay; // some value of current day
var firstDay; // some value of first day of month
var report = session.QueryOver<User>
.Left.JoinAlias(u => u.Reports, () => dayReport, r => r.ReportDate == currentDay)
.Left.JoinAlias(u => u.Reports, () => monthReport, r => r.ReportDate == firstDat)
.SelectList(
// some logic to check whether user has reports
.TransformUsing(Transformers.AliasToBean<UserSummaryDto>())
.List<UserSummaryDto>()
And I've got error:
'duplicate association path:Reports'.
Is it possible to avoid this problem or it's a limitation of HNibernate?
To answer your question:
...Is it possible to avoid this problem or it's a limitation of HNibernate?
Have to say NO.
For more information see similar Q & A: Rename NHibernate criteria
We are not querying the DB, not using SQL (which does allow to do a lot). Here we work with "mapped" domain model, and that could bring some limitations - as the one discussed here...
If that could help, the workaround is to map such property twice and use the WHERE clause: 6.2. Mapping a Collection
where="" (optional) specify an arbitrary SQL WHERE condition to be used when retrieving or removing the collection (useful if the collection should contain only a subset of the available data)

RavenDB static index on document with dynamic field

I am trying to create a static index for the following sample class:
public class Board {
...other assorted fields
List<dynamic> Messages {get; set;}
internal Board() {Messages = new List<dynamic>();}
}
The index is to filter boards which have messages which are a older than a certain date. The aim is to perform an "update" operation on messages which are due today, update their content, and persist them back. The index is needed to avoid traversing all the messages for a board for all clients as that may be computationally expensive. Messages is a list of message types which inherit from a base class which contains a property ExpiryDate.
Trying to create an index like follows results in an "An expression tree may not contain a
dynamic operation" error. I know that the dynamic type does not play well with Linq queries hence the need to use LuceneQueries instead of Query() in RavenDB. Is there any way to make this index work with dynamic properties? Thanks!
public class ScanBoardMessagesIndex : AbstractIndexCreationTask<Board>
{
public ScanBoardMessagesIndex () {
Map = boards => from board in boards
where board.Messages.Any(msg => ((MessageItem) msg).ExpiryDate <= DateTime.UtcNow.Date)
select board;
}
}
EDIT:
I ran into a raven serialization issue because the metadata clr-type of existing Board documents was set to a class namespace which was not valid anymore. I am doing a migration project so I went ahead and first issued a patch to change the metadata clr-type of the existing documents before migrating them to the new data structure which uses a base/abstract class for list of Messages instead of type dynamic.
A Map/Reduce index seems more appropriate for the given requirements. Effectively, you want to be able to query boards by the oldest expiry date of messages in the board. This is an aggregating operation, exactly what Map/Reduce was designed to solve. Also, using a base class for messages will allow you to define the index without resorting to the lower level IndexDefinition:
public class Message
{
public DateTime ExpiryDate { get; set; }
}
public class Board
{
public string Id { get; set; }
public List<Message> Messages { get; set; }
}
public class OldestExpiryDateMessageInBoard : AbstractIndexCreationTask<Board, OldestExpiryDateMessageInBoard.Result>
{
class Result
{
public string BoardId { get; set; }
public DateTime OldestExpiryDate { get; set; }
}
public OldestExpiryDateMessageInBoard()
{
this.Map = boards => from board in boards
from message in board.Messages
select new
{
BoardId = board.Id,
OldestExpiryDate = message.ExpiryDate
};
this.Reduce = results => from result in results
group result by result.BoardId into g
select new
{
BoardId = g.Key,
OldestExpiryDate = g.Min(x => x.OldestExpiryDate)
};
}
}
You can then query this index with Lucene syntax.

Supersedes clause in database structure

Imagine a database table that looks like this:
create table [dbo].[user]
(
id int IDENTITY(1,1),
username varchar(50) NOT NULL,
firstname varchar(20) NOT NULL,
lastname varchar(30) NOT NULL,
currentid int NULL,
processedby varchar(50) NOT NULL,
processeddate varchar(50) NOT NULL
processedaction varchar(50) NOT NULL
)
What I want to do is to setup NHibernate to load it into my user object, but I only want the current version of the object "user" to be brought back. I know how to do a SQL select to do this on my own, and I feel as if there's something in nHibernate with the usage of triggers and event listeners, but can anyone tell me how to implement the nHibernate repository so I can:
{Repository}.GetCurrent(id) <- pass it any of the ids that are assigned to any of the historical or the current record, and get back the current object.
{Repository}.Save(user) <- I want to always insert the changes to a new row, and then update the old versions to link back to the new id.
Edit
So, there's some confusion here, and maybe I explained it wrong... What I'm trying to do is this, in regards to always getting the current record back...
Select uc.*
FROM User uo
JOIN User uc on uo.currentid=uc.id
WHERE uo.id==:id
But, I don't want to expose "CurrentID" to my object model, since it has no bearing on the rest of the system, IMHO. In the above SQL statement, uo is considered the "original" object set, and uc is considered the current object in the system.
Edit #2:
Looking at this as a possible solution.
http://ayende.com/blog/4196/append-only-models-with-nhibernate
I'm honestly being pigheaded, as I'm thinking about this backward. In this way of running a database, the autoincrementing field should be the version field, and the "id" field should be whatever the autoincrementer's value has at the time of the initial insert.
Answer:
I don't want to take #Firo's fury, and I'm not going to remove it from him, as he took me down the right path... what I wound up with was:
Created a base generic class with two types given
a. type of the object's "ID"
b. type of the object itself.
instantiate all classes.
create a generic interface IRepository class with a type of the object to store/retrieve.
create an abstract generic class with a type of the object to store/retrieve.
create a concrete implementation class for each type to store/retrieve.
inside of the create/update, the procedure looks like:
Type Commit(Type item)
{
var clone = item.DeepClone();
_Session.Evict(item);
clone.Id = 0;
clone.ProcessedDate = DateTime.Now;
if (clone.Action.HasValue)
{
if (clone.Action == ProcessedAction.Create)
clone.Action = ProcessedAction.Update;
}
else
{
clone.Action = ProcessedAction.Create;
}
clone.ProcessedBy = UserRepos.Where(u => u.Username == System.Threading.Thread.CurrentPrincipal.Identity.Name).First().Current;
var savedItem = (_Session.Merge(clone) as Type);
_Session.CreateQuery("UPDATE Type SET CurrentID = :newID where ID=:newID OR CurrentID=:oldID")
.SetParameter("newID", savedItem.Id)
.SetParameter("oldID", item.Id)
.ExecuteUpdate();
return savedItem;
}
In the delete method, we simply update the {object}.Action = ProcessedAction.Delete
I wanted to do this another way, but realizing we need to eventually do historical comparisons, we weren't able to ask nHibernate to filter the deleted objects, as the users will want to see that. We'll create a business facade to take care of the deleted records.
Again, much thanks to #Firo for his help with this.
So, with all that, I can finally do this:
var result = {Repository}.Where(obj => obj.Id == {objectID from caller}).FirstOrDefault();
if (result != null)
{
return result.Current;
}
else
{
return null;
}
and always get my current object back for any requesting ID. Hope it helps someone that is in my situation.
in mapping if you use FluentNHibernate
public UserMap : ClassMap<User>
{
public UserMap()
{
Where("id = currentid"); // always bring back the most recent
}
}
// in Userrepository
public void Update(User user)
{
var clone = user.Clone();
session.Evict(user); // to prevent flushing the changes
var newId = session.Save(clone);
session.CreateQuery("UPDATE User u SET u.currentid = :current") // <-- hql
.SetParameter("current", newId)
.ExecuteUpdate();
}
objectgraphs are a lot trickier with this simple code. I would then do one of the following:
use NHibernate.Envers to store auditing information for me
explicitly creating new entities in BL code
i once saw an append-only-model doing something like the following
// UserBase is there to ensure that all others referencing the User doesnt have to update because user properties changed
class UserBase
{
public virtual int Id { get; set; }
public virtual ICollection<PersonDetails> AllDetails { get; private set; }
public virtual PersonDetails CurrentDetails
{
get { return _currentDetauils; }
set { _currentDetauils = value; AllDetails.Add(value); }
}
// same as above
public virtual ICollection<ConfigDetails> AllConfigs { get; set; }
}
class Order
{
public virtual int Id { get; set; }
public virtual UserBase User { get; set; }
public virtual IList<OrderDetail> AllDetails { get; private set; }
public virtual IList<OrderDetail> ActiveDetails { get; private set; }
public virtual void Add(OrderDetail detail)
{
AllDetails.Add(detail);
ActiveDetails.Add(detail);
}
public virtual void Delete(OrderDetail detail)
{
detail.Active = false;
ActiveDetails.Remove(detail);
}
}
class OrderDetail
{
public virtual int Id { get; set; }
public virtual Order Parent { get; set; }
public virtual bool Active { get; set; }
}
class OrderMap : ClassMap<Order>
{
public OrderMap()
{
HasMany(o => o.AllDetails);
HasMany(o => o.ActiveDetails).Where("active=1");
}
}
// somewhere
public void UpdateTaxCharge(OrderDetail detail, TaxCharge charge)
{
var clone = detail.Clone();
clone.TaxCharge = charge;
detail.Order.Delete(detail);
detail.Order.Add(clone);
}
You can tell NHibernate what exactly SQL it should generate when persisting and loading an entity. For example you can tell NHibernate to use a stored procedure instead of a plain SQL statement. If this is an option for you I can farther elaborate my answer.