Fluent nHibernate mapping problem - c#

I have these tables/classes (example):
table FirstTable (
Id INTEGER NOT NULL DEFAULT AUTOINCREMENT,
Name VARCHAR(100) NOT NULL,
Document VARCHAR(20) NOT NULL
)
table SecondTable (
Id INTEGER NOT NULL,
Something VARCHAR(100) NULL,
FOREIGN KEY (Id) REFERENCES FirstTable (Id)
)
public class FirstClass {
public string Name { get; set; }
public string Document { get; set; }
public SecondClass SecondClass { get; set; }
}
public class SecondClass {
public string Something { get; set; }
public FirstClass FirstClass { get; set; }
}
The mapping is:
public class FirstClassMap : ClassMap<FirtsClass> {
Table("FirstTable");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
Map(x => x.Document);
References(x => x.SecondClass, "Id").ForeignKey();
}
public class SecondClassMap : ClassMap<SecondClass> {
Table("SecondTable");
Id(x => x.Id).GeneratedBy.Foreign("FirstClass");
Max(x => x.Something);
HasOne(x => x.FirstClass).PropertyRef(x => x.SecondClass).Cascade.SaveUpdate();
}
FirstClass can have (0,1) SecondClass, and SecondClass can have (1,1) FirstClass.
The bellow code return the error "attempted to assign id from null one-to-one property: SecondClass"
var test = new SecondClass();
test.FirstClass = new FirstClass();
test.Something = "New test";
test.FirstClass.Name = "My name";
test.FirstClass.Document = "My document";
// ... commands to save.

It seems like NH is trying to save SecondClass first and fails to grab a generated ID from the not yet saved FirstClass.
Try to move the .Cascade.SaveUpdate() to the References declaration in FirstClassMap and call the save command on FirstClass.

Related

Entity Framework Include relations on derived types for Table per Hierarchy

How can I Include the relationships of properties on a collection where the collection type is of a Table per Hierarchy design and the relationships are defined on the derived types with lazy loading disabled?
What I am trying to avoid is to declare the relationships on the base type or to have multiple DB calls to retrieve the relationships. Is there a way around this?
Attempts and Errors
I have tried using OfType but receive this message.
System.ArgumentException - The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
I have tried using as but receive this error
System.InvalidOperationException - A specified Include path is not valid. The EntityType 'Sandbox.TPHMcve.Person' does not declare a navigation property with the name 'Buildings'.
Example
Please do not focus on the business details of this silly example, it is an mcve to illustrate the error I am receiving. My actual project has nothing to do with schools / school systems etc.
Structure
A school in a University has Persons
A Person is either Student or Teacher
Student has zero or more course books on loan
Teacher can be assigned as the care taker / guardian for one or more campus buildings
Code
SchoolService.cs
This is the actual code of interest that produces the Exceptions
public sealed class SchoolService
{
private readonly UniversityDbContext _dbContext;
public SchoolService(UniversityDbContext dbContext)
{
_dbContext = dbContext;
}
public School GetSchoolAndPeopleWithDetails_OfType(int schoolId)
{
return _dbContext.Schools.Where(x => x.SchoolId == schoolId)
.Include(x => x.EntitiesOfSchool.OfType<Teacher>().Select(y => y.Buildings))
.Include(x => x.EntitiesOfSchool.OfType<Student>().Select(y => y.BooksOnLoan))
.SingleOrDefault();
}
public School GetSchoolAndPeopleWithDetails_Cast(int schoolId)
{
return _dbContext.Schools.Where(x => x.SchoolId == schoolId)
.Include(x => x.EntitiesOfSchool.Select(y => (y as Teacher).Buildings))
.Include(x => x.EntitiesOfSchool.Select(y => (y as Student).BooksOnLoan))
.SingleOrDefault();
}
}
Entities.cs
public sealed class School
{
public int SchoolId { get; set; }
public string Name { get; set; }
public List<Person> EntitiesOfSchool { get; set; }
}
public abstract class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public School PrimarySchool { get; set; }
public int SchoolId { get; set; }
}
public sealed class Teacher : Person
{
public ICollection<Building> Buildings { get; set; }
}
public sealed class Student : Person
{
public ICollection<CourseBook> BooksOnLoan { get; set; }
}
public sealed class Building
{
public int BuildingId { get; set; }
public string Name { get; set; }
public Teacher AssignedGuardian { get; set; }
public int GuardianId { get; set; }
}
public sealed class CourseBook
{
public int CourseBookId { get; set; }
public int BookNumber { get; set; }
public Student AssignedTo { get; set; }
public int? AssignedToId { get; set; }
}
EntityMappings.cs does not include Building or CourseBook mapping types as they are not relevant
public sealed class SchoolMap : EntityTypeConfiguration<School>
{
public SchoolMap()
{
ToTable("Schools");
HasKey(x => x.SchoolId);
Property(x => x.SchoolId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.Name).IsRequired().IsUnicode(true).HasMaxLength(200);
HasMany(x => x.EntitiesOfSchool).WithRequired(x => x.PrimarySchool).HasForeignKey(person => person.SchoolId);
}
}
public sealed class PersonMap : EntityTypeConfiguration<Person>
{
public PersonMap()
{
ToTable("Persons");
HasKey(x => x.PersonId);
Property(x => x.PersonId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.Name).IsRequired().IsUnicode(true).HasMaxLength(256);
Map<Teacher>(configuration => configuration.Requires("PersonType").HasValue(1));
Map<Student>(configuration => configuration.Requires("PersonType").HasValue(2));
}
}
public sealed class TeacherMap : EntityTypeConfiguration<Teacher>
{
public TeacherMap()
{
HasMany(x => x.Buildings).WithRequired(x => x.AssignedGuardian).HasForeignKey(x => x.GuardianId);
}
}
public sealed class StudentMap : EntityTypeConfiguration<Student>
{
public StudentMap()
{
HasMany(x => x.BooksOnLoan).WithOptional(x => x.AssignedTo).HasForeignKey(x => x.AssignedToId);
}
}
UniversityDbContext.cs
public sealed class UniversityDbContext : DbContext
{
public UniversityDbContext() : base("Name=default")
{
this.Configuration.LazyLoadingEnabled = false; // disable lazy loading
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new SchoolMap());
modelBuilder.Configurations.Add(new PersonMap());
modelBuilder.Configurations.Add(new TeacherMap());
modelBuilder.Configurations.Add(new StudentMap());
}
public DbSet<School> Schools { get; set; }
}
Sql code for table DDE and seed of data.sql
CREATE TABLE [dbo].[Schools](
[SchoolId] [int] IDENTITY(1,1) NOT NULL,
[Name] Nvarchar (200) not null
CONSTRAINT [PK_Schools] PRIMARY KEY CLUSTERED
(
[SchoolId] ASC
)) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Persons](
[PersonId] [int] IDENTITY(1,1) NOT NULL,
[SchoolId] [int] not null,
[PersonType] [int] not null,
[Name] nvarchar(256) not null
CONSTRAINT [PK_Persons] PRIMARY KEY CLUSTERED
(
[PersonId] ASC
)) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Buildings](
[BuildingId] [int] IDENTITY(1,1) NOT NULL,
[Name] nvarchar(200) not null,
[GuardianId] [int] not null,
CONSTRAINT [PK_Buildings] PRIMARY KEY CLUSTERED
(
[BuildingId] ASC
)) ON [PRIMARY]
GO
CREATE TABLE [dbo].[CourseBooks](
[CourseBookId] [int] IDENTITY(1,1) NOT NULL,
[BookNumber] varchar(200) not null,
[AssignedToId] [int] null,
CONSTRAINT [PK_CourseBooks] PRIMARY KEY CLUSTERED
(
[CourseBookId] ASC
)) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Persons] WITH CHECK ADD CONSTRAINT [FK_Schools_Persons] FOREIGN KEY([SchoolId])
REFERENCES [dbo].[Schools] ([SchoolId])
ALTER TABLE [dbo].[Buildings] WITH CHECK ADD CONSTRAINT [FK_Persons_Buildings] FOREIGN KEY([GuardianId])
REFERENCES [dbo].[Persons] ([PersonId])
ALTER TABLE [dbo].[CourseBooks] WITH CHECK ADD CONSTRAINT [FK_Persons_CourseBooks] FOREIGN KEY([AssignedToId])
REFERENCES [dbo].[Persons] ([PersonId])
INSERT INTO Schools (Name) values (N'Business'), (N'Education')
INSERT INTO Persons (Name, PersonType, SchoolId) values ('Eddy',1, 1), ('Fran',1, 1), ('Joe',2, 1), ('Kim',2, 1)
INSERT INTO Buildings (Name, GuardianId) values (N'Offsite staff', 1), (N'Main Business Building', 1)
INSERT INTO CourseBooks(AssignedToId, BookNumber) values (3, 'Course A book 1'),(3, 'Course C book 31')

NHibernate Envers: Cannot insert duplicate key in object

I'm using Envers to audit tables, but it's creating some audit tables for unknown/absent tables.
It's looks like a Many To Many relation audit table for Many To One relations.
Is this right? If it's, Why?
dbo.HorarioFixo - OK
dbo.HorarioFixo_Auditoria - OK
dbo.HorarioFixo_JanelaHorarioFixo_Auditoria - NOK
dbo.JanelaHorarioFixo - OK
dbo.JanelaHorarioFixo_Auditoria - OK
But when I try to remove/delete and HorarioFixo I'm getting an error.
The error I'm getting:
NHibernate.Exceptions.GenericADOException
could not execute batch command.[SQL: SQL not available]
em NHibernate.Engine.ActionQueue.BeforeTransactionCompletionProcessQueue.BeforeTransactionCompletion()
em NHibernate.Impl.SessionImpl.BeforeTransactionCompletion(ITransaction tx)
em NHibernate.Transaction.AdoTransaction.Commit()
em Foo.Testes.Servicos.TesteCanalDeTransmissaoService.RemoveDependenciasCorretamente() na TesteCanalDeTransmissaoService.cs: line 195
System.Data.SqlClient.SqlException
Violation of PRIMARY KEY constraint 'PK__HorarioF__450088476960C81E'. Cannot insert duplicate key in object 'dbo.HorarioFixo_JanelaHorarioFixo_Auditoria'.
Violation of PRIMARY KEY constraint 'PK__HorarioF__450088476960C81E'. Cannot insert duplicate key in object 'dbo.HorarioFixo_JanelaHorarioFixo_Auditoria'.
The statement has been terminated.
The statement has been terminated.
This is the SQL duplicated:
exec sp_executesql N'INSERT INTO HorarioFixo_JanelaHorarioFixo_Auditoria (REVTYPE, REV, HorarioFixoId, JanelaHorarioFixoId) VALUES (#p0, #p1, #p2, #p3)',N'#p0 tinyint,#p1 int,#p2 bigint,#p3 bigint',#p0=2,#p1=3,#p2=1,#p3=2 go
All this is a part of the code. If you need something more, leave a comment.
My classes:
public class Entidade
{
protected Entidade();
public virtual long Id { get; set; }
public virtual long Version { get; set; }
public abstract override bool Equals(object obj);
public override int GetHashCode();
}
public class Horario : Entidade
{
protected Horario()
{
}
}
public class HorarioFixo : Horario
{
public virtual int Frequencia { get; set; }
public virtual ICollection<JanelaHorarioFixo> JanelasRemessa { get; set; }
public virtual ICollection<JanelaHorarioFixo> JanelasRetorno { get; set; }
}
public class JanelaHorarioFixo : Entidade
{
public virtual TimeSpan HorarioInicio { get; set; }
public virtual TimeSpan? HorarioLimite { get; set; }
}
My mappings:
public class HorarioMap : ClassMapping<Horario>
{
public HorarioMap()
{
Id(x => x.Id, mapper =>
{
mapper.Generator(Generators.Identity);
mapper.UnsavedValue(0);
});
}
}
public class HorarioFixoMap : JoinedSubclassMapping<HorarioFixo>
{
public HorarioFixoMap()
{
Property(x => x.Frequencia);
Bag(x => x.JanelasRemessa, m =>
{
m.Cascade(Cascade.All);
m.Lazy(CollectionLazy.NoLazy);
}, map => map.OneToMany());
Bag(x => x.JanelasRetorno, m =>
{
m.Cascade(Cascade.All);
m.Lazy(CollectionLazy.NoLazy);
}, map => map.OneToMany());
}
}
public class JanelaHorarioFixoMap : ClassMapping<JanelaHorarioFixo>
{
public JanelaHorarioFixoMap()
{
Id(x => x.Id, mapper =>
{
mapper.Generator(Generators.Identity);
mapper.UnsavedValue(0);
});
Property(x => x.HorarioInicio, m => m.NotNullable(true));
Property(x => x.HorarioLimite, m => m.NotNullable(false));
}
}
NH and Envers configurations:
var ormHelper = ORMHelperUtils.GetORMHelper();
var mapper = new MyConventionModelMapper();
_config = new Configuration();
mapper.AddMappings(ormHelper.GetMappings());
mapper.AddMapping(typeof(REVINFOMap));
ormHelper.SetupApplicationNeeds(_config);
_config.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
_config.SetProperty(Environment.CurrentSessionContextClass, "call");
if (ormHelper.UseEnvers)
{
var classesDominio = ormHelper.GetDomainTables();
if (classesDominio.Any())
{
var envers = new FluentConfiguration();
envers.Audit(classesDominio);
envers.SetRevisionEntity<REVINFO>(e => e.Id, e => e.Date, new CustomRevisionListener());
_config.SetEnversProperty(ConfigurationKey.AuditTableSuffix, "_Auditoria");
_config.IntegrateWithEnvers(envers);
}
}
I've just changed my class to
public class HorarioFixo : Horario
{
public virtual int Frequencia { get; set; }
public virtual ICollection<JanelaHorarioFixo> Janelas { get; set; }
}
And added a property to JanelaHorarioFixo to identify the type. But the table dbo.HorarioFixo_JanelaHorarioFixo_Auditoria is still there, and I don't know why.
If you use unidirectional one-to-many, Envers needs a link table to be able to have correct history.
If you use bidirectional one-to-many, no link table is needed.
See this answer.

Fluent nhibernate one to one mapping

How do I make a one to one mapping.
public class Setting
{
public virtual Guid StudentId { get; set; }
public virtual DateFilters TaskFilterOption { get; set; }
public virtual string TimeZoneId { get; set; }
public virtual string TimeZoneName { get; set; }
public virtual DateTime EndOfTerm { get; set; }
public virtual Student Student { get; set; }
}
Setting Class map:
public SettingMap()
{
// Id(Reveal.Member<Setting>("StudentId")).GeneratedBy.Foreign("StudentId");
//Id(x => x.StudentId);
Map(x => x.TaskFilterOption)
.Default(DateFilters.All.ToString())
.NvarcharWithMaxSize()
.Not.Nullable();
Map(x => x.TimeZoneId)
.NvarcharWithMaxSize()
.Not.Nullable();
Map(x => x.TimeZoneName)
.NvarcharWithMaxSize()
.Not.Nullable();
Map(x => x.EndOfTerm)
.Default("5/21/2011")
.Not.Nullable();
HasOne(x => x.Student);
}
Student Class map
public class StudentMap: ClassMap<Student>
{
public StudentMap()
{
Id(x => x.StudentId);
HasOne(x => x.Setting)
.Cascade.All();
}
}
public class Student
{
public virtual Guid StudentId { get; private set; }
public virtual Setting Setting { get; set; }
}
Now every time I try to create a settings object and save it to the database it crashes.
Setting setting = new Setting
{
TimeZoneId = viewModel.SelectedTimeZone,
TimeZoneName = info.DisplayName,
EndOfTerm = DateTime.UtcNow.AddDays(-1),
Student = student
};
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Settings_Students". The conflict occurred in database "Database", table "dbo.Students", column 'StudentId'.
The statement has been terminated.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Data.SqlClient.SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Settings_Students". The conflict occurred in database "Database", table "dbo.Students", column 'StudentId'.
The statement has been terminated.
What am I missing?
Edit
public class StudentMap: ClassMap<Student>
{
public StudentMap()
{
Id(x => x.StudentId)
.GeneratedBy.Guid();
HasOne(x => x.Setting)
.PropertyRef("Student")
.Cascade.All();
}
}
public class SettingMap: ClassMap<Setting>
{
public SettingMap()
{
Id(x => x.StudentId)
.GeneratedBy.Guid();
Map(x => x.TaskFilterOption)
.Default(DateFilters.All.ToString())
.NvarcharWithMaxSize().Not.Nullable();
Map(x => x.TimeZoneId)
.NvarcharWithMaxSize().Not.Nullable();
Map(x => x.TimeZoneName)
.NvarcharWithMaxSize().Not.Nullable();
Map(x => x.EndOfTerm)
.Default("5/21/2011").Not.Nullable();
References(x => x.Student).Unique();
}
}
Setting setting = new Setting
{
TimeZoneId = viewModel.SelectedTimeZone,
TimeZoneName = info.DisplayName,
EndOfTerm = DateTime.UtcNow.AddDays(-1),
Student = student
};
studentRepo.SaveSettings(setting);
studentRepo.Commit();
I get these error for both ways
Invalid index 5 for this SqlParameterCollection with Count=5. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.IndexOutOfRangeException: Invalid index 5 for this SqlParameterCollection with Count=5. Source Error: Line 76: using (ITransaction transaction = session.BeginTransaction()) Line 77: { Line 78: transaction.Commit(); Line 79: } Line 80: }
There are two basic ways how to map bidirectional one-to-one association in NH. Let's say the classes look like this:
public class Setting
{
public virtual Guid Id { get; set; }
public virtual Student Student { get; set; }
}
public class Student
{
public virtual Guid Id { get; set; }
public virtual Setting Setting { get; set; }
}
Setting class is a master in the association ("aggregate root"). It is quite unusual but it depends on problem domain...
Primary key association
public SettingMap()
{
Id(x => x.Id).GeneratedBy.Guid();
HasOne(x => x.Student).Cascade.All();
}
public StudentMap()
{
Id(x => x.Id).GeneratedBy.Foreign("Setting");
HasOne(x => x.Setting).Constrained();
}
and a new setting instance should be stored:
var setting = new Setting();
setting.Student = new Student();
setting.Student.Name = "student1";
setting.Student.Setting = setting;
setting.Name = "setting1";
session.Save(setting);
Foreign key association
public SettingMap()
{
Id(x => x.Id).GeneratedBy.Guid();
References(x => x.Student).Unique().Cascade.All();
}
public StudentMap()
{
Id(x => x.Id).GeneratedBy.Guid();
HasOne(x => x.Setting).Cascade.All().PropertyRef("Student");
}
Primary key association is close to your solution. Primary key association should be used only when you are absolutely sure that the association will be always one-to-one. Note that AllDeleteOrphan cascade is not supported for one-to-one in NH.
EDIT: For more details see:
http://fabiomaulo.blogspot.com/2010/03/conform-mapping-one-to-one.html
http://ayende.com/blog/3960/nhibernate-mapping-one-to-one
Here a complete sample with foreign key association
using System;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using FluentNHibernate.Mapping;
namespace NhOneToOne
{
public class Program
{
static void Main(string[] args)
{
try
{
var sessionFactory = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2005
.ConnectionString(#"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=NHTest;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False")
.ShowSql()
)
.Mappings(m => m
.FluentMappings.AddFromAssemblyOf<Program>())
.BuildSessionFactory();
ISession session = sessionFactory.OpenSession();
Parent parent = new Parent();
parent.Name = "test";
Child child = new Child();
child.Parent = parent;
parent.Child = child;
session.Save(parent);
session.Save(child);
int id = parent.Id;
session.Clear();
parent = session.Get<Parent>(id);
child = parent.Child;
}
catch (Exception e)
{
Console.Write(e.Message);
}
}
}
public class Child
{
public virtual string Name { get; set; }
public virtual int Id { get; set; }
public virtual Parent Parent { get; set; }
}
public class Parent
{
public virtual string Name { get; set; }
public virtual int Id { get; set; }
public virtual Child Child { get; set; }
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Table("ChildTable");
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name);
References(x => x.Parent).Column("IdParent");
}
}
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Table("ParentTable");
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name);
HasOne(x => x.Child).PropertyRef(nameof(Child.Parent));
}
}
}
And the SQL to create tables
CREATE TABLE [dbo].[ParentTable] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] VARCHAR (MAX) NULL
);
CREATE TABLE [dbo].[ChildTable] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[IdParent] INT NOT NULL,
[Name] VARCHAR (50) NULL
);
ALTER TABLE [dbo].[ChildTable]
ADD CONSTRAINT [FK_ChildTable_ToTable] FOREIGN KEY ([IdParent]) REFERENCES [dbo].[ParentTable] ([Id]);
First, define one of the sides of the relationship as Inverse(), otherwise there is a redundant column in the database and this may cause the problem.
If this doesn't work, output the SQL statements generated by NHibernate (using ShowSql or through log4net) and try to understand why the foreign key constraint is violated (or post it here with the SQL, and don't forget the values of the bind variables that appear afer the SQL statement).
You should not define the StudentId in Sesstings class. Sessting class already has it (from
public virtual Student Student { get; set; } ). Probably it should be SesstingId and you should map the Id field as well (you have to define/map the primary key).

Nhibernate generates an error in db create script

I'm trying to generate db schema using fluent nhibernate, nhibernate 3.0, spring.net 1.3.1 and SQLite. The create/update script generated by NHibernate is
create table LogEntries (Id UNIQUEIDENTIFIER not null, Hostname TEXT not null, LoggerName TEXT not null, LogLevel INTEGER not null, Message TEXT not null, primary key (Id))
create table Properties (Id INTEGER not null, Key TEXT, Value TEXT, LogEntry_id UNIQUEIDENTIFIER, Index INTEGER, primary key (Id))
But it fails with the following error
System.Data.SQLite.SQLiteException: SQLite error
near "Index": syntax error
The entities:
public class LogEntry
{
public virtual Guid Id { get; set; }
public virtual string LoggerName { get; set; }
public virtual string Message { get; set; }
public virtual int LogLevel { get; set; }
public virtual string Hostname { get; set; }
public virtual IList<Property> Properties { get; set; }
}
public class Property
{
public virtual int Id { get; set; }
public virtual string Key { get; set; }
public virtual string Value { get; set; }
}
And the mapping classes
public class LogEntryMap : ClassMap<LogEntry>
{
public LogEntryMap()
{
Table("LogEntries");
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Hostname).Not.Nullable();
Map(x => x.LoggerName).Not.Nullable();
Map(x => x.LogLevel).Not.Nullable();
Map(x => x.Message).Not.Nullable();
HasMany(x => x.Properties).Cascade.AllDeleteOrphan().AsList();
}
}
public class PropertyMap : ClassMap<Property>
{
public PropertyMap()
{
Table("Properties");
Id(x => x.Id).GeneratedBy.Increment();
Map(x => x.Key);
Map(x => x.Value);
}
}
I'm currently learning NHibernate myself (reading NHibernate 3.0 Cookbook), so in no way am I an expert.
I have the same problem at the moment, having a HasMany-map Parent.Children in an SQLite environment. This also crashes on the Index syntax error.
From what I've managed to deduce, Index is a reserved keyword (isn't it in almost every RDBMS?). It seems these keywords are not escaped by default, and hence, the SQL-script is invalid.
According to the book, you can escape the columnnames by adding a backtick to the column-name:
HasMany(x => x.Children).Cascade.AllDeleteOrphan().AsList(p => p.Column("`Index"));
However, even though this "works", it generates the following SQL-query, which seems to have dropped the x:
create table Child (
Id INTEGER not null,
ChildType TEXT not null,
Version INTEGER not null,
Content TEXT,
Title TEXT not null,
Parent_id INTEGER,
"Inde" INTEGER,
primary key (Id)
)
So, either consider:
specifying a custom index columnname which isn't a keyword,
rely on the backtick auto-escape (no clue what's happening here, no time to check)
use a different collection type if you don't actually need an ordered list. See List vs Set vs Bag in NHibernate

Fluent NHibernate mapping

I have these classes:
public class Entity
{
public virtual Guid Id { get; set; }
public virtual string EntityName { get; set; }
public virtual IDictionary<string, Property> { get; set; }
// ...
}
public class Property
{
public virtual int? IntValue { get; set; }
public virtual decimal? DecimalValue { get; set; }
}
Is it possible to create Fluent NHibernate mappings so that executing the resulting schema will give these tables:
[Entities]
* Id : UNIQUEIDENTIFIER NOT NULL
* EntityName : NVARCHAR(50) NOT NULL
with a clustered index on "Id"
[Properties]
* EntityId : UNIQUEIDENTIFIER NOT NULL
* PropertyName : VARCHAR(50) NOT NULL
* IntValue : INT NULL
* DecimalValue : DECIMAL(12,6) NULL
with a clustered index on "EntityId" and "PropertyName"
Or do I need to change my classes?
An answer more verbose than yes/no will be much appreciated :)
Besides your clustered index which you would need to manually create, yes. Do you absolutely require FNH to generate your schema?
Why don't you just generate the schema yourself specific to your requirements and then map it accordingly.
(not tested or anything, written off the top of my head)
public class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
Table("Entities");
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name).CustomSqlType("NVARCHAR").Length(50).Not.Nullable();
HasMany<Property>(x => x.Properties)
.Table("Properties")
.KeyColumn("PropertyName")
.Inverse()
.AsBag();
}
}
public class PropertyMap : ClassMap<Property>
{
public PropertyMap()
{
Table("Properties");
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.PropertyName).Length(50).Not.Nullable();
Map(x => x.IntValue);
Map(x => x.DecimalValue);
}
}
Here's the mapping that I have come up with:
public sealed class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
Table("Entities");
Id(c => c.Id);
Map(c => c.EntityName).CustomSqlType("nvarchar(50)").Not.Nullable();
HasMany(c => c.Properties)
.KeyColumn("EntityId")
.AsMap<string>("PropertyName")
.Component(part =>
{
part.Map(x => x.IntValue);
part.Map(x => x.DecimalValue).Precision(12).Scale(6);
});
}
}
Schema generation yields this:
create table Entities (
Id UNIQUEIDENTIFIER not null,
EntityName nvarchar(50) not null,
primary key (Id)
)
create table Properties (
EntityId UNIQUEIDENTIFIER not null,
IntValue INT null,
DecimalValue DECIMAL(12, 6) null,
PropertyName INT not null,
primary key (EntityId, PropertyName)
)
alter table Properties
add constraint FK63646D8550C14DC4
foreign key (EntityId)
references Entities
Which is pretty much what I need, with an exception of the column order (minor issue) and PropertyName being nvarchar(255) instead of varchar(50) (something I actually care about).

Categories