NHibernate map by code keycolumn? - c#

just started on a project to convert a NHibernate Fluent mapping into the NHibernate Mapping By Code as part of an upgrade associated with one of my old applications.
Almost there, but I stumbled upon something I can't convert properly and found myself stumped. Now I hope maybe some of you experienced out there can help me with it.
Below is the original mapping using Fluent:
HasMany<ExampleEntity>(x => x.OtherExampleEntities)
.OptimisticLock.False()
.AsSet()
.KeyColumn("ParentExampleEntityId")
.Inverse()
.Cascade.SaveUpdate();
Now I got stuck on converting the KeyColumn-part of my mapping, below is my current progress (thus keyColumn still being there):
Set(x => x.OtherExampleEntities, x => {
x.OptimisticLock(false);
x.KeyColumn("ParentExampleEntityId");
x.Inverse(true);
x.Cascade(Cascade.Persist);
}, map => map.OneToMany(r => r.Class(typeof(ExampleEntity))));
There's not a lot of documentation regarding the mapping by code part of NHibernate but I've been spending a lot of time with the posts made by notherdev (#blogspot.se). All help is appreciated.

There is a comprehensive post about <set> mapping
Mapping-by-Code - Set and Bag by Adam Bar
Small snippet, but please observe the post:
Set(x => x.Users, c =>
{
c.Fetch(CollectionFetchMode.Join); // or CollectionFetchMode.Select,
// CollectionFetchMode.Subselect
c.BatchSize(100);
c.Lazy(CollectionLazy.Lazy); // or CollectionLazy.NoLazy, CollectionLazy.Extra
c.Table("tableName");
c.Schema("schemaName");
c.Catalog("catalogName");
c.Cascade(Cascade.All);
c.Inverse(true);
c.Where("SQL command");
c.Filter("filterName", f => f.Condition("condition"));
c.OrderBy(x => x.Name); // or SQL expression
c.Access(Accessor.Field);
c.Sort<CustomComparer>();
c.Type<CustomType>();
c.Persister<CustomPersister>();
c.OptimisticLock(true);
c.Mutable(true);
<key column="" ...> mapping:
c.Key(k =>
{
k.Column("columnName");
// or...
k.Column(x =>
{
x.Name("columnName");
// etc.
});
k.ForeignKey("collection_fk");
k.NotNullable(true);
k.OnDelete(OnDeleteAction.NoAction); // or OnDeleteAction.Cascade
k.PropertyRef(x => x.Name);
k.Unique(true);
k.Update(true);
});
....

Related

Using RefactorThis.GraphDiff library

I have the following tables in my database
And a graph mapping of (table address not shown in image):
.OwnedEntity(s => s.Address)
.OwnedCollection(s => s.SiteActivityPlants)
.OwnedCollection(s => s.SiteActivityPlants, with => with.AssociatedCollection(d => d.SiteActivityPlantNotes))
What I am trying to do is update the data in table SiteActivityPlantNotes.
After looking at Documentation, AssociatedCollection just updates the reference, what I need to do is update the data records within SiteActivityPlantNotes.
Is this possible?
2 Changes in your code should do it:
Removed second line ".OwnedCollection(s => s.SiteActivityPlants)"
as it is already part of the third line
Changed in third line "with.AssociatedCollection" to
"with.OwnedCollection"
Final Code:
.OwnedEntity(s => s.Address)
.OwnedCollection(s => s.SiteActivityPlants, with => with.OwnedCollection(d => d.SiteActivityPlantNotes))

Why is nHibernate lazy loading and ignoring the results of a future query?

I have a method that retrieves an Entity from a database using nHibernate. It's quite a complex Entity:
Level4 has many Level3s which have many Level2s which have many Level1s which has a Level1Ref
So I use a few futures like this:
var future = this.Session.QueryOver<Level4>()
.Where(x => x.Id == level4Id)
.Fetch(x => x.Level3List).Eager
.Fetch(x => x.Level3List.First().Level2List).Eager
.Left.JoinAlias(x => x.Level3List, () => level3Alias, x => x.AnotherThing.Id == anotherThingId)
.FutureValue();
And some queries like this:
this.Session.QueryOver<Level1>()
.Fetch(x => x).Eager
.Fetch(x => x.Level1Ref).Eager
.Fetch(x => x.Level2).Eager
.Inner.JoinAlias(x => x.Level2, () => level2Alias)
.Inner.JoinAlias(() => level2Alias.Level3, () => level3Alias, x => x.AnotherThing.Id == anotherThingId && level3Alias.Level4.Id == level4Id)
.Future();
And then:
var record = future.Value;
This all generates the SQL that I would expect but when I try to iterate over the Level2.Level1List it lazy loads the records in that list.
The Question:
This is the problem. What have I? done wrong in my query that nHibernate thinks it needs to go to the database for information that it has already got? (I've got a hunch that I need to swap some of my JoinQueryOver bits for eager fetches?
(Questions edited to simplify examples)
After lots of investigation the only way I could get this working was change all of my queries so they have the same TRoot. Ie. Change them so they all start like this:
this.Session.QueryOver<Level4>()
This obviously isn't ideal in situations like this:
FishMonger
Collection of Fish
Collection of Eye
Collection of Bone
It means I have to write two queries and join in Fish twice...
The alternative is to lazy load and batch the queries but the requirement is to make only one round trip to the database.

How do I apply RandomScore to a FunctionScore query using NEST

I'm trying to apply RandomScore to a functionScore query as shown below (i've simplified list of filters):
sd.Query(q => q
.FunctionScore(fs => fs
.Query(fsq => fsq.MatchAll())
.Functions(
fx => fx.Weight(0.8).Filter(f => f.Term("ImageSize", "Small")),
fx => fx.Weight(0.7).Filter(f => f.Exists(t => t.ThumbnailUrl)))
.RandomScore(74)
.ScoreMode(FunctionScoreMode.Multiply)
.BoostMode(FunctionBoostMode.Multiply)
)
);
When I execute this I get the following error:
ElasticsearchParseException[You can either define "functions":[...] or
a single function, not both. Found "functions": [...] already, now
encountering "random_score".];
Am I doing something wrong or is this a bug?
Many thanks.
As per Rob above this is a known bug and will be fixed in next release - https://github.com/elastic/elasticsearch-net/issues/1559

Filter nested models on property in last node with NHibernate

I am using NHibernate with mapping by code.
I have three models: Solution, Installation and System. There are one-to-many relations between them. So that each Solution has a list of Installations, and each Installation has a list of Systems.
Each system has a property "Type", which can be "1" or "0".
I am trying to write a method in the Solution repository that will return all the Solutions, with their Installations with only the Systems of type "1".
I have tried the Where-keyword in the SystemMap but i get the same result with and without it. Then i tried a few different experiments with QueryOver(???) without success.
How do i go about to filter on information in the last node?
Thank to your answer, i have done the following implementation, but it results in a huge amount of Systems and Solutions. Maybe i have done something wrong?
The Maps are as follows:
public SAPSolutionMap()
{
Id(t => t.YPID);
Property(e => e.ShortName);
Property(e => e.FullName);
Bag(x => x.SapInstallations, colmap =>
{
colmap.Table("SAPInstallation");
colmap.Key(x => x.Column("Solution"));
colmap.Inverse(true);
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Fetch(CollectionFetchMode.Join);
colmap.Cascade(Cascade.None);
}, map => map.OneToMany(m => m.Class(typeof(SAPInstallation))));
}
public SAPInstallationMap()
{
Id(t => t.InstallationNumber);
Bag(x => x.SapSystems, colmap =>
{
colmap.Table("sapgui");
colmap.Key(x => x.Column("Installation"));
colmap.Inverse(true);
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Cascade(Cascade.None);
colmap.Fetch(CollectionFetchMode.Join);
//colmap.Where("Type = 1");
}, map => map.OneToMany(m => m.Class(typeof(SAPSystem))));
ManyToOne(x => x.SapSolution, map =>
{
map.Column("Solution");
map.NotNullable(true);
map.Cascade(Cascade.None);
map.Class(typeof(SAPSolution));
});
}
public SAPSystemMap()
{
Id(t => t.ID, t => t.Generator(Generators.Identity));
Property(e => e.Type);
Property(e => e.ExplanationText);
ManyToOne(x => x.SapInstallation, map =>
{
map.Column("Installation");
map.NotNullable(true);
map.Cascade(Cascade.None);
map.Class(typeof(SAPInstallation));
});
}
And the Query:
public IList<SAPSolution> GetProductionSystems()
{
SAPSystem syst = null;
SAPInstallation installation = null;
var subquery = QueryOver.Of(() => syst)
.JoinQueryOver(x => x.SapInstallation, () => installation)
.Where(() => syst.Type == 1)
.Select(x => installation.SapSolution.YPID);
// main Query
var query = Session.QueryOver<SAPSolution>()
.WithSubquery
.WhereProperty(root => root.YPID)
.In(subquery);
return query.List<SAPSolution>();
}
Thank you!
General solution should be:
// this is a subquery (SELECT ....
System syst = null;
Installation installation = null;
var subquery = QueryOver.Of(() => syst)
.JoinQueryOver(x => x.Installation, () => installation)
.Where(() => syst.Type == 1)
.Select(x => installation.Solution.ID)
;
// main Query
var query = session.QueryOver<Solution>()
.WithSubquery
.WhereProperty(root => root.ID)
.In(subquery)
;
var list = query
.Take(10)
.Skip(10)
.List<Solution>();
What we can see, that Solution, Installation and System
System has property Installation (many-to-one)
Installation has property Solution (many-to-one)
This is expect-able, because it goes side by side with one-to-many (it is the reverse mapping)
So, then we create subquery, which returns just solution ID's which belong to system with searched Type.
Main query is flat (the great benefit) and we can use paging on top of it.
We would be able to do that even if there is only one way (one-to-many). But that will generate more complicated SQL query ... and does not make sense. In C# we can have both relations...
EXTEND:
You did a great job. Your mapping and query is really cool. But there is one big but: LAZY is what we should/MUST use. Check this:
NHibernate is lazy, just live with it, by Ayende
So, our, collections cannot be FETCHING with a JOIN, because that will multiply the result (10 solutions * 100 installation * 10 systems == 10000 results)
Bag(x => x.SapSystems, colmap =>
{
...
// THIS IS not good way
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Fetch(CollectionFetchMode.Join);
We should use LAZY as possible. To avoid later 1 + N issue, we can use batch-fetching (for example check this)
How to Eager Load Associations without duplication in NHibernate?
So, our collections should be mapped like this:
Bag(x => x.SapSystems, colmap =>
{
...
// THIS IS not good way
colmap.Lazy(CollectionLazy.Lazy);
colmap.BatchSize(100);
With this setting, the query will really use only the root object and related collections will be loaded very effectively

Adding a new item to a collection using Fluent NHibernate doesn't INSERT the new row in the DB

I have a model called ShoppingLists and each ShoppingLists has a collection of ShoppingListItems called Items. What I would like to be able to do is add a new item to my list as such:
dbList.Items.Add(new ShoppingListItems(Guid.NewGuid(), identity.UserId, source.Raw));
I would expect the ShoppingListItems to automatically be linked to its parent ShoppingLists class, and for NHibernate to create the appropriate SQL INSERT statement when the transaction is committed. However, instead I get the exception:
NHibernate.StaleStateException was unhandled
HResult=-2146232832
Message=Unexpected row count: 0; expected: 1
Source=NHibernate
What I have to do instead is create the object, save it, then add it to the collection:
var newItem = new ShoppingListItems(Guid.NewGuid(), identity.UserId, source.Raw);
newItem.ShoppingList = dbList;
session.Save(newItem);
dbList.Items.Add(newItem);
I'd like to eliminate the need to do this. My mappings for ShoppingLists is as such:
Id(x => x.ShoppingListId);
Map(x => x.UserId).Not.Nullable();
Map(x => x.Title).Not.Nullable();
HasMany(x => x.Items)
.KeyColumn("ShoppingListId")
.Cascade.Delete(); // If Shopping List is deleted, delete all the Items that reference this list
And my mappings for ShoppingListItems is:
Id(x => x.ItemId);
Map(x => x.Raw).Length(50);
Map(x => x.Qty);
Map(x => x.Unit);
Map(x => x.UserId).Not.Nullable();
Map(x => x.CrossedOut).Not.Nullable();
References(x => x.Recipe).Column("RecipeId");
References(x => x.Ingredient).Column("IngredientId");
References(x => x.ShoppingList).Column("ShoppingListId");
I've tried playing around with Cascade.All() on each, to no avoid. Any ideas?
The issue here is related to the ShoppingListItem Guid ID handling (see the code snippets of the item creation in the question).
The "working" scenario, when the ShoppingListItem is created and persisted, does the following step - generates the Guid ID and passes it into the constructor
var newItem = new new ShoppingListItems(Guid.NewGuid()...)
Next, NHiberante is asked to do session.Save(newItem), so the INSERT statement is issued...
But what will happen in the second case, when the session.Save(newItem) is not called, only Add() the item into the collection of the parent ShoppingLists?
As mentioned here: 5.1.4.7. Assigned Identifiers (an extract)
...entities that use assigned generator cannot be saved via the ISession's SaveOrUpdate() method. Instead you have to explicitly specify to NHibernate if the object should be saved or updated...
What is NHibernate doing in this case (when the session.Flush() is called) is guessing: should be INSERT or UPDATE issued for each of the collection items?
When it comes to the newItem, it compares its Guid ID with the UnsavedValue(), which is in this case Guid.Empty.
This comparison will result in a decision: Issue UPDATE statement, the Guid ID does not represent new object.
The UPDATE is executed. DB responds: Update done, no errors, but also no rows updated*. NHibernate throws:
Unexpected row count: 0; expected: 1
Solution
Leave the GUID generation on the NHibernate. E.g.:
Id(x => x.ItemId)
.GeneratedBy.GuidComb()
.UnsavedValue(Guid.Empty);
And also, rather explicitly set the newItem reference to its parent, an optimize the cascade with Inverse() setting
// both assignments
newItem.ShoppingList = dbList;
dbList.Items.Add(newItem);
The list mapped with inverse:
// improved write operations handling
HasMany(x => x.Items)
.KeyColumn("ShoppingListId")
.Inverse()
...
please recheck the mappings for ShoppingListItems
You have to do a cascade all and inverse should be true
you have a bidirectional association between shoppinglist and item.
You may specify a bidirectional many-to-many association simply by mapping two many-to-many associations to the same database table and declaring one end as inverse.

Categories

Resources