Nhibernate dont update child - nhibernate

i have a Class Map CardMatch which have Employee and Card as reference
Id(x => x.MatchId).GeneratedBy.Sequence("CARDMATCH_SEQ").Column("MATCHID");
References(x => x.Employee).Column("EMPNO");
References(x => x.Card).Column("CARDID").LazyLoad(Laziness.False).Cascade.SaveUpdate();
CardMatch _cm = _rep.getById(1);
_cm.Card.CardLimit = 500;
_rep.Update(_cm);
doesnt update the Child (Card). How can i update the Child?

IMO your FNH mapping is correct. Flush method should be called on ISession instance. The last line is most likely redundand because NH tracks changes on its own.
CardMatch _cm = _rep.getById(1);
_cm.Card.CardLimit = 500;
_rep.Flush(); // save changes

Related

NHibernate Stackoverflow exception in composite relationship

I get a stackoverflow exception when trying to remove a composite object from a collection (but only when i wrap it inside a transaction). However getting the object and deleting it on the session, instead of the collection, and saving the parent works.
The code looks like this:
using (var session = sessionProvider.GetSession())
{
using (var transaction = session.BeginTransaction())
{
var products = session.Query<MyObject>().Select(x => x.MyObjectId > 0).ToList();
//Would fail
var variant = session.Query<MyObject>().FirstOrDefault(x => x.Name == "some name that exists");
var parent = MyObject.Parent;
parent.RemoveMyObject(variant);
session.Save(parent);
//Doesn't fail
//session.Delete(variant);
transaction.Commit();
}
session.Flush();
}
And my mapping looks like this:
HasMany(x => x.MyObject).AsSet().Cascade.AllDeleteOrphan().Inverse().KeyColumn("MyObjectId").BatchSize(2000);
References(x => x.MyObject).Column("MyObjectId");
What should i do to fix this ? It should work in both ways. And it is, as said, only when there's a transaction around. Both approaches works without a transaction.
Best regards
Morten

When does NHibernate JoinQueryOver load full collection?

I've got a model where a Person has a lazy-loaded collection of Registrations. My NHibernate 3.3.3 query uses JoinQueryOver across that relation and some more:
var reg = Session.QueryOver<Person>()
.Where(p => p.ID == pid)
.JoinQueryOver<Registration>(p => p.Registrations)
.Where(r => r.IsDropped == false)
.JoinQueryOver<Slot>(r => r.Slot)
.JoinQueryOver<Position>(s => s.Position)
.JoinQueryOver<AcademicTerm>(p => p.Term)
.Where(t => t.ID == currentTerm.ID)
.SingleOrDefault();
In the log, I see the expected SQL for the query, and I get the expected Person instance, but when I access the Registrations collection, the full collection of registrations gets (lazy) loaded. I was expecting it would just be a partial collection of the registrations that meet the criteria.
In a previous question, I had the opposite problem, and fixed it by adding a Fetch() clause to the query.
When does a JoinQueryOver criterion cause the root entity to have a partial collection, and when does it have an unloaded collection that will get lazy loaded? I feel like I should understand this.
My fluent mapping for Person:
public class PersonMapping : ClassMap<Person>
{
public PersonMapping()
{
Id(x => x.ID).GeneratedBy.Guid();
Map(x => x.Username).Length(20).Unique();
Map(x => x.HashedPassword).Length(Security.HashedPasswordEncodedLength);
Map(x => x.PasswordSalt).Length(Security.PasswordSaltEncodedLength);
Map(x => x.LastName).Length(25).Index("FullName");
Map(x => x.FirstName).Length(20).Index("FullName");
Map(x => x.StudentID).Length(12).Nullable();
Map(x => x.EmployeeID).Length(12).Nullable();
Map(x => x.EmailAddress).Length(72);
Map(x => x.IsAdmin).Nullable();
Map(x => x.IsAgencyStaff).Nullable();
Map(x => x.IsCenterStaff).Nullable();
Map(x => x.IsFaculty).Nullable();
Map(x => x.IsRegistrant).Nullable();
Map(x => x.IsStudent).Nullable();
Map(x => x.Type);
Map(x => x.AuthenticationMode);
HasMany<Registration>(x => x.Registrations).KeyColumn("Registrant_id")
.LazyLoad()
.AsBag()
.Inverse()
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate();
}
}
Update
Comparing this with my other question (linked above), where I had the opposite problem, that one uses a Left.JoinQueryOver to get from the base entity to the collection. I tried that here, and then got the expected partial collection. (I don't actually need the left join in the other case; I don't remember why it's there.)
The records fetched are the same with or without the left join, but NHibernate populates the collection in one case, and not in the other. I doubt that's behavior I can depend on, so I've changed my strategy to query for the registrations separately.
This condition in the QueryOver:
...
.JoinQueryOver<Registration>(p => p.Registrations)
.Where(r => r.IsDropped == false)
...
Is in fact a WHERE clause for a Person. I mean, this condition is deciding if there is such a Person, having at least one Registration which is NOT dropped. It is not a way, how to filter the Registration colleciton. BUT:
NHibernate has two nice ways how to filter collections themselves. These are where clause and filter.
1. where clause
Easily said: we can extend the collection mapping, whit a SQL Statement, which will be always added as a where condition. See where 6.2. Mapping a Collection, extract:
where="arbitrary sql where condition"
== (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)
so in our case the mapping:
HasMany<Registration>(
...
.Where("IsDropped = 0"); // SQL Statement
2. filter setting
While where clause is static, baked into the assembly/mapping... filter is a way how to do that dynamically. So just imagine, that we would sometimes like to have a set of Registrations, which are dropped, next time, which are not dropped.
We would like to be able to change "IsDropped = 0" to "IsDropped = 1" (better scenario could be switch by culture or lang). And that's where the filter could be applied, see 18.1. NHibernate filters
There is a small draft of the filter stuff with fluent, see the links below to get a complete picture.
HasMany
...
.ApplyFilter<MyFilter>()
And filter could be like:
public class MyFilter: FilterDefinition
{
public MyFilter()
{
WithName("dropped")
.WithCondition("IsDropped == :isDropped")
.AddParameter("isDropped", NHibernate.NHibernateUtil.Boolean);
}
}
And finally, we can enable the Filter for a whole session (coool, all collections will be filtered the same way in that transaction/session, all collection mapped with MyFilter)
session.EnableFilter("dropped").SetParameter("isDropped", false);
See more here:
Syntax to define a NHibernate Filter with Fluent Nhibernate (NOT Accepted answer)
How do you do additional filtering on a HasMany mapping in Fluent nHibernate?

Insert new NHibernate entity and immediately assign to parent

I have parent and child objects BOOKLET and DEMOGRAPHICS_INFO in my Oracle database mapped as follows in my data layer:
public BookletMapping()
{
Table("BOOKLET");
Id(x => x.Id, m => m.Column("ID");
...
ManyToOne(x => x.DemographicsInfo, m => m.Column("DEMOGRAPHICS_INFO_ID"));
}
public DemographicsInfoMapping()
{
Table("DEMOGRAPHICS_INFO");
Id(x => x.Id, m => m.Column("ID");
...
}
I have intentionally left out the DemographicsInfo relationship to the Booklet because I don't need to traverse my entities that direction. The ManyToOne relationship will actually be a one-to-one.
I have written a test to ensure I can create a DemographicsInfo and immediately assign it to its parent Booklet, and that looks like this:
[Test]
public void ShouldSaveCorrectEntity()
{
var booklet = _unitOfWork.Get<Booklet>(4);
var demInfo = new DemographicsInfo();
_unitOfWork.Insert(demInfo);
booklet.DemographicsInfo = demInfo;
_unitOfWork.Save();
demInfo.Id.ShouldNotEqual(0);
}
When I call Save(), I get the following exception:
{"ORA-02291: integrity constraint (<schema>.BOOKLET_DEMOGRAPHICS_INFO_FK1) violated - parent key not found\n"}
This is because the demInfo object is not given an Id upon Insert(). My Insert implementation looks like this:
public void Insert<T>(T entity)
{
using (var transaction = _session.BeginTransaction())
{
_session.Save(objectToSave);
_session.Flush();
transaction.Commit();
}
}
Where _session is an NHibernate ISession. Because I have both Saved the new entity (it persists successfully) and Flushed my session, I would expect my demInfo variable to have an Id, but it remains 0, which is a foreign key violation when I try to save my parent object. Am I overlooking a step here? Should I rethink my pattern for adding a new child to an existing parent?
I have solved my issue. As it turns out, the Oracle convention of using Sequences for Id column values and populating them using Triggers on row insert disallows NHibernate from learning the Id upon persisting an entity. I added a Sequence Generator to my Id maps and Insert() now updates the Id on my transient entity upon save so I can assign the DemographicsInfo to my Booklet.DemographicsInfo.
Id(x => x.Id, m =>
{
m.Column("ID");
m.Generator(Generators.Sequence, a => a.Params(new { sequence = "SEQ_TABLE_ID" }));
});
Now my test passes with no exceptions.

property filter with fluent nHibernate automapping

i'm trying to create a filter, using fluent nH (1.2) automapping with nH 2.1.2.
I've followed the example here, but I keep getting the exception:
filter-def for filter named 'DateFilter' was never used to filter classes nor collections..
the filter class:
public class DateFilter : FilterDefinition
{
public DateFilter()
{
WithName(Consts.FilterConsts.DATE_FILTER)
.AddParameter("date", NHibernate.NHibernateUtil.DateTime)
.WithCondition("DATEPART(dayofyear,EntityTime) = DATEPART(dayofyear, :date)")
;
}
}
and in the mapping override:
mapping.HasMany(x => x.Stuff)
.LazyLoad()
.ReadOnly()
.ApplyFilter<DateFilter>();
here's my configuration code.
Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.DefaultSchema("dbo") //set default schema to enable full-qualified queries
.AdoNetBatchSize(batchSize > 0 ? batchSize : 1)
.UseReflectionOptimizer()
.ConnectionString(c => c.FromConnectionStringWithKey(connectionStringKey))
.Cache(c => c.UseQueryCache()
.ProviderClass(
isWeb ? typeof(NHibernate.Caches.SysCache2.SysCacheProvider).AssemblyQualifiedName //in web environment- use sysCache2
: typeof(NHibernate.Cache.HashtableCacheProvider).AssemblyQualifiedName //in dev environmet- use stupid cache
))
)
.Mappings(m => m.AutoMappings.Add(
AutoMap.AssemblyOf<Domain.Entity>(cfg) //automapping the domain entities
.IncludeBase<Domain.SomethingBase>() //ensure that although SomethingBase is a base class, map it as well. this enables us to store all Something sub-classes in the same table
.IncludeBase<Domain.OrOtherBase>() //create a table for the abstract 'OrOtherBase' class
.UseOverridesFromAssemblyOf<MappingOverrides.MappingOverride>()
.Conventions.Add(DefaultCascade.All()) //make sure that all saves are cascaded (i.e when we save a zone, its queues are saved as well)
.Conventions.AddFromAssemblyOf<IdGenerationWithHiLoConvention>()
))
.Mappings(m => m.FluentMappings.Add(typeof(DateFilter)));
if I move the line before the automapping part, I get the exception:
NHibernate.MappingException: filter-def for filter named 'DateFilter' was not found.
can anybody tell me what I'm doing wrong?
OK, so I figured this out. When you add the mappings separately like that, they end up in different mappings and it will either complain that you never use the filter or complain that it can't find the filter, because it's not looking both places. The solution is to add it directly to the automap, so in your case like:
//other stuff up here
.Mappings(m => m.AutoMappings.Add(() => {
var a = AutoMap.AssemblyOf<Domain.Entity>(cfg)
.IncludeBase<Domain.SomethingBase>() //and also cascades and conventions and stuff
a.Add(typeof(DateFilter));
return a;
}));
Kinda gross because .Add() isn't fluent, but it does work.

Refreshing collections filtered in the mapping

I have a collection which is filtered at the mapping level to enable soft deletion using an "isDeleted" column in the database.
The mapping looks like this:
HasMany(x => x.UploadedFiles).Where("IsDeleted = 0")
When I set the isDeleted property for some items the collection does not automatically refresh to reflect the deletion until I reload the entity.
Is there any way to force a "refiltering" without reloading the entity ?
The Where clause in the mapping is to filter during fetching. It is not used at run-time, which is why you're not seeing UploadedFiles drop out of your collection when you set IsDeleted = true. I don't believe it is possible to refresh the collection without reloading the entity that owns it.
I would recommend expressing your intent in your object model.
private IList<File> uploadedFiles = new List<File();
public virtual IEnumerable<File> UploadedFiles {
get {
return uploadedFiles.Where(x => x.IsDeleted == false);
}
}
And then modifying your mapping to access your backing field...
HasMany(x => x.UploadedFiles)
.Access.CamelCaseField()
.Where("IsDeleted = 0")