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.
Related
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.
I'm experiencing an odd problem with FluentNHibernate: when I save my entity, one of the (reference) properties is not updated. Other properties, both fields and references, are updated, and the failing property is correctly mapped (retrieving entities works like a charm).
A (slightly simplified) description of what I'm doing:
Into my MVC action method, an InputModel is bound and set. It has a property for the TypeID, where I wish to set the Type of my entity (let's call the entity type Thing).
A new Thing object is created, and the simple properties of the InputModel is copied over. For a couple of complex properties, among them the Type property which isn't working and another property which is, the following is done:
2.1. The correct ThingType is fetched from the repository, based on the provided type id.
2.2. The type is set (using thing.Type = theType) on the new Thing object.
The Thing that I want to update is fetched from the repository, based on the id on the input model (not the same id as the TypeID).
All properties, complex and other, are copied over from the new thing (created by me) to the original one (fetched from db).
The original Thing is saved, using session.Save();.
As stated above, it's only one property that isn't working - other properties, following (as far as I can tell) the exact same pattern, work. I've also debugged and verified that the original Thing has the correct, updated Type when it is passed to session.Save().
I have no idea where to start troubleshooting this...
Update: The classes are plain POCOs:
public class Thing
{
public int ID { get; set; }
public string SomeSimpleProp { get; set; }
public ThingType Type { get; set; }
public OtherEntity OtherReference { get; set; }
}
public class ThingType
{
public int ID { get; set; }
public string Name { get; set; }
}
My exact mappings (except for the names of types and properties) are these:
// In ThingMap : ClassMap<Thing> constructor:
Id(t => t.ID).Column("ThingID");
Map(t => t.SomeSimpleProp);
References(t => t.Type).Column("ThingTypeID");
References(t => t.OtherReference).Column("OtherReferenceID");
// In ThingTypeMap : ClassMap<ThingType> constructor:
Id(t => t.ID).Column("ThingTypeID");
Map(t => t.Name);
As I said, OtherReference is updated correctly while Type is not. They are mapped identically, so I don't see how this could be a mapping error.
You should specify <many-to-one .... cascade="save-update"/> in order to update references.
Is there any know issues with lazy loading a property whose type is a byte[]? I've got an image column on a sql server 2008 db and I'm trying to map it into a byte[] property of a class. Since I don't want to load it always, I've configured it so that it uses lazy loading. Here's the code:
public class Documento : Entity, IHasAssignedId<Int32> {
public virtual Byte[] Bytes { get; private set; }
//...more properties
}
Here's the mapping:
Map( doc => doc.Bytes, "Documento" )
.CustomSqlType( "image" )
.CustomType<Byte[]>( )
.LazyLoad( )
.Not.Nullable( );
Now, the problem is that when I'm trying to check the mappings with PersistenceSpecification, I get an exception which says:
NHibernate.PropertyAccessException : Invalid Cast (check your mapping for property type mismatches); setter of Sra.Assistencias.Documento
----> System.InvalidCastException : Unable to cast object of type 'System.Object' to type 'System.Byte[]'.
If I configure the property so that it doesn't use lazy loading or if I change its type to Object (leaving the db mapping as is), everything works out ok. Any ideas?
I think the problem comes from your mapping, not the lazy loading itself. Here's I map Image types in Fluent NHibernate:
interface IEmployee
{
int Id { get; }
string Name { get; }
byte[] Image { get; set; }
}
public class EmployeeMap : ClassMap<IEmployee>
{
public EmployeeMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
Map(x => x.Image).CustomType("BinaryBlob");
}
}
Where the "Image" column is of Image type using SQL Server 2008.
I have a situation where my primary key is a char(2) in SqlServer 2008, and I want to reference it in a one-to-many relationship, but the ManyToOneBuilder (which is returned by ClassMap<>.References()) doesn't have a CustomSqlType() method. Specifically:
public class State
{
// state FIPS code is 2 characters
public virtual string StateCode { get; set; }
public virtual ICollection<County> { get; set; }
}
public class County
{
// state-county FIPS code is 5 characters
public virtual string StateCountyCode { get; set; }
public virtual State State { get; set; }
}
public class StateMap : ClassMap<State>
{
public StateMap()
{
Id(e => e.StateCode).CustomSqlType("char(2)").GeneratedBy.Assigned();
}
}
public class CountyMap : ClassMap<County>
{
public CountyMap()
{
Id(e => e.StateCountyCode).CustomSqlType("char(5)").GeneratedBy.Assigned();
References(e => e.State, "StateCode")
// Here's what I want to do, but can't because the method is not
// implemented on the class ManyToOneBuilder:
.CustomSqlType("char(2)");
}
}
Is there any way to accomplish this without modifying the ManyToOneBuilder? Is there a way to automatically map the FK (i.e. County.StateCode) to the correct type? It's trivial to add CustomSqlType to ManyToOneBuilder, but is that the right thing to do?
Keep your mapping definition as is, add your "State" column definition with
.CustomSqlType("char(2)")
and set for this column Insert=false and update=false.
I've the same problem and in AutoMapping I use this code:
mapping.Map(x => x.IdUniArticolo)
.CustomSqlType("varchar(50)")
.Not.Insert().Not.Update();
mapping.References(x => x.Articolo)
.Column("IdUniArticolo").PropertyRef("IdUniArticolo");
Keep in mind that if NHibernate itself doesn't support it, then Fluent NHibernate can't, and I don't NHibernate supports the scenario you have. I had a similar problem in that I had a 2 column composite key on a table and on one of the fields, I wanted to use an enumerated type which had a custom IUserType to translate it to its appropriate code value in the DB. Couldn't do it, so I was stuck keeping the property of the string type rather than the enumerated type.
I try to write a (fluent) mapping against an interface
public interface IOrderDiscount : IDomainObject<long>
where
public interface IDomainObject<IdT> : IDomainObject
{
IdT Id { get; }
}
like so (and all other thinkable varieties of access strategies)
Id(d => d.Id, "DiscountId")
.GeneratedBy.HiLo("9")
.WithUnsavedValue(0)
.Access.AsReadOnlyPropertyThroughCamelCaseField();
but all I get are variations of
Could not find field 'id' in class 'IOrderDiscount'
My base class implements this as
public virtual IdT Id { get; protected set; }
but event using a backing field does not change a thing.
So I am left to wonder, how I could get this to work...
Anyone with an idea?
Specify the custom column name via the Column method instead:
Id(d => d.Id)
.Column("DiscountId")
.GeneratedBy.HiLo("9")
.WithUnsavedValue(0)
.Access.AsReadOnlyPropertyThroughCamelCaseField();