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
Related
I need to save a list of DataNode objects that have a property refering a NodeType objects, but I've tried all kinds of mapping settings, but keep getting errors.
The classes look as followed:
public class DataNode
{
public virtual int Id { get; set; }
public virtual string Value { get; set; }
public virtual NodeType NodeType { get; set; }
}
public class NodeType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
This is the mapping for the DataNode
public class DataNodeMapping : ClassMap<DataNode>
{
protected readonly string _tableName;
public DataNodeMapping()
: this("ClientDataNode")
{
}
protected DataNodeMapping(string table)
{
_tableName = (table.StartsWith("[") ? table : ($"[{table}]"));
Table(_tableName);
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.Value);
HasOne(x => x.NodeType)
.ForeignKey("ClientNodeTypeId")
.PropertyRef(p => p.Id)
.Cascade.AllDeleteOrphan()
.LazyLoad();
OptimisticLock.Version();
}
}
The actual save function looks as follows:
public async Task Execute()
{
var apiDataNodes = await _apiService.GetDataNodes();
var dbDataNodes = await _session.Query<DataNode>().ToListAsync();
using (var transaction = _session.BeginTransaction())
{
foreach (var dto in apiDataNodes.Where(api => !dbDataNodes.Any(db => db.Id == api.Id)))
{
var datanode = _mapper.Map<DataNode>(dto);
await _session.SaveAsync(datanode);
}
transaction.Commit();
}
}
And this results in the following exception:
a different object with the same identifier value was already associated with the session: 203, of entity: ....NodeType
Removing the LazyLoad attribite didn't help either
The nodetype table is already seeded and every DataNode object has a valid value for DataNode.NodeType.Id
There's a one-to-many relationship between DataNode and NodeType.
I've been struggling for hours to get the correct mapping settings, tried several other options like:
References(x => x.NodeType)
.Column("ClientNodeTypeId")
.PropertyRef(p => p.Id);
Resulting in:
Error dehydrating property value for ......NodeType
=> Unable to resolve property: Id
Adding .ReadOnly() to that line ends up in storing the reccords, but with a NULL for every ClientNodeTypeId
Also tried:
Map(x => x.NodeType.Id, "ClientNodeTypeId");
But that resulted in a foreign key constraint error:
The INSERT statement conflicted with the FOREIGN KEY constraint "ClientDataNode_ClientNodeTypeId_ClientNodeType_Id".
Please help, I have no ideas left and spend way too much time already on what shouldn't be a big problem.
I have small problem with nHibernate (fluent) I have two objects, one contains another - a parent and a child (predefined objects, readonly).
mappings:
public class ParentClass
{
public virtual int Id { get; set; }
public virtual ChildClass Metoda { get; set; }
}
public ParentClassMap() {
Table("Wyceny");
Id(x => x.Id).Column("Id").GeneratedBy.TriggerIdentity();
References(x => x.Metoda).Column("RMW_ID");
}
public ChildClass
{
public virtual int Id { get; set; }
public virtual string Nazwa { get; set; }
}
public ChildClassMap()
{
Table("Metody");
Id(x => x.Id).Column("Id");
Map(x => x.Nazwa).Column("Nazwa_met");
}
Everything works perfectly until I chose child object with id = 0, reading still works for id=0, but when I'm trying to save or update Parent with correct ChildObject(readed previously from db through nHibernate), nHibernate stores null instead of value.
Any suggestions?
nHibernate 3.3.1.4000
fluent 1.4.0.0
The issue here would be the unsaved-value. NHibernate must decide if operations with object will be insert or update. This decision comes from unsaved-value setting, which is by default for int set to 0.
Try to extend your mapping of a ChildClass:
public ChildClassMap()
{
Table("Metody");
Id(x => x.Id)
.Column("Id")
.UnsavedValue(-1);
...
See 5.1.4. id, cite:
unsaved-value (optional - defaults to a "sensible" value): An identifier property value that indicates that an instance is newly instantiated (unsaved), distinguishing it from transient instances that were saved or loaded in a previous session.
And here is nice Id mapping overview by Adam Bar (the second half of the article)
I have a thesaurus application built on NHibernate. The data model is very straight-forward:
public class Word
{
public virtual int Id { get; set; }
public virtual string Text { get; set; }
public virtual IList<Word> Synonyms { get; set; }
public virtual IList<Word> Antonyms { get; set; }
}
So each word has a list of synonyms and a list of antonyms. I thought the mapping would be straight-forward as well:
public class WordMapping : ClassMap<Word>
{
public WordMapping()
{
Id(x => x.Id);
Map(x => x.Text);
HasMany(x => x.Synonyms).Cascade.AllDeleteOrphan();
HasMany(x => x.Antonyms).Cascade.AllDeleteOrphan();
}
}
When I look at the table generated in MS SQL Server 2008, I only see one key column for the two HasMany relations:
Id int
Text nvarchar(255)
Word_id int
I think this is causing some weird things to happen when I insert data. When I get the lists after doing some insertions, both Synonyms and Antonyms contain the same Words, but the Words are just seemingly-arbitrary subsets of what should be in the two lists.
Do I have the problem pegged right? If so, how can I fix it?
I was way off base. I assumed FNH was generating a separate table for the HasMany relationships (it wasn't). Proper code found here: Fluent NHibernate Self Referencing Many To Many
Specific to my problem, I created two separate tables:
-- Synonyms table --
WordId int
SynonymId int
-- Antonyms table --
WordId int
AntonymId int
Then in my mapping class:
public class WordMapping : ClassMap<Word>
{
public WordMapping()
{
Id(x => x.Id);
Map(x => x.Text);
HasManyToMany(x => x.Synonyms).ParentKeyColumn("WordId")
.ChildKeyColumn("SynonymId")
.Table("Synonyms");
HasManyToMany(x => x.Antonyms).ParentKeyColumn("WordId")
.ChildKeyColumn("AntonymId")
.Table("Antonyms");
}
}
That fixes all the issues.
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).
Question: is there a way to map a single foreign key to a number of mutually exclusive tables, based on a context?
Background...
In my specific example, I have the following domain graph, representing an insurance claim which can be against a vehicle or property:
public enum InvolvedPartyContext
{
Vehicle = 1,
Property = 2
}
public class Claim
{
public virtual Guid Id { get; set; }
public virtual InvolvedPartyContext InvolvedPartyContext { get; set; }
public virtual Vehicle Vehicle { get; set; } // set if Context = Vehicle
public virtual Property Property { get; set; } // set if Context = Property
}
public class Vehicle { //... }
public class Property { //... }
The SQL looks like this (notice the single foreign key InvolvedPartyId):
CREATE TABLE Claims (
Id uniqueidentifier NOT NULL,
InvolvedPartyContext int NOT NULL,
InvolvedPartyId uniqueidentifier NOT NULL
)
CREATE TABLE Vehicles (
Id uniqueidentifier NOT NULL,
Registration varchar(20) NOT NULL
)
CREATE TABLE Properties (
Id uniqueidentifier NOT NULL,
PostCode varchar(20) NOT NULL
)
The Fluent NHibernate mapping file for Claim:
public ClaimMap()
{
Id(x => x.Id);
Map(x => x.InvolvedPartyContext).CustomTypeIs(typeof(InvolvedPartyContext));
References(x => x.Vehicle, "InvolvedPartyId");
References(x => x.Property, "InvolvedPartyId");
}
This throws an "Invalid index {n} for this SqlParameterCollection with Count {m}" exception, since the same field (InvolvedPartyId) is mapped twice. A simple fix would be to create VehicleId and PropertyId fields, but in the real world there are many more contexts, so this isn't very flexible.
Personally, I wouldn't go with the design you have. Instead I'd create subclasses of your Claim class, VehicleClaim and PropertyClaim respectively.
public class VehicleClaim : Claim
{
public virtual Vehicle Vehicle { get; set; }
}
Then change your mappings to use your InvolvedPartyContext column as a discriminator (the column which NHibernate uses to determine which class the row represents), and create subclass mappings for each subclass.
public class ClaimMap : ClassMap<Claim>
{
public ClaimMap()
{
Id(x => x.Id);
DiscriminateSubClassesOnColumn("InvolvedPartyContext");
}
}
public class VehicleClaimMap : SubclassMap<VehicleClaim>
{
public VehicleClaimMap()
{
DiscriminatorValue(1);
References(x => x.Vehicle);
}
}
If you really do want to run with what you've got, you should look into the any mappings; there isn't a lot of documentation on them, but you use the ReferencesAny method.