Fluent NHibernate - IndexOutOfRange - nhibernate

I've read all the posts and know that IndexOutOfRange usually happens because a column is being referenced twice. But I don't see how that's happening based on my mappings. With SHOW_SQL true in the config, I see an Insert into the Events table and then an IndexOutOfRangeException that refers to the RadioButtonQuestions table. I can't see the SQL it's trying to use that generates the exception. I tried using AutoMapping and have now switched to full ClassMap for these two classes to try to narrow down the problem.
public class RadioButtonQuestion : Entity
{
[Required]
public virtual Event Event { get; protected internal set; }
[Required]
public virtual string GroupIntroText { get; set; }
}
public class Event : Entity
{
[Required]
public virtual string Title { get; set; }
[Required]
public virtual DateTime EventDate { get; set; }
public virtual IList<RadioButtonQuestions> RadioButtonQuestions { get; protected internal set; }
}
public class RadioButtonQuestionMap : ClassMap<RadioButtonQuestion>
{
public RadioButtonQuestionMap()
{
Table("RadioButtonQuestions");
Id(x => x.Id).Column("RadioButtonQuestionId").GeneratedBy.Identity();
Map(x => x.GroupIntroText);
References(x => x.Event).Not.Nullable();
}
}
public class EventMap : ClassMap<Event>
{
public EventMap()
{
Id(x => x.Id).Column("EventId").GeneratedBy.Identity();
Map(x => x.EventDate);
Map(x => x.Title);
HasMany(x => x.RadioButtonQuestions).AsList(x => x.Column("ListIndex")).KeyColumn("EventId").Not.Inverse().Cascade.AllDeleteOrphan().Not.KeyNullable();
}
}
The generated SQL looks correct:
create table Events (
EventId INT IDENTITY NOT NULL,
EventDate DATETIME not null,
Title NVARCHAR(255) not null,
primary key (EventId)
)
create table RadioButtonQuestions (
RadioButtonQuestionId INT IDENTITY NOT NULL,
GroupIntroText NVARCHAR(255) not null,
EventId INT not null,
ListIndex INT null,
primary key (RadioButtonQuestionId)
)
This is using NH 3.3.0.4000 and FNH 1.3.0.727. When I try to save a new Event (with a RadioButtonQuestion attached) I see
NHibernate: INSERT INTO Events (EventDate, Title) VALUES (#p0, #p1);#p0 = 5/21/2012 12:32:11 PM [Type: DateTime (0)], #p1 = 'My Test Event' [Type: String (0)]
NHibernate: select ##IDENTITY
Events.Tests.Events.Tasks.EventTasksTests.CanCreateEvent:
NHibernate.PropertyValueException : Error dehydrating property value for Events.Domain.RadioButtonQuestion._Events.Domain.Event.RadioButtonQuestionsIndexBackref
----> System.IndexOutOfRangeException : An SqlCeParameter with ParameterIndex '3' is not contained by this SqlCeParameterCollection.
So if a column really is being referenced twice, what's the problem with my FNH config that's causing that behavior? I'm trying for a bidirection relationship (One Event Has Many Radio Button Questions) with ordering (I'll maintain it since NH won't in a bidir relationship, from what I've read). FWIW I also tried this as a unidirectional relationship by removing the Event from RadioButtonQuestion and it still caused the same exception.

I am using mapping in code (NH 3.3.1) and I have noticed that adding Update(false) and Insert(false) cures the problem:
ManyToOne(x => x.DictionaryEntity, map =>
{
map.Column("Dictionary");
map.Update(false);
map.Insert(false);
map.Cascade(Cascade.None);
map.Fetch(FetchKind.Select);
map.NotFound(NotFoundMode.Exception);
map.Lazy(LazyRelation.Proxy);
});

You have a bidirectional association, so one side should be marked as Inverse() and that can only be the RadioButtonQuestions collection. If you want the collection to be the owner, you have to remove the reference to the event in your RadioButtonQuestion class.
Additionally, the EventId column in the table RadioButtonQuestions is not nullable, which can cause problems, if the collection mapping is not inverse. See the note in the documentation.

I just spent a morning rooting this error out. The IndexOutOfRangeException sent me down the wrong path initially, but I've found the cause.
My problem concerned a FluentNHibernate class map that uses several components; the issue was that two properties were inadvertedly and incorrectly mapped to one and the same column:
before:
// example is stripped for simplicity, note the column names
Component(mappedClass => mappedClass.MappedComponent1,
map =>
{
map.Map(c => c.SomeProperty, "samecolumn");
});
Component(mappedClass => mappedClass.MappedComponent2,
map =>
{
map.Map(c => c.OtherProperty, "samecolumn");
});
after:
Component(mappedClass => mappedClass.MappedComponent1,
map =>
{
map.Map(c => c.SomeProperty, "firstcolumn");
});
Component(mappedClass => mappedClass.MappedComponent2,
map =>
{
map.Map(c => c.OtherProperty, "secondcolumn");
});
How this results in an IndexOutOfRangeException isn't obvious to me; I'm guessing that there's an array of mapped (source) properties and an array of destination columns, and in this case the destination array is too short for the number of items in the source properties array, because some of the destination columns are identical.
I think but it's worth writing a pull request for FluentNHibernate to check for this and throw a more explicit exception.

Related

Mapping-By-Code ignores Column Name in BagPropertyMapper

I'm encountering a problem, and I think it's an NHibernate defect.
My Database Schema, containing a simple Parent-Child mapping:
TABLE Company
(
ID BIGINT PRIMARY KEY
)
TABLE CompanyMailRecipient
(
ID BIGINT PRIMARY KEY IDENTITY(1, 1),
Company_ID BIGINT NOT NULL FOREIGN KEY REFERENCES Company(id),
Name VARCHAR(MAX),
EmailAddress VARCHAR(MAX),
DestinationType TINYINT
)
My classes. Note that the CompanyMailRecipient table has a column called EmailAddress, but my MailRecipient class has a column called Address.
public enum MessageDestinationType
{
Normal = 1,
CC = 2,
BCC = 3
}
public class MailRecipient
{
public virtual string Name {get; set }
public virtual string Address {get; set; } // Different name to the column!
public virtual MessageDestinationType DestinationType {get; set;}
}
public class MailConfiguration
{
private Lazy<IList<MailRecipient>> _recipients = new Lazy<IList<MailRecipient>>(() => new List<MailRecipient>());
public virtual IList<MailRecipient> Recipients
{
get
{
return _recipients.Value;
}
set
{
_recipients = new Lazy<IList<MailRecipient>>(() => value);
}
}
}
public class Company
{
public virtual long Id { get; set; }
public virtual MailConfiguration MailConfiguration { get; set; }
}
The mapping code
mapper.Class<Company>(
classMapper =>
{
classMapper.Table("Company");
classMapper.Component(
company => company.MailConfiguration,
componentMapper =>
{
componentMapper.Bag(mc => mc.Recipients,
bagPropertyMapper =>
{
bagPropertyMapper.Table("CompanyMailRecipient");
bagPropertyMapper.Key(mrKeyMapper =>
{
mrKeyMapper.Column("Company_Id");
});
},
r => r.Component(
mrc =>
{
mrc.Property
(
mr => mr.Name,
mrpm => mrpm.Column("Name")
);
/*****************************/
/* Here's the important bit */
/*****************************/
mrc.Property
(
mr => mr.Address,
mrpm => mrpm.Column("EmailAddress");
);
mrc.Property
(
mr => mr.DestinationType,
mrpm => mrpm.Column("DestinationType")
);
};
)
);
}
}
Now here's the problem: when I attempt to query a Company, I get the following error (with significant parts in bold)
NHibernate.Exceptions.GenericADOException : could not initialize a collection: [Kiosk.Server.Entities.Company.MailConfiguration.Recipients#576][SQL: SELECT recipients0_.Company_Id as Company1_0_, recipients0_.Name as Name0_, recipients0_.Address as Address0_, recipients0_.DestinationType as Destinat4_0_ FROM CompanyMailRecipient recipients0_ WHERE recipients0_.Company_Id=?]
----> System.Data.SqlClient.SqlException : Invalid column name 'Address'.
at NHibernate.Loader.Loader.LoadCollection(ISessionImplementor session, Object id, IType type)
But, if I change my C# code so my MailRecipient class has a propery called EmailAddress instead of Address, everything works.
It's like NHibernate is ignoring my column mapping.
Is this an NHibernate bug, or am I missing something?
The version of NHibernate I'm using is 4.0.4.4.
The example above has a one-to-many component hanging off a component that hangs off the entity.
I found that if I removed a layer of inderection, and had my one-to-many component hanging off the entity directly, then the column name takes effect.
Yes, this was indeed a bug in NHibernate.
I issued a fix as a pull request which has now been merged into the codebase. It should be in a release after 4.1.1.
Bug NH-3913
GitHub Commit

Nhibernate query for items that have a Dictionary Property containing value

I need a way to query in Nhibernate for items that have a Dictionary Property containing value.
Assume:
public class Item
{
public virtual IDictionary<int, string> DictionaryProperty {get; set;}
}
and mapping:
public ItemMap()
{
HasMany(x => x.DictionaryProperty)
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
.AsMap<string>(
index => index.Column("IDNumber").Type<int>(),
element => element.Column("TextField").Type<string>().Length(666)
)
.Cascade.AllDeleteOrphan()
.Fetch.Join();
}
I want to query all Items that have a dictionary value of "SomeText". The following example in Linq fails:
session.Query<Item>().Where(r => r.DictionaryProperty.Any(g => g.Value == "SomeText"))
with error
cannot dereference scalar collection element: Value
So is there any way to achieve that in NHibernate? Linq is not an exclusive requirement but its preffered. Not that I'm not interested to query over dictionary keys that can be achieved using .ContainsKey . Φορ this is similar but not the same
Handling IDictionary<TValueType, TValueType> would usually bring more issues than advantages. One way, workaround, is to introduce a new object (I will call it MyWrapper) with properties Key and Value (just an example naming).
This way we have to 1) create new object (MyWrapper), 2) adjust the mapping and that's it. No other changes... so the original stuff (mapping, properties) will work, because we would use different (readonly) property for querying
public class MyWrapper
{
public virtual int Key { get; set; }
public virtual string Value { get; set; }
}
The Item now has
public class Item
{
// keep the existing for Insert/Updae
public virtual IDictionary<int, string> DictionaryProperty {get; set;}
// map it
private IList<MyWrapper> _properties = new List<MyWrapper>();
// publish it as readonly
public virtual IEnumerable<MyWrapper> Properties
{
get { return new ReadOnlyCollection<MyWrapper>(_properties); }
}
}
Now we will extend the mapping:
HasMany(x => x.Properties)
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
.Component(c =>
{
c.Map(x => x.Key).Column("IDNumber")
c.Map(x => x.Value).Column("TextField")
})
...
;
And the Query, which will work as expected:
session
.Query<Item>()
.Where(r =>
r.Properties.Any(g => g.Value == "SomeText")
)
NOTE: From my experience, this workaround should not be workaround. It is preferred way. NHibernate supports lot of features, but working with Objects brings more profit

Unidirectional one-to-many NHibernate mapping failing when using FluentNHibernate PersistenceSpecification

I have a domain model where a Order has many LineItems. When I create a new Order (with new LineItems) and use PersistenceSpecification to test the mapping, NHibernate throws a PropertyValueException:
var order = new Order() { LineItems = new List<LineItem>() };
order.LineItems.Add(new LineItem());
new PersistenceSpecification<Order>(session)
.CheckList(o => o.LineItems, order.LineItems) // PropertyValueException
.VerifyTheMappings();
NHibernate.PropertyValueException: not-null property references a null or transient value LineItem._Order.LineItemsBackref
Domain model
public class Order {
public virtual Guid Id { get; set; }
public virtual ICollection<LineItem> LineItems { get; set; }
[...]
}
public class LineItem {
public virtual Guid Id { get; set; }
[...]
}
A LineItem on its own is not interesting, and they will never appear without a Order, so the relationship is unidirectional.
Fluent Mappings/Schema
// OrderMap.cs
Id(x => x.Id).GeneratedBy.GuidComb();
HasMany(x => x.LineItems)
.Not.Inverse()
.Not.KeyNullable()
.Not.KeyUpdate()
.Cascade.AllDeleteOrphan();
// LineItemMap.cs
Id(x => x.Id).GeneratedBy.GuidComb();
// Schema
CREATE TABLE Orders ( Id uniqueidentifier NOT NULL, /* ... */ )
CREATE TABLE LineItems ( Id uniqueidentifier NOT NULL,
OrderId uniqueidentifier NOT NULL, /* ... */ )
The foreign key column in the LineItems table is not nullable, so based on the information in this question I specified Not.KeyNullable() and Not.Inverse() to prevent NHibernate from attempting to insert a LineItem with a NULL Id.
I'm using NHibernate 3.3.2.400 and FluentNHibernate 1.3.0.733 (the current latest versions from NuGet).
This occurs because the CheckList() method tries to save each item in the list as soon as you call it. At this point, the parent entity hasn't been saved yet -- That doesn't happen until you call VerifyTheMappings().
Since the relationship is unidirectional, a child entity (LineItem) can't be persisted unless it is part of a parent (Order), and the exception is thrown. (GitHub issue)
I don't have a solution for this yet other than "don't bother testing the list mapping".

Set identity seed in fluentnhibernate

Using NHibernate you can set an identity seed like so:
<column name="Column1" not-null="true" sql-type="int IDENTITY(1,1000)"/>
The FluentNHibernate IdentityPart has CustomType and SqlCustomType methods, neither does it for me though. Is there a way to fluently set an identity seed?
More info:
When I do this: Map(x => x.Id).Column("CustomerId").CustomSqlType("int IDENTITY(1,1000)");
I get this error: The entity 'Customer' doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id).
When I do this: Id(x => x.Id).Column("CustomerId").CustomSqlType("int IDENTITY(1,1000)");
I get this error: More than one column IDENTITY constraint specified for column 'CustomerId', table 'Customer'
Using FluentNHibernate 1.2.0.712.
I was able to duplicate that xml by doing something like this:
Map(x => x.LoginName, "Column1").CustomSqlType("int IDENTITY(1,1000)");
Edit:
If you can't achieve what you are wanting maybe you should explicitly map this using xml for now.
There is the article at the link below about implementing custom identity generator (see: Part 1: Inheriting from TableGenerator class) but the example throws the exception for SQLite database ("SQLite errorr no such table: hibernate_unique_key"). Thus as regard SQLite there is no possibility to gain current id key from a table. It uses class TableGenerator from NHibernate API (NHibernate.Id);
http://nhforge.org/wikis/howtonh/creating-a-custom-id-generator-for-nhibernate.aspx
To avoid the exception I implemented another solution (especially the way of getting current Id). It takes advantage of Fluent-NHibernate API (GeneratedBy.Custom()). Look at the following source code:
public class MyAutoincrement<T> : IIdentifierGenerator where T : IId
{
#region IIdentifierGenerator Members
public object Generate(ISessionImplementor session, object obj)
{
NHibernate.ISession s = (NHibernate.ISession)session;
int seedValue = 1000;
int maxId = -1;//start autoincrement from zero! (fluent nhibernate start from 1 as default)
List<T> recs = s.Query<T>().ToList<T>();
if (recs.Count > 0)
{
maxId = recs.Max(x => x.getId());
}
return seedValue + maxId + 1;
}
#endregion
}
//Interface for access to current Id of table
public interface IId
{
int getId();
}
//Entity
public class MyEntity : IId
{
public virtual int Id { get; protected set; }
public virtual string MyField1 { get; set; }
public virtual string MyField2 { get; set; }
#region IId Members
public virtual int getId()
{
return this.Id;
}
#endregion
}
//Entity Mapping
public class MyEntityMap : ClassMap<MyEntity>
{
public MyEntityMap()
{
Id(x => x.Id).GeneratedBy.Custom<MyAutoincrement<MyEntity>>();
Map(x => x.MyField1);
Map(x => x.MyField1);
}
}
It works with SQLite database and involves custom identity seed.
Regards
Bronek

NHibernate, SQL Server - enum to int mapping

I have a class that has an enum type indicating whether the message type is Email or Sms. The enum type is defined:
public enum ReminderType
{
Email = 1,
Sms = 2
}
The class that utilizes this type looks like:
public class Reminder : EntityBase
{
public virtual string Origin { get; set; }
public virtual string Recipient { get; set; }
public virtual ReminderType Type { get; set; }
public virtual Business Business { get; set; }
public virtual DateTime Created { get; set; }
public Reminder()
{
Created = DateTime.UtcNow;
}
}
When I try to persist an entity of type Reminder to the database however, I get the following error:
System.Data.SqlClient.SqlException (0x80131904): Conversion failed when converting the nvarchar value 'Email' to data type int.
The backing field is of type int, so I'm not sure why NHibernate is trying to map the string representation by default. I'm using Fluent NHibernate, and the relevant mapping code is:
mappings.Override<Reminder>(map =>
{
map.Map(x => x.Type).Column("Type")
});
I'm pretty sure the default behavior of NHibernate is to map enums as ints, so why is it not doing so in this case? I'm using SQL Server 2005, if that matters.
I am doing the same thing and got it working like so...
In my case EmployeeType is the enum class
Map(x => x.EmployeeType, "EmployeeType_Id").CustomType(typeof (EmployeeType));
I don't know why this person keeps posting and then deleting their comment or answer, but the link they provided () does answer my question. I opted not to go with a full blow class definition for the convention, but rather, an inline convention in the mappings code, like so:
var mappings = AutoMap.AssemblyOf<Business>()
.Where(x => x.IsSubclassOf(typeof(EntityBase)))
.IgnoreBase(typeof(EntityBase))
.Conventions.Add
(
ConventionBuilder.Id.Always(x => x.GeneratedBy.Identity()),
ConventionBuilder.HasMany.Always(x => x.Cascade.All()),
ConventionBuilder.Property.Always(x => x.Column(x.Property.Name)),
Table.Is(o => Inflector.Pluralize(o.EntityType.Name)),
PrimaryKey.Name.Is(o => "Id"),
ForeignKey.EndsWith("Id"),
DefaultLazy.Always(),
DefaultCascade.All(),
ConventionBuilder.Property.When(
c => c.Expect(x => x.Property.PropertyType.IsEnum),
x => x.CustomType(x.Property.PropertyType))
);
The last convention builder statement did the trick. I'm curious as to why Fluent NHibernate's default is to map enums as strings now. That doesn't seem to make much sense.
You should never map Enum as int in NHibernate. It becomes a reason of having a ghost updates.
The best way to it is just not setting a type property in XML mappings. To achieve that in Fluent NHibernate you can use .CustomType(string.Empty).
Some additional info you can find here.