I have a class diagram looking like the picture above. I use NHibernate to do CRUD commands on the objects. When I write to the permission table (adds a role to the user’s role collection and vice versa), NHibernate starts acting strange.
I have a Method called UpdateRoles() that makes sure that all Roles are up to date. This method fails as NHibernate sometimes generates proxy objects, hence UpdateRoles() now think the Role does not exist and makes a new object (causing my hierarchy to be duplicated).
I found a pattern for when NHibernate loads objects as proxies:
Case 1 works, case 2 doesn’t.
What happens is that in case 1 the user is added to the user collection of each Role in each 3 levels of the hierarchy. This is working as intended.
In case 2, the user is only added to the last level in the hierarchy. Now the parent role (uCommerce) is loaded as a proxy object.
My RoleMap looks like this:
References(x => x.ParentRole).Nullable().Column("ParentRoleId");
HasManyToMany(x => x.Users).AsSet().Table("uCommerce_Permission");
HasMany(x => x.Roles).Inverse().KeyColumn("ParentRoleId").Cascade.AllDeleteOrphan();
My UserMap looks like this:
HasManyToMany(x => x.Roles).AsSet().Table("uCommerce_Permission");
How can I prevent NHibernate from doing this?
I use Fluennt NHibernate.
Specifying Not().LazyLoad() doesn't prevent the problem. Also i read that specifying such is a bad case of programming.
1)
var proxy = parent as INHibernateProxy;
if (proxy != null)
{
// var t = x.GetType(); becomes
var t = proxy.HibernateLazyInitializer.GetImplementation().GetType();
}
or 2)
References(x => x.ParentRole).Nullable().Column("ParentRoleId").Not.LazyLoad();
or 3)
ReferencesAny(x => x.ParentRole)
.Nullable()
.EntityIdentifierColumn("ParentRoleId")
.EntityTypeColumn("parentdiscriminator");
typeof(CreateProductRole).IsAssignableFrom(role.Parent.GetType()).
1) and 2) are basicly the same, the parent is not lazyloaded
3) means that each role knows the type of its parent so that when a proxy is created, it is a proxy for the actual role type (CreateProductRoleProxy) and not RoleProxy
Related
I have 2 objects in my domain A, and B
Object A has a property of B
Object B has a property of list
when I do a hit on my db, of B, it returns a list of As, but each A has a B, which in turn has a list of As. over and over and over.
clearly a lazy loading issue. Lazy loading is on, but my problem is that this is a WCF service, do i need to convert all my domain objects into dto objects to send down the wire and when i do it does the following - pseudocode
ADTO adto Transform(ADomain a)
{
adto.name = a.name;
adto.surname = a.surname;
adto.B = Transform(a.B);
}
BDTO bdto Transform(BDomain b)
{
bdto.bob = b.bob;
foreach (A a in b.As)
{
bdto.bs.add(Transform(a));
}
}
so how can i make my fetch of the collection only go one layer deep.
B's mapping:
HasMany(x => x.As)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn("AId");
A's mapping:
References(x => x.B).Column("AId");
Well, to pass circular reference over WCF you should adjust the Parent DTO (B) with IsReference parameter DataContractAttribute.IsReference Property (or here The Problem of Circular References).
Use the IsReference property to instruct the DataContractSerializer to insert XML constructs that preserve object reference information.
[DataContract(Namespace = "http://domain.../", IsReference=true)] public class BDTO ...
To give you answer:
...so how can i make my fetch of the collection only go one layer deep.
NHibernate won't have any issue with circular references. And even more, you can easily get all the data while executing 2 SQL queries only. Adjust the mapping:
HasMany(x => x.As)
.Cascade.AllDeleteOrphan()
.BatchSize(25)
//.Not.LazyLoad()
.Inverse()
.KeyColumn("AId");
NOTE: Not.LazyLoad make sense only if the A object is almost always needed to get B working. When "lazy" mode is used, you have to keep the session opened during the whole WCF service processing
The BatchSize setting will optimize loading lists of B objects. Read more here: http://ayende.com/blog/3943/nhibernate-mapping-set
NHibernate session will execute two queries 1) Select B and 2) Select A for all B and materialize the results into complete A and B instances, with both-ways references fully populated. The NHibernate session will serve you fully loaded instances. Even calls Get<A>(id) and Get<B>(id) will retrieve the objects from session
Next steps are up to you, you can use DTO objects, mapping tools to convert them...
I've got a table called AdministratorPrivilages that has the following fields:
ID
List item
MemberId
Value
MemberType
Now, the members can be of two types (Enterprise and Express). Enterprise members live in the enterprise table. Express members live in the expressmember table. I've tried to do my fluent mapping like so.
public class AdministratorPrivilegesMapping : ClassMap<AdministratorPrivileges>
{
public AdministratorPrivilegesMapping()
{
Id(x=>x.Id);
Map(x => x.Value).Column("Value");
ReferencesAny(x => x.Member)
.EntityTypeColumn("MemberType")
.EntityIdentifierColumn("MemberId")
.IdentityType<Int32>()
.AddMetaValue<ExpressMember>("Express")
.AddMetaValue<Member>("Enterprise");
}
}
Both member tables have integer ids with ascending values. When I try to pull back the privilages associated with enterprise member 10, I'm getting the permission set associated with Express Member 10. Both other tables are mapped with the old school hbm mapping files.
Am I missing something obvious? I'm using NHibernate 2.1 and FluentNhibernate 1.1
I actually found the solution using .Where().
My situation is a little different, I have objectA and objectB.
objectA contains objectB, but objectB also contains a collection of objectBs.
"objectA":{
"someProp" : "(string)",
"objectB":{
"someProp" : "(string)",
"comeCol" : [
"(objectB)"
]
}
}
So the "Parent" property of objectB can either be of type objectB or objectA, which is why I needed to use ReferencesAny in the mapping of objectB.
Mapping looks like
ReferencesAny( x => x.Parent )
.IdentityType< int >()
.MetaType< string >()
.EntityTypeColumn( "ParentType" )
.EntityIdentifierColumn( "ParentId" )
.AddMetaValue< objectA >( "E" )
.AddMetaValue< objectB >( "S" )
.Access.Property()
.LazyLoad()
.Cascade.All();
All this works well when saving, however, my problem occured when retrieving, because the framework wasn't told what to retrieve and simply retrieved everything.
So now here is the mapping of the collection that fixed the problem:
HasMany( x => x.objectBs )
.KeyColumn( "ParentId" )
.Where( "ParentType = 'S'" )
.Cascade.All()
.LazyLoad();
Hope it will help anyone in the same situation :)
As seems to always be the case when I post on Stackoverflow, I'm being a complete goober and missing something glaringly obvious that cleared itself up with a good night's rest and some caffeine. I needed to look into the mappings for the ExpressMember and Member classes. Turns out the bag declarations there didn't filter by the type of object appropriately. Initially, I thought I had made the breaking change, when in fact it was actually a very old issue. The collision in id numbers between the two different types of members was not an issue for a very long time, as express members typically have either all the permissions or none, as do most of the older members (which were created first under an admin/plebe scheme with privileges being broken out later).
I am building a small web application and using nhibernate as my DAL.
I have the following situation:
Father class - Profile Class:
{
long ID
string Name
[A whole bunch of info]
List<Voting> Votes
}
Son class - Votes Class:
{
long id
short rating
}
This is my mapping:
HasMany(o => o.Votes)
.LazyLoad()
.AsBag()
.Inverse()
.Cascade.All();
I want the following to happen:
Users will be able to rate a profile, and then all I need to do is merely call update on the profile itself, instead of sending a Save call on each Vote.
This i what I am currently doing:
var myProfile = new Profile();
myProfile.Rate(1,2);
myProfile.Rate(5,2);
myProfile.Rate(20,2);
Session.SaveOrUpdate(myProfile);
Sadly enough nothing gets saved. Why?
Thank you!
If your one-to-many is mapped as inverse, the other side needs to know the parent object. So your Votes class needs a reference to a Profile (mapped as many-to-one).
You also have to make sure that this reference to the parent object is set in your code.
Additional note: Your code sample doesn't make much sense. If you create a new profile, you have to call Session.Save and not Session.Update.
I just started with NHibernate, created my mappings using fluent NHibernate as follows:
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x._id, "Id");
Map(x => x._KdNr, "KdNr");
Map(x => x._Name, "Name");
HasMany(x => x._Contact)
.Table("Contacts")
.KeyColumn("FKCustomerID")
.LazyLoad();
}
}
public class ContactMap : ClassMap<Contact>
{
public ContactMap()
{
Id(x => x._id, "Id");
Map(x => x._Name, "Name");
}
}
And to save new records works also:
public static void AddCustomer(Customer cust)
{
using (var session = SessionFactory.Instance.OpenSession())
{
session.Save(cust);
session.Save(cust._Contact);
session.Flush();
}
}
Then I tried to select the customer I added using:
using (var session = SessionFactory.Instance.OpenSession())
{
try
{
var v = session.CreateQuery("from Customer").List<Customers>();
return (List<Customer>)v;
}
catch
{
session.Close();
throw;
}
finally
{
session.Disconnect();
}
}
}
The Customer also is loaded fine, but the contacts inside are not referenced with the following error:
Initializing[fnh.DataModel.Customer#d2f2d1c5-7d9e-4f77-8b4f-9e200088187b]-failed
to lazily initialize a collection of role:
fnh.DataModel.Kunde._Contact, no session or session was closed
But I cannot understand where the error comes from because the session is closed after executing my HQL error...
The problem is that the contacts are lazy loaded, ie. the collection is not fetched from the database in the initial query. I presume that you are passing the objects directly to the view rather than using a viewmodel? You have a few options, each have their drawbacks.
Keep the session open while the view
is being process (so called Open
Session In View approach). You are probably closing the NH session in the controler at the moment right?
Eager load the whole object graph
using .Not.Lazyload() on the
contacts. (Not recommended)
Copy all the data you need to a view
model in the controller, and pass
this to the view. Use automapper to help you with this.
Update in response to comment:
There is still great benefit in leaving collections lazy loaded. Sure, in this context you don't benefit from lazy loaded contacts on the customer object, becase you need to use them. But in another context you might only need the customer name and Id - and you can rest assured that this wont generate a big query with joins etc.
To utilize Open Session in View you don't have to pass your session to the view explicitly, rather you just pass the object as before, but leave the session open - NH will automatically generate a new query when you try to access the contacts collection. (This works becuase the customer object is still 'attached' to an open session behind the scenes). The only difference here is that you need to close the session not in the controller (where it is currently being closed explicitly with the .close() or implicitly with 'using'). Regarding where to open and close the session - there are different approaches:
In the global.asax - open in Application_BeginRequest and close in Application_EndRequest (see: this article) I'd recomend starting with this for the sake of simplicity if you want to do Open Session in View.
In a http module (basically the same as the last, but modularised) (see this article)
With these last two, you are probably thinking 'But that will mean creating a session for every page request!' - and you would be right, but really how many pages are not going to go to the DB? plus session creation is lightweight once the session factory is created.
Using an Attribute decorating the action method (you basically do the same thing as the last two, but this way you can choose which actions open an NH session). Close the session in OnActionExecuted if you want it closed after the action has completed. Close in OnResultExecuted if you want open session in view.
Of course, many people don't like Open Session in View, becuase you cannot control what queries are being generated purely in the controller - the view is triggering many queries, this can cause unpredictable performance.
3 is subtely different from 2, because in situations where you don't need the lazy loaded collection it is not fetched from the DB. For instance, in this case you might be copying to a ViewModel with the full object graph, but in another situation you might use a ViewModel with just the customer name and Id - in this case the joins for the contacts collection would not needlessly be executed. In my opinion 3 is the proper way to do things - but you end up creating many more objects (becuase of the view objects).
I am using: NHibernate, NHibernate.Linq and Fluent NHibernate on SQL Server Express 2008. I am selecting an entity using a predicate on a referenced property (many-one mapping). I have fetch=join, unique=true, lazy-load=false. I enabled the log4net log and when any such query executes it logs two identical SQL queries. Running the query returns one row, and when I attempt to use the IQueryable.Single extension method it throws the exception stating there is more than one row returned. I also tried running the query using the standard IQuery.UniqueResult method with the same result, it ends up logging and actually running the query twice, then throwing an exception stating that there were multiple rows, however running the actual query in management studio returns only one result. When I disable logging I receive the same error.
The entities and mappings are declared as follows (proper access modifiers and member type variance are implied)
class User
{
int ID;
string UserName;
}
class Client
{
int ID;
User User;
Person Person;
Address Address;
}
class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.ID);
Map(x => x.UserName);
}
}
class ClientMap : ClassMap<Client>
{
public ClientMap()
{
Id(x => x.ID);
References(x => x.User).Unique();
...
}
}
Then I invoke a queries such as the following:
ISession s = GetNHibernateSession();
...
var client = s.Linq<Client>().SingleOrDefault(x => x.User.ID = 17);
or
var client = s.Linq<Client>().Where(x => x.User.ID = 17);
or
var client = s.CreateQuery("from Client as c where c.User.ID = 17").UniqueResult<Client>();
In all cases executes two identical queries. When I enable lazy load, the client is again loaded using two queries, however upon accessing a member, such as Person, only one additional query is executed.
Is this possibly a result of Fluent generating an improper mapping? Or SQL Server Express edition not being used properly by NHibernate?
The problem was caused by another mapping I had declared. I had a class inheriting from Client which had an associated mapping. This is what caused NHibernate to query twice. I noticed this because when using Linq() it returned the subclass, not Client itself. This particular instance of inheritance and mapping was a design flaw on my part and was the root of the whole problem!
NHibernate doesn't have any trouble with SQL Express, I've used it fairly extensively. Similarily, it's unlikely Fluent NHibernate is generating invalid mappings in this simple scenario (but not unheard of).
A shot in the dark, but I believe NHibernate reserves the name Id as an identifier name, so when it sees Id in the query it knows to just look at the foreign key instead of the actual joined entity. Perhaps your naming of ID instead of Id is throwing it off?
You could try using the excellent NHibernate profiler for a more detailed view of what is happening. It comes with a 30 day trial license and while in Beta there is a discount on the full license cost