Mapping a property to an index column - nhibernate

I'm trying to map a List with an index column. This works fine, but I would also like to be able to query the index column from HQL. When I do that HQL throws an exception:
NHibernate.QueryException: could not resolve property: Position of: component[Location,Time]
To be able to query the WayPoint.Position column from HQL, I have created a Position-property in the WayPoint-class, and I would like to map this property to the Position-column.
I have tried with:
wp.Map(x => x.Position).Column("Position");
But this results in a MappingException:
NHibernate.MappingException: Repeated column in mapping for collection: Route.WayPoints column: Position
public class RouteMap : ClassMap<Route>
{
private const string LocationKey = "LocationIndex";
public RouteMap()
{
Not.LazyLoad();
ReadOnly();
Id(x => x.Id).GeneratedBy.Assigned();
Version(x => x.Version);
Map(x => x.Time);
References(x => x.StartingPoint).UniqueKey(LocationKey);
References(x => x.Destination).UniqueKey(LocationKey);
HasMany(x => x.WayPoints).Component( wp =>
{
wp.Map(x => x.Time);
//wp.Map(x => x.Position).Column("Position");
wp.Component( wp2 => wp2.Location, gl =>
{
gl.Map(x => x.Latitude);
gl.Map(x => x.Longitude);
}
);
}
).AsList(index => index.Column("Position")).Cascade.All();
}
}
create table `Route` (
Id VARCHAR(40) not null,
Version INTEGER not null,
Time BIGINT,
StartingPoint_id VARCHAR(40),
Destination_id VARCHAR(40),
primary key (Id),
unique (StartingPoint_id, Destination_id)
)
create table WayPoints (
Route_id VARCHAR(40) not null,
Latitude DOUBLE,
Longitude DOUBLE,
Time BIGINT,
Position INTEGER not null,
primary key (Route_id, Position)
)
Is it possible to map the Position property? Or is there another approach to make HQL aware of the Position-index-field?

I don't think you need to map the Position property at all; if you need to query on the index of a collection, you can do so as follows:
select item, index(item) from Order order
join order.Items item
where index(item) < 5
More information can be found in the HQL Documentation.

Try adding .ReadOnly() to your wp.Map(x => x.Position).Column("Position"); That has allowed me to map multiple properties to the same column before, at least when one of them was a References().

Related

Optional many-to-one/References results in foreign key violations on insert

I currently have the following relationship: ProductUom -> ProductImage
They both have the same primary keys: PROD_ID and UOM_TYPE
I have them mapped like this:
public ProductUomMap()
{
Table("PROD_UOM");
CompositeId()
.KeyReference(x => x.Product, "PROD_ID")
.KeyProperty(x => x.UomType, "UOM_TYPE");
References(x => x.Image)
.Columns(new string[] { "PROD_ID", "UOM_TYPE" })
.Not.Update()
.Not.Insert()
.NotFound.Ignore()
.Cascade.All();
}
public ProductImageMap()
{
Table("PROD_UOM_IMAGE");
CompositeId()
.KeyReference(x => x.ProductUom, new string[] {"PROD_ID", "UOM_TYPE"});
Map(x => x.Image, "PROD_IMAGE").Length(2147483647);
}
Whenever I create a ProductUom object that has a ProductImage it tries to insert the ProductImage first which results in a foreign key violation. I swear this was working at one time with the mapping that I have but it doesn't now.
I need the ProductImage to be a Reference (many-to-one) because the relationship here is optional and I want to be able to lazy load product images. The inserts do work correctly if I use a HasOne (one-to-one) mapping but the I cannot lazy load when I do this and querying a ProductUom seems to cause issues.
Is there something that I'm missing here? How can this mapping be modified to get what I want?
can you use LazyLoaded Properties? Then you could use something like this
Join("PROD_UOM_IMAGE", join =>
{
join.KeyColumn("PROD_ID", "UOM_TYPE");
join.Optional();
join.Map(x => x.Image, "PROD_IMAGE").Length(2147483647).LazyLoad();
}
another option is:
Id().GeneratedBy.Foreign(x => x.ProductUom);
can't test it here though, i'm writing on Mobile

Fluent NHibernate Parent-child cascade SaveOrUpdate fails

I have a parent-child relationship that I've put a test case together between Users and Groups. I did this to replicate a failure in
a Parent-Child relationship when trying to perform a cacade insert using thes relationship.
The two SQL tables are as follows:
CREATE TABLE [dbo].[User]
(
[Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Name] [varchar](50) NOT NULL,
)
CREATE TABLE [dbo].[Group]
(
[Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[GroupName] [varchar](50) NOT NULL,
[UserId] [int] NOT NULL,
)
ALTER TABLE [dbo].[Group] WITH CHECK ADD CONSTRAINT [FK_Group_User] FOREIGN KEY([UserId])
REFERENCES [dbo].[User] ([Id])
The objects represent these two tables with the following mappings:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("[User]");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name).Not.Nullable();
HasMany(x => x.Groups).KeyColumn("UserId").Cascade.SaveUpdate();
}
}
public class GroupMap : ClassMap<Group>
{
public GroupMap()
{
Table("[Group]");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.GroupName).Not.Nullable();
References(x => x.User).Column("UserId").Not.Nullable();
}
}
The code to created the objects is simply:
User u = new User() { Name = "test" };
Group g = new Group() { GroupName = "Test Group" };
u.Groups.Add(g);
using (var session = factory.OpenSession())
{
session.SaveOrUpdate(u);
}
However it fails with exception "Cannot insert the value NULL into column 'UserId', table 'test.dbo.Group'; column does not allow nulls. INSERT fails.
The statement has been terminated". I suspect that this is dude to the parent object's Id (an identity column) being passed through as NULL and not the new values. Is this a bug or is there a way to fix these mappings so that this cascade relationship succeeds?
I recently had this exact type of mapping working fine in a project. My advice is:
Learn how the Inverse attribute of a HasMany relationship works. Great explanation here
You need a two way association between the parent and child object. This is explained at the bottom of the article linked to above.
Another good advice is to encapsulate your collections better
- Don't access your collections modification methods directly. The collection properties should be read-only and the parent (User class in your case) should have AddGroup() and RemoveGroup() methods that changes the private collection. In order for this to work you have to let NHibernate access the private collection member by using the .Access.CamelCaseField(Prefix.Underscore) or similar mapping attribute. Good discussion about it here
I can post an example mapping and class files if needed.
You will have to save the user first then assign the group to the user and save that:
using (var session = factory.OpenSession())
{
User u = new User() { Name = "test"};
session.SaveOrUpdate(u);
Group g = new Group() { GroupName = "Test Group", User = u };
session.SaveOrUpdate(g)
}
I have found that you cannot cascade save child /parent related objects which have only just been created.

HasMany relation inside a Join Mapping

So, I'm having a problem mapping in fluent nhibernate. I want to use a join mapping to flatten an intermediate table: Here's my structure:
[Vehicle]
VehicleId
...
[DTVehicleValueRange]
VehicleId
DTVehicleValueRangeId
AverageValue
...
[DTValueRange]
DTVehicleValueRangeId
RangeMin
RangeMax
RangeValue
Note that DTValueRange does not have a VehicleID. I want to flatten DTVehicleValueRange into my Vehicle class. Tgis works fine for AverageValue, since it's just a plain value, but I can't seem to get a ValueRange collection to map correctly.
public VehicleMap()
{
Id(x => x.Id, "VehicleId");
Join("DTVehicleValueRange", x =>
{
x.Optional();
x.KeyColumn("VehicleId");
x.Map(y => y.AverageValue).ReadOnly();
x.HasMany(y => y.ValueRanges).KeyColumn("DTVehicleValueRangeId"); // This Guy
});
}
The HasMany mapping doesn't seem to do anything if it's inside the Join. If it's outside the Join and I specify the table, it maps, but nhibernate tries to use the VehicleID, not the DTVehicleValueRangeId.
What am I doing wrong?
Can you explain the average value column in the DTVehicleValueRange table? Isn't this a calculated value (i.e. no need to persist it)?
It looks like you have a many-to-many relationship between Vehicle and DTValueRange, which of course would not be mapped with a join, rather with a HasManyToMany call.
Ran into a similar issue today using a Map to create a view. The SQL generated showed it trying to do the HasMany<> inside the join based on the Id of the ParentThing and not WorkThing (same problem you were having)
After much mapping of head-to-desk it turns out adding the propertyref onto the hasmany solved it.
public class ThingMap : ClassMap<WorkThingView> {
public ThingMap() {
ReadOnly();
Table("ParentThing");
Id(x => x.ParentThingId);
Map(x => x.ParentName);
Join("WorkThing", join => {
join.KeyColumn("ParentThingId");
join.Map(m => m.FooCode);
join.Map(m => m.BarCode);
join.Map(x => x.WorkThingId);
join.HasMany(x => x.WorkThingCodes)
.Table("WorkThingCode").KeyColumn("WorkThingId").PropertyRef("WorkThingId")
.Element("WorkThingCode");
});
}
}

NHibernate: why doesn't my profiler query correspond to my fluent mapping?

I have this fluent mapping:
sealed class WorkPostClassMap : ClassMap<WorkPost>
{
public WorkPostClassMap()
{
Not.LazyLoad();
Id(post => post.Id).GeneratedBy.Identity().UnsavedValue(0);
Map(post => post.WorkDone);
References(post => post.Item).Column("workItemId").Not.Nullable();
References(Reveal.Property<WorkPost, WorkPostSheet>("Owner"), "sheetId").Not.Nullable();
}
parent class:
sealed class WorkPostSheetClassMap : ClassMap<WorkPostSheet>
{
public WorkPostSheetClassMap()
{
Id(sheet => sheet.Id).GeneratedBy.Identity().UnsavedValue(0);
Component(sheet => sheet.Period, period =>
{
period.Map(p => p.From, "PeriodFrom");
period.Map(p => p.To, "PeriodTo");
});
References(sheet => sheet.Owner, "userId").Not.Nullable();
HasMany(sheet => sheet.WorkPosts).KeyColumn("sheetId").AsList();
}
WorkItem class:
sealed class WorkItemClassMap : ClassMap<WorkItem>
{
public WorkItemClassMap()
{
Not.LazyLoad();
Id(wi => wi.Id).GeneratedBy.Assigned();
Map(wi => wi.Description).Length(500);
Version(wi => wi.LastChanged).UnsavedValue(new DateTime().ToString());
}
}
And when a collection of WorkPosts are lazy loaded from the owning work post sheet I get the following select statement:
SELECT workposts0_.sheetId as sheetId2_,
workposts0_.Id as Id2_,
workposts0_.idx as idx2_,
workposts0_.Id as Id2_1_,
workposts0_.WorkDone as WorkDone2_1_,
workposts0_.workItemId as workItemId2_1_,
workposts0_.sheetId as sheetId2_1_,
workitem1_.Id as Id1_0_,
workitem1_.LastChanged as LastChan2_1_0_,
workitem1_.Description as Descript3_1_0_
FROM "WorkPost" workposts0_
inner join "WorkItem" workitem1_ on workposts0_.workItemId=workitem1_.Id
WHERE workposts0_.sheetId=#p0;#p0 = 1
No, there are a couple of things here which doesn't make sence to me:
The workpost "Id" property occurs twice in the select statement
There is a select refering to a column named "idx" but that column is not a part of any fluent mapping.
Anyone who can help shed some light on this?
The idx column is in the select list because you have mapped Sheet.WorkPosts as an ordered list. The idx column is required to set the item order in the list. I'm not sure why the id property is in the select statement twice.
By the way, an unsaved value of 0 is the default for identity fields, so you can remove .UnsavedValue(0) if you want.

Fluent NHibernate executes single-row-selects on thousands of objects to link to parents

The following is a simplification of my problem domain. We have a series of Trades, that each gets a record in Valuations every business day.
We filter our Valuations List used for a specific day, but to populate the Trade against each of the Valuation rows, NHibernate fires single row selects on the Trades table for around 50k rows in the valuation table. How can I change this so NHibernate does a single select on the Trade table?
CREATE TABLE Trades
( TradeId INT
, InstrumentType VARCHAR(20)
, TradeDate DATETIME
, PRIMARY KEY
( TradeId ) )
CREATE TABLE Valuations
( TradeId INT
, ValueDate DATETIME
, PresentValue NUMERIC(12,4)
, PRIMARY KEY
( TradeId
, ValueDate ) )
.
class Trade
{
public int TradeId;
public string InstrumentType;
public DateTime TradeDate;
}
class Valuation
{
public int TradeId;
public DateTime ValueDate;
public double PresentValue;
public Trade Trade;
}
.
class ValuationMap : ClassMap<Valuation>
{
public ValuationMap()
{
WithTable("Valuations");
UseCompositeId()
.WithKeyProperty(x => x.ValueDate)
.WithKeyProperty(x => x.TradeId);
Map(x => x.PresentValue);
References(x => x.Trade, "TradeId")
.LazyLoad()
.Cascade.None()
.NotFound.Ignore()
.FetchType.Join();
}
}
class TradeMap : ClassMap<Trade>
{
public TradeMap()
{
WithTable("Trades");
Id( x => x.TradeId );
Map(x => x.InstrumentType);
Map(x => x.TradeDate);
Map(x => x.Valuations);
}
}
.
public List<Valuation> GetValuations(DateTime valueDate)
{
return (from p in _nhibernateSession.Linq<Valuation>()
where p.ValueDate == valueDate
select p).ToList();
}
You should also look at batch fetching. This is quoted from the Nhib manual - actually google's cache as the site seems to be down for maintenance atm:
Imagine you have the following
situation at runtime: You have 25 Cat
instances loaded in an ISession, each
Cat has a reference to its Owner, a
Person. The Person class is mapped
with a proxy, lazy="true". If you now
iterate through all cats and get the
Owner of each, NHibernate will by
default execute 25 SELECT statements,
to retrieve the proxied owners. You
can tune this behavior by specifying a
batch-size in the mapping of Person:
<class name="Person" lazy="true" batch-size="10">...</class>
NHibernate will now execute only three
queries, the pattern is 10, 10, 5. You
can see that batch fetching is a blind
guess, as far as performance
optimization goes, it depends on the
number of unitilized proxies in a
particular ISession.