I'm getting an intermittant problem with NHibernate where it generates a query for an entity, but replaces one of the columns with a column from completely different (and unrelated) entity.
It only ever replaces a single column, and is generally solved by restarting the application (though sometimes it takes a couple of attempts).
ASP.NET application (.NET 4.0)
SessionFactory created during Application_Start
NHibernate 3.3.1- All mappings/configuration done via Mapping By Code
Using Nhibernate Criteria
Any input on this would be much appreciated!
Entity
public class LiquiditySourceItem : RunDataEntity, IEntity<int>
{
public virtual int Id { get; protected internal set; }
public virtual int IdentID { get; protected internal set; }
public virtual string Portfolio { get; protected internal set; }
public virtual string ProfitCentre { get; protected internal set; }
public virtual DateTime? MaturityDate { get; protected internal set; }
public virtual string Curr1 { get; protected internal set; }
public virtual string Curr2 { get; protected internal set; }
public virtual decimal Reval { get; protected internal set; }
public virtual string ContractType { get; protected internal set; }
public virtual string ContractType2 { get; protected internal set; }
public virtual string ContractCode { get; protected internal set; }
public virtual decimal AmountSignedTradeUnit { get; protected internal set; }
public virtual decimal Amount2Signed { get; protected internal set; }
public virtual decimal SpotDelta { get; protected internal set; }
public virtual string TradeRevalCurr { get; protected internal set; }
}
Entity Mapping
public LiquiditySourceItemMap()
{
Id(x => x.Id, map => map.Column("RowId"));
Property(x => x.IdentID, map => map.Column("IdentID"));
Property(x => x.Portfolio, map => map.Column("Portfolio"));
Property(x => x.ProfitCentre, map => map.Column("ProfitCentre"));
Property(x => x.MaturityDate, map => map.Column("Con_Expiry"));
Property(x => x.BuySell, map => map.Column("BS"));
Property(x => x.Curr1, map => map.Column("Curr1"));
Property(x => x.Curr2, map => map.Column("Curr2"));
Property(x => x.Reval, map => map.Column("Reval"));
Property(x => x.ContractType, map => map.Column("ContractType"));
Property(x => x.ContractType2, map => map.Column("ContractType2"));
Property(x => x.ContractCode, map => map.Column("ContractCode"));
Property(x => x.AmountSignedTradeUnit, map => map.Column("AmountSignedTradeUnit"));
Property(x => x.Amount2Signed, map => map.Column("Amount2Signed"));
Property(x => x.ValSpot, map => map.Column("Val_Spot"));
Property(x => x.SpotDelta, map => map.Column("SpotDelta"));
Property(x => x.TradeRevalCurr, map => map.Column("Traderevalcurr"));
Property(x => x.SourceReport, map => map.Column("SourceReport"));
ManyToOne(x => x.RunContext, map => map.Column("RunContextID"));
Table("Staging.vw_Liquidity");
}
Report Entity
public class BusinessBreakdownStandardPosition : ReportRunDataEntity, IEntity<long>
{
public virtual long Id { get; set; }
public virtual decimal FinalNettingAmountUSD { get; set; }
public virtual decimal InitialChargeAmountUSD { get; set; }
public virtual BusinessBreakdownInitialPrr InitialPrr { get; set; }
public virtual IEnumerable<FinalInstrumentPosition> FinalInstrumentPositions { get; set; }
public virtual decimal CreditEventPaymentUSD { get; set; }
public virtual decimal ValuationChangeIncreaseUSD { get; set; }
public virtual decimal ValuationChangeDecreaseUSD { get; set; }
public virtual string ReportKey { get; set; }
public virtual decimal USDCharge { get; set; }
public virtual decimal USDChargeICG { get; set; }
public virtual string InstrumentType { get; set; }
}
Report Entity Mapping
public class BusinessBreakdownStandardPositionMap : ClassMapping<BusinessBreakdownStandardPosition>
{
public BusinessBreakdownStandardPositionMap()
{
Id(x => x.Id,
m =>
{
m.Column("BusinessBreakdownStandardPositionID");
m.Generator(Generators.HighLow,
g =>
g.Params(
new
{
table = "dbo.HiValue",
max_lo = 10000,
Where = string.Format("EntityName = 'BusinessBreakdownStandardPosition'")
}));
});
Property(x => x.FinalNettingAmountUSD, map => map.Column("FinalNettingAmountUSD"));
Property(x => x.InitialChargeAmountUSD, map => map.Column("InitialAmountUSD"));
Property(x => x.CreditEventPaymentUSD);
Property(x => x.ValuationChangeDecreaseUSD);
Property(x => x.ValuationChangeIncreaseUSD);
Property(x => x.USDCharge);
Property(x => x.USDChargeICG);
Property(x=>x.InstrumentType);
ManyToOne(p => p.RunContext, map => map.Column("ReportRunContextID"));
ManyToOne(p => p.InitialPrr, m =>
{
m.Column("InitialPrrID");
m.Cascade(Cascade.All);
});
Property(x => x.ReportKey);
Bag(x => x.FinalInstrumentPositions, collectionMapping =>
{
collectionMapping.Table("Reporting.BusinessBreakdownFinalInstrumentPositionStandardPositionMap");
collectionMapping.Cascade(Cascade.All);
collectionMapping.Key(k => k.Column("StandardPositionID"));
}, mapping => mapping.ManyToMany(y => y.Column("FinalInstrumentPositionID")));
Table("Reporting.BusinessBreakdownStandardPosition");
}
}
SQL Query, Generated By NHibernate
SELECT
this_.RowId AS RowId47_0_,
this_.IdentID AS IdentID47_0_,
this_.Portfolio AS Portfolio47_0_,
this_.ProfitCentre AS ProfitCe4_47_0_,
this_.Con_Expiry AS Con5_47_0_,
this_.BS AS BS47_0_,
this_.Curr1 AS Curr7_47_0_,
this_.Curr2 AS Curr8_47_0_,
this_.Reval AS Reval47_0_,
this_.ContractType AS Contrac10_47_0_,
this_.ContractType2 AS Contrac11_47_0_,
this_.ContractCode AS Contrac12_47_0_,
this_.AmountSignedTradeUnit AS AmountS13_47_0_,
this_.Amount2Signed AS Amount14_47_0_,
this_.Val_Spot AS Val15_47_0_,
this_.SpotDelta AS SpotDelta47_0_,
this_.InitialAmountUSD AS Initial17_47_0_,
this_.RunContextID AS RunCont18_47_0_,
this_.SourceReport AS Sou19_47_0_
FROM Staging.vw_Liquidity this_
Exception
System.Data.SqlClient.SqlException (0x80131904): Invalid column name 'InitialAmountUSD'.
As you can see, nhibernate has replaced the LiquiditySourceItem column 'Traderevalcurr' with 'InitialAmountUSD', which belongs to the BusinessBreakdownStandardPosition entity. These entities have no relationship whatsoever. Otherwise, the SQL is exactly as you'd expect( including column order).
Observations
The wrong column is always a valid column in a different mapped entity
The wrong column will replace an existing one
The issue sometimes ocurrs between other entities. Again, there's no relationship between these
Any thoughts?
I asked the same question on the NHibernate Users Google Groups forum, and someone thinks they have worked out the root cause (and have also proposed a solution):
https://groups.google.com/forum/#!topic/nhusers/BZoBoyWQEvs
The problem code is in PropertyPath.Equals(PropertyPath) which attempts to determine equality by only using the hash code. This works fine for smaller code bases as the default Object.GetHashCode() returns a sequential object index. However, after garbage collection, these indices get reused as finalized objects are removed and new objects are created...which results in more than one object getting the same hashcode...Once garbage collection kicks in, property paths have a chance to share the same hashcode which means they will ultimately mix up their customizers for the colliding properties, thus the wrong column names...
If you want to fix this the bug, you can patch the NH source code:
If you have your own copy of the NH source, you can fix the bug by changing NHibernate/Mapping/ByCode/PropertyPath.cs line #66 from:
return hashCode == other.GetHashCode();
To:
return hashCode == other.GetHashCode() && ToString() == other.ToString();
Please check out the Google Group for full details of the issue.
Related
I'm having a problem with NHibernate querying the database way too many times. I just realized it likely relates to the n+1 problem but I can't figure out how to change my mappings to solve the problem.
As you will see, my attempts involve specifying not to lazy load other objects, but it doesn't seem to do the trick.
This is the query:
public IQueryable<Report> ReadAll(DateTime since)
{
return m_session.QueryOver<Report>()
.JoinQueryOver(r => r.Mail)
.Where(m => m.Received >= since)
.List()
.AsQueryable();
}
Thanks in advance for any response! If you need any more information about my objects or mappings, please let me know.
Simplified objects graph (some omitted):
public class Report : EntityBase
{
public virtual Product Product { get; set; }
public virtual StackTrace StackTrace { get; set; }
public virtual Mail Mail { get; set; }
public virtual IList<ClientUser> ReadBy { get; set; }
}
-
public class Product : EntityBase
{
public virtual string Name { get; set; }
public virtual Version Version { get; set; }
public virtual IList<Report> Reports { get; set; }
public virtual IList<StackTrace> StackTraces { get; set; }
}
-
public class StackTrace : EntityBase
{
public virtual IList<StackTraceEntry> Entries { get; set; }
public virtual IList<Report> Reports { get; set; }
public virtual Product Product { get; set; }
}
Mapping examples:
public class ReportMap : ClassMap<Report>
{
public ReportMap()
{
Table("Report");
References(x => x.User)
.Column("EndUserId")
.Not.LazyLoad();
References(x => x.Product)
.Column("ProductId")
.Not.LazyLoad();
References(x => x.StackTrace)
.Column("StackTraceId")
.Not.LazyLoad();
HasManyToMany(x => x.ReadBy)
.Cascade.SaveUpdate()
.Table("ClientUserRead")
.ParentKeyColumn("ReportId")
.ChildKeyColumn("ClientUserId")
.Not.LazyLoad().BatchSize(200);
}
}
-
public class StackTraceMap : ClassMap<StackTrace>
{
public StackTraceMap()
{
Table("StackTrace");
References(x => x.Product)
.Column("ProductId");
HasMany(x => x.Entries)
.KeyColumn("StackTraceId")
.Not.LazyLoad()
.Cascade
.All().BatchSize(500);
HasMany(x => x.Reports)
.KeyColumn("StackTraceId")
.Inverse().BatchSize(100);
}
}
The way to go is to use batch fetching. Read more about it here:
How to Eager Load Associations without duplication in NHibernate?
On every entity mapping apply BatchSize (for many-to-one relation - avoiding 1 + N)
public ReportMap()
{
Table(...)
BatchSize(25);
...
And on every collection (solves issue with one-to-many 1 + N)
HasMany(x => x.Reports)
.KeyColumn("StackTraceId")
.BatchSize(25)
...
You can specify fetch paths in the query. For example, this fetch path can tell the query to eagerly join product and main objects, as well as an imaginary association on the collection of clients:
public IQueryable<Report> ReadAll(DateTime since)
{
return m_session.QueryOver<Report>()
.JoinQueryOver(r => r.Mail)
.Where(m => m.Received >= since)
.Fetch(x => x.Product).Eager
.Fetch(x => x.Mail).Eager
.Fetch(x => x.ReadBy.First().SomeProp).Eager
.List()
.AsQueryable();
}
If you want that always to happen, try using .Fetch.Join() instead of .Not.LazyLoad() for the assocations. But I would not recommend that because it can cause simple queries to become huge. Batching or subqueries can help too.
I am having a real problem with NHibernate Mapping By Code and a Composite Key in one of my classes.
The Domain class is as follows:-
public partial class UserRole {
public virtual int UserId { get; set; }
public virtual int RoleId { get; set; }
//public virtual UserRoleId Id { get; set; }
public virtual User User { get; set; }
public virtual Role Role { get; set; }
[NotNullNotEmpty]
public virtual DateTime VqsCreateDate { get; set; }
public virtual DateTime? VqsUpdateDate { get; set; }
[NotNullNotEmpty]
public virtual bool VqsMarkedForDelete { get; set; }
[Length(50)]
public virtual string VqsCreateUserName { get; set; }
[Length(50)]
public virtual string VqsLastUpdatedUserName { get; set; }
[NotNullNotEmpty]
[Length(128)]
public virtual string VqsCreateSessionId { get; set; }
[Length(128)]
public virtual string VqsLastUpdatedSessionId { get; set; }
[NotNullNotEmpty]
[Length(15)]
public virtual string VqsCreateIpAddress { get; set; }
[Length(15)]
public virtual string VqsLastUpdatedIpAddress { get; set; }
[Length(255)]
public virtual string VqsCreateSessionInfo { get; set; }
[Length(255)]
public virtual string VqsLastUpdatedSessionInfo { get; set; }
[NotNullNotEmpty]
public virtual bool VqsAllowDelete { get; set; }
public virtual bool? VqsAllowEdit { get; set; }
[Length(50)]
public virtual string VqsRffu1 { get; set; }
[Length(50)]
public virtual string VqsRffu2 { get; set; }
[Length(50)]
public virtual string VqsRffu3 { get; set; }
#region NHibernate Composite Key Requirements
public override bool Equals(object obj) {
if (obj == null) return false;
var t = obj as UserRole;
if (t == null) return false;
if (UserId == t.UserId
&& RoleId == t.RoleId)
return true;
return false;
}
public override int GetHashCode() {
int hash = GetType().GetHashCode();
hash = (hash * 397) ^ UserId.GetHashCode();
hash = (hash * 397) ^ RoleId.GetHashCode();
return hash;
}
#endregion
}
And the mapping class is as follows:-
public partial class UserRoleMap : ClassMapping<UserRole> {
public UserRoleMap() {
Schema("Membership");
Lazy(true);
ComposedId(compId =>
{
compId.Property(x => x.UserId, m => m.Column("UserId"));
compId.Property(x => x.RoleId, m => m.Column("RoleId"));
});
Property(x => x.VqsCreateDate, map => map.NotNullable(true));
Property(x => x.VqsUpdateDate);
Property(x => x.VqsMarkedForDelete, map => map.NotNullable(true));
Property(x => x.VqsCreateUserName, map => map.Length(50));
Property(x => x.VqsLastUpdatedUserName, map => map.Length(50));
Property(x => x.VqsCreateSessionId, map => { map.NotNullable(true); map.Length(128); });
Property(x => x.VqsLastUpdatedSessionId, map => map.Length(128));
Property(x => x.VqsCreateIpAddress, map => { map.NotNullable(true); map.Length(15); });
Property(x => x.VqsLastUpdatedIpAddress, map => map.Length(15));
Property(x => x.VqsCreateSessionInfo, map => map.Length(255));
Property(x => x.VqsLastUpdatedSessionInfo, map => map.Length(255));
Property(x => x.VqsAllowDelete, map => map.NotNullable(true));
Property(x => x.VqsAllowEdit);
Property(x => x.VqsRffu1, map => map.Length(50));
Property(x => x.VqsRffu2, map => map.Length(50));
Property(x => x.VqsRffu3, map => map.Length(50));
ManyToOne(x => x.User, map =>
{
map.Column("UserId");
////map.PropertyRef("Id");
map.Cascade(Cascade.None);
});
ManyToOne(x => x.Role, map =>
{
map.Column("RoleId");
////map.PropertyRef("Id");
map.Cascade(Cascade.None);
});
}
}
Whenever I try to insert into this table I get the following error:-
Invalid index for this SqlParameterCollection with Count 'N'
I have looked at all the occurrences of this error on StackExchange and across the majority of the internet for the last day and am still no further on.
There seem to be a lot of examples for Xml and Fluent mapping, but very little for Mapping By Code.
I think I have tried every combination of suggestions, but all to no avail.
I am fully aware that the problem is related to the fact that I have the ID fields in the table referenced twice and this is ultimately causing the error,
The problem is that I need both the ID and Entity Fields in my class as they are used extensively throughout the code.
I seem to be experiencing the issue as I have the combination of a composite and foreign keys in my many to many table.
Any help that anyone could provide to preserve my sanity would be very much appreciated.
Many thanks in advance.
Simon
You can set Insert(false) and Update(false) to prevent Nhibernate from generating an update or insert statement which includes the User and Role columns (which do not exist).
ManyToOne(x => x.User, map =>
{
map.Column("UserId");
////map.PropertyRef("Id");
map.Cascade(Cascade.None);
map.Insert(false);
map.Update(false);
});
ManyToOne(x => x.Role, map =>
{
map.Column("RoleId");
////map.PropertyRef("Id");
map.Cascade(Cascade.None);
map.Insert(false);
map.Update(false);
});
That's the same as Not.Update() Not.Insert() in Fluent mapping.
If you now create a valid UserRole object and set bot IDs to valid user/role ID NHibernate should be able to persist your object.
Using NHibernate 3.2 ByCode configuration, I am attempting to map the following hierarchical entity:
public class BusinessType
{
public virtual Guid BusinessTypeId { get; set; }
public virtual Guid? ParentBusinessTypeId { get; set; }
public virtual String BusinessTypeName { get; set; }
public virtual ICollection<BusinessType> Children { get; set; }
}
with this ClassMapping:
public class BusinessTypeMapper : ClassMapping<BusinessType>
{
public BusinessTypeMapper()
{
Id(x => x.BusinessTypeId, x => x.Type(new GuidType()));
Property(x => x.ParentBusinessTypeId, x => x.Type(new GuidType()));
Property(x => x.BusinessTypeName);
Set(x => x.Children,
cm =>
{
// This works, but there is an ugly string in here
cm.Key(y => y.Column("ParentBusinessTypeId"));
cm.Inverse(true);
cm.OrderBy(bt => bt.BusinessTypeName);
cm.Lazy(CollectionLazy.NoLazy);
},
m => m.OneToMany());
}
}
This works fine, but I'd rather be able to specify the key of the relation using a lambda so that refactoring works. This seems to be available, as follows:
public class BusinessTypeMapper : ClassMapping<BusinessType>
{
public BusinessTypeMapper()
{
Id(x => x.BusinessTypeId, x => x.Type(new GuidType()));
Property(x => x.ParentBusinessTypeId, x => x.Type(new GuidType()));
Property(x => x.BusinessTypeName);
Set(x => x.Children,
cm =>
{
// This compiles and runs, but generates some other column
cm.Key(y => y.PropertyRef(bt => bt.ParentBusinessTypeId));
cm.Inverse(true);
cm.OrderBy(bt => bt.BusinessTypeName);
cm.Lazy(CollectionLazy.NoLazy);
},
m => m.OneToMany());
}
}
The problem is that this causes NHibernate to generate a column called businesstype_key, ignoring the already-configured ParentBusinessTypeId. Is there any way to make NHibernate use a lambda to specify the relation, rather than a string?
I never need to navigate from children to parents, only from parents
to children, so I hadn't thought it necessary
then remove public virtual Guid? ParentBusinessTypeId { get; set; } completly. NH will then only create "businesstype_key" (convention) and no "ParentBusinessTypeId". if you want to change that then you have to specify your prefered columnname with cm.Key(y => y.Column("yourpreferredColumnName"));
The class:
public class SOPProcess : ISOPProcess
{
public virtual Guid Id { get; set; }
public virtual SOP SOP { get; set; }
public virtual ProcessType Type { get; set; }
public virtual SOPProcessInput Input { get; set; }
public virtual SOPProcessOutput Output { get; set; }
public virtual SOPProcessMeasures Measures { get; set; }
public virtual decimal YieldFactor { get; set; }
public virtual SOPProcess PreviousProcess { get; set; }
public virtual SOPProcess NextProcess { get; set; }
}
The Mapping:
public class SOPProcessMap : ClassMapping<SOPProcess>
{
public SOPProcessMap()
{
Id(s => s.Id, i => i.Generator(Generators.GuidComb));
Property(s => s.YieldFactor);
ManyToOne(s => s.SOP, m =>
{
m.Column("SopId");
m.Cascade(Cascade.All);
});
ManyToOne(s => s.Type, m =>
{
m.Column("ProcessTypeId");
m.Cascade(Cascade.All);
});
ManyToOne(s => s.NextProcess, m =>
{
m.Column("NextProcessId");
m.Cascade(Cascade.All);
});
ManyToOne(s => s.PreviousProcess, m =>
{
m.Column("PreviousProcessId");
m.Cascade(Cascade.All);
});
}
}
The Error:
NHibernate.MappingException: Could not determine type for: MES.ProcessManager.SOP.SOPProcess, MES.ProcessManager, for columns: NHibernate.Mapping.Column(id)
I hope it's something simple, this is my first project using the Conformist mapping, so maybe I'm just overlooking something.
From our discussion on the nhusers mailing list.
I ran across the same problems.
You haven't defined the type of relationship. See the line action => action.OneToMany()); in the mapping below.
public class SportMap : ClassMapping<Sport>
{
public SportMap()
{
Id(x => x.Id, map =>
{
map.Column("Id");
map.Generator(Generators.GuidComb);
});
Property(x => x.Name, map =>
{
map.NotNullable(true);
map.Length(50);
});
Bag(x => x.Positions, map =>
{
map.Key(k => k.Column(col => col.Name("SportId")));
map.Cascade(Cascade.All | Cascade.DeleteOrphans);
},
action => action.OneToMany());
Property(x => x.CreateDate);
Property(x => x.CreateUser);
Property(x => x.LastUpdateDate);
Property(x => x.LastUpdateUser);
}
}
It turned out that the problem was in my Set mappings in other classes. If you don't specify the action for the mapping, it throws this (misleading) error.
I have tried various approaches to mapping the following structure, but I finally admit that after a day of not getting very far, I need some help.
So the question is, how would you guys go about mapping something like this. The schema is not fixed at this point.
public abstract class BaseObject
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string Prefix { get; set; }
public virtual string Suffix { get; set; }
public virtual BaseObject Parent { get; set; }
}
public class Room : BaseObject
{
public virtual int AreaId { get; set; }
}
public class Item : BaseObject
{
public virtual string Owner { get; set; }
public virtual IList<ItemAttribute> Attributes { get; set; }
public virtual int ItemTypeId { get; set; }
}
public class Potion : Item
{
public virtual int AmountLeft { get; set; }
}
Your input is very much appreciated.
This allows you to have it all in one table... doing this from memory, so syntax might not be exact.
public class ItemMap : ClassMap<BaseObject>
{
...
WithTable("objects");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
Map(x => x.Description);
...
DiscriminateSubClassesOnColumn("Type")
.SubClass<Room>("Room", x =>
{
x.Map(r => r.AreaId);
})
.SubClass<Item>("Item", c =>
{
i.Map(x => x.Owner);
i.References(x => x.Account).LazyLoad();
HasManyToMany(x => x.Attributes)
.WithParentKeyColumn("ItemId")
.WithChildKeyColumn("AttributeId")
.WithTableName("ItemAttributes")
.LazyLoad();
});
.SubClass<Potion>("Potion", x =>
{
x.Map(p => p.AmountLeft);
})
I would probably have a table for each class - Room, Item, Potion and then do fairly standard mappings for each.
I'd like to note that in my own experiences, it is a bad idea to name your Id field in your business objects "Id"
Here's a sample with Item, assoming some data names for your table.
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
WithTable("Items");
Id(x => x.Id, "ItemId").GeneratedBy.Identity();
Map(x => x.Name);
Map(x => x.Description);
Map(x => x.Prefix);
Map(x => x.Suffix);
Map(x => x.Owner);
Map(x => x.ItemTypeId);
References<Item>(x => x.Parent, "ParentItemId");
HasManyToMany(x => x.Attributes)
.WithParentKeyColumn("ItemId")
.WithChildKeyColumn("AttributeId")
.WithTableName("ItemAttributes")
.LazyLoad();
}
}
This is more than likely not be perfect - as I'm not sure how the mapping will work with the abstract parent.