Basic Fluent NHibernate relationship issue - nhibernate

The project that I am working on at the moment is using Entity Framework, however there are some issues that we have come across and therefore I am researching using NHibernate which we believe will sort out the majority of issues we have.
Anyway, I have been replicating a simple part of the system, but I have ran into what I assume is a very simple problem with a one-to-many relationship as it is giving very strange results.
Here are my entities:
public class Task : Base.Domain
{
private IList<TaskProperty> _taskProperties = new BindingList<taskProperty>();
private string _name = String.Empty;
private string _description = String.Empty;
public virtual IList<TaskProperty> TaskProperties
{
get
{
return _taskProperties;
}
set
{
if (_taskProperties == value) return;
_taskProperties = value;
OnNotifiyPropertyChanged("TaskProperties");
}
}
public virtual string Name
{
get
{
return _name;
}
set
{
if (_name == value) return;
_name = value;
base.OnNotifiyPropertyChanged("Name");
}
}
public virtual string Description
{
get
{
return _description;
}
set
{
if (_description == value) return;
_description = value;
base.OnNotifiyPropertyChanged("Description");
}
}
public Task()
: base()
{ }
}
public class TaskProperty : Base.Domain
{
private Task _task = null;
private string _name = string.Empty;
private string _description = string.Empty;
private int _propertyType = 0;
//public virtual int TaskID { get; set; }
public virtual Task Task
{
get
{
return _task;
}
set
{
if (_task == value) return;
_task = value;
OnNotifiyPropertyChanged("Task");
}
}
public virtual string Name
{
get
{
return _name;
}
set
{
if (_name == value) return;
_name = value;
OnNotifiyPropertyChanged("Name");
}
}
public virtual string Description
{
get
{
return _description;
}
set
{
if (_description == value) return;
_description = value;
OnNotifiyPropertyChanged("Description");
}
}
public virtual int PropertyType
{
get
{
return _propertyType;
}
set
{
if (_propertyType == value) return;
_propertyType = value;
OnNotifiyPropertyChanged("PropertyType");
}
}
public TaskProperty()
: base()
{ }
}
Here are my NHibernate mappings:
public class TaskMapping : ClassMap<Task>
{
public TaskMapping()
{
Id(x => x.Id).Column("RETTaskID");
Map(x => x.Name);
Map(x => x.Description);
Map(x => x.Version);
HasMany(x => x.TaskProperties).KeyColumn("RETTaskPropertyID");
Table("RETTask");
}
}
public class TaskPropertyMapping : ClassMap<TaskProperty>
{
public TaskPropertyMapping()
{
Id(x => x.Id).Column("RETTaskPropertyID");
Map(x => x.Name);
Map(x => x.Description);
Map(x => x.PropertyType);
References(x => x.Task).Column("RETTaskID");
Table("RETTaskProperty");
}
}
Note: The Domain class which these entities inherit from holds the ID (int Id).
The problem that I am facing is that when I get I Task from the database with an ID of 27 for example, I get the TaskProperty with an ID of 27 as well, not the expected 4 TaskProperties that are related to the Task via a foreign key.
This worked fine in Entity Framework and I know this is a simple situation for any ORM, so I assume I have set up my mappings incorrectly, but from all the examples I have found, I don't seem to be doing anything wrong!
Any answers/suggestions will be most welcome. Thanks.

You are almost there. The Column mapping for HasMany and References must be the same:
public TaskMapping()
{
...
HasMany(x => x.TaskProperties).KeyColumn("RETTaskID"); // use this
// HasMany(x => x.TaskProperties).KeyColumn("RETTaskPropertyID"); // instead of this
}
public TaskPropertyMapping()
{
...
References(x => x.Task).Column("RETTaskID");
}
The collection item has to have a reference column to the owner. This column is used for both directions, because that's how the reference in DB managed...

Related

fluentnhibernate hasmanytomany same identifier exception

I have a many-to-many relationship between two objects (Application, Query). I have constructed the map to have a HasManyToMany mapping in both object maps.
The first time I use the SaveOrUpdate the application, it works fine, and the entry is placed correctly in the join table.
However, the second time I use it, I get the error: 'a different object with the same identifier was already associated with the session'.
The exception is thrown in the last couple of lines of the addQuery code shown below.
Here are the entities and the maps. Any suggestions would be greatly appreciated.
public class Application
{
public virtual int id { get; set; }
public virtual string name { get; set; }
public virtual IList<Query> queries { get; set; }
public Application()
{
this.queries = new List<Query>();
}
public virtual void AddQuery(Query qry)
{
qry.applicationsUsedIn.Add(this);
queries.Add(qry);
}
}
public class Query
{
public virtual int id { get; protected set; }
public virtual string name { get; set; }
public virtual string query { get; set; }
public virtual IList<QueryParameters> parameters { get; set; }
public virtual IList<Application> applicationsUsedIn { get; set; }
public Query()
{
this.parameters = new List<QueryParameters>();
this.applicationsUsedIn = new List<Application>();
}
public virtual void AddParameter(QueryParameters qp)
{
qp.query = this;
this.parameters.Add(qp);
}
}
public class ApplicationMap : ClassMap<Application>
{
public ApplicationMap()
{
Table("dbo.Applications");
Id(x => x.id).Column("id");
Map(x => x.name).Column("name");
HasManyToMany(x => x.queries)
.Table("dbo.ApplicationsQueries")
.ParentKeyColumn("appid")
.ChildKeyColumn("qryid")
.Not.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class QueryMap : ClassMap<Query>
{
public QueryMap()
{
Table("dbo.Queries");
Id(x => x.id);
Map(x => x.name);
Map(x => x.query);
HasMany(x => x.parameters)
.Cascade.All()
.Inverse();
HasManyToMany(x => x.applicationsUsedIn)
.Table("dbo.ApplicationsQueries")
.ParentKeyColumn("qryid")
.ChildKeyColumn("appid")
.Inverse()
.Cascade.SaveUpdate()
.Not.LazyLoad();
}
}
public void addQuery(string appname, string qryname, string qrystr)
{
Application app = getApplication(appname);
if (null == app)
{
app = addApplication(appname);
}
Query qry = getQuery(appname, qryname);
if (null == qry)
{
using (ISessionFactory isf = getSessionFactory())
{
using (var sess = isf.OpenSession())
{
using (var tran = sess.Transaction)
{
tran.Begin();
qry = new Query();
qry.name = qryname;
qry.query = qrystr;
sess.Save(qry);
tran.Commit();
}
}
}
}
if (!app.queries.Contains(qry))
{
using (ISessionFactory isf = getSessionFactory())
{
using (var sess = isf.OpenSession())
{
using (var tran = sess.Transaction)
{
tran.Begin();
app.AddQuery(qry);
//This is where the exception is thrown
sess.SaveOrUpdate(app);
tran.Commit();
}
}
}
}
}
UPDATED CODE in case it helps someone else
public ApplicationMap()
{
Table("dbo.Applications");
Id(x => x.id).Column("id");
Map(x => x.name).Column("name");
HasManyToMany(x => x.queries)
.Table("dbo.ApplicationsQueries")
.ParentKeyColumn("appid")
.ChildKeyColumn("qryid")
.LazyLoad();
}
public QueryMap()
{
Table("dbo.Queries");
Id(x => x.id);
Map(x => x.name);
Map(x => x.query);
HasMany(x => x.parameters)
.Cascade.All()
.Inverse();
HasManyToMany(x => x.applicationsUsedIn)
.Table("dbo.ApplicationsQueries")
.ParentKeyColumn("qryid")
.ChildKeyColumn("appid")
.Inverse()
.LazyLoad();
}
public void addQuery(string appname, string qryname, string qrystr)
{
using (ISessionFactory isf = getSessionFactory())
{
using (var sess = isf.OpenSession())
{
using (var tran = sess.Transaction)
{
tran.Begin();
var critapp = sess.CreateCriteria<Application>()
.Add(Restrictions.Eq("name", appname));
Application app = (Application)critapp.UniqueResult();
if (null == app)
{
app = new Application();
app.name = appname;
sess.Save(app);
}
var critqry = sess.CreateCriteria<Query>()
.Add(Restrictions.Eq("name", qryname));
Query qry = (Query)critqry.UniqueResult();
if (null == qry)
{
qry = new Query();
qry.name = qryname;
qry.query = qrystr;
sess.Save(qry);
}
if (!app.queries.Contains(qry))
{
app.AddQuery(qry);
}
tran.Commit();
}
}
}
}
The problem here is hidden in many opened sessions for (in fact) one operation. This is not the correct way how to execute this insert/updates. We should always wrap a set of operations relying on each other, into ONE session, one transaction.
So what happened is that here we've got the Query recieved like this:
Query qry = getQuery(appname, qryname);
We have an object, which is (was) part of the session, which just ran out of the scope. The qry instance is fully populated now because
it is an existing object (loaded from DB)...
the mapping of the collection IList<Application> applicationsUsedIn (see your mapping) is .Not.LazyLoad()
and the same is true for other Not.LazyLoad() mappings...
So once we come to the last transaction (and its own session) ... our objects could be deeply populated... having the same IDs as loaded objects inside of that last session
To solve the issue quickly:
Open the session at the begining of the operation
Open the transaction at the begining
Call Save() only, if we do have new object
For objects retrieved via getQuery(), getApplication() (beeing in the same session) DO NOT call the SaveOrUpdate(). They are already in the session, and that's what the SaveOrUpdated in this case mostly does (put them in the session)
a) Call the transaction.Commit() and all the stuff will be properly persisted
b) Close the session at the end of operation
Note: I would change the mapping of your many-to-many
remove the Not.LazyLoad(). Lazy is what we mostly want..
remove Cascade because it is about the other end not about the pairing table. If this was intended then leave it

Fluent NHibernate Mapping IDictionary<Enum,Class> - Key saves as null

I have the following classes and mappings:
public enum VariationType
{
Base = 1,
RiderMain = 2,
RiderSpouse = 3,
RiderChild = 4,
Family = 5,
FamilyBase = 6
}
public class PlanParameter
{
private IDictionary<VariationType, PlanParameterDefaultValue> _defaultValues;
public PlanParameter()
{
ParameterContext = new Parameter();
}
public virtual Parameter ParameterContext { get; set; }
public virtual object DefaultValue { get; set; }
public virtual string DefaultValueString
{
get
{
return DefaultValue == null ? null : DefaultValue.ToString();
}
set
{
DefaultValue = value == null ? null : Convert.ChangeType(value, ParameterContext.Type);
}
}
public virtual IDictionary<VariationType, PlanParameterDefaultValue> DefaultValues
{
get
{
if (_defaultValues == null)
_defaultValues = new Dictionary<VariationType, PlanParameterDefaultValue>();
return _defaultValues;
}
}
}
class PlanParameterMap : ClassMap<PlanParameter>
{
public PlanParameterMap()
{
Id().GeneratedBy.Identity().Column("ID");
References(x => x.ParameterContext,"ParameterID");
Map(x => x.DefaultValueString);
HasMany(x=> x.DefaultValues)
.Access.CamelCaseField(Prefix.Underscore)
.KeyColumn("PlanParameterID").Inverse()
.AsMap("Variation")
.Cascade.AllDeleteOrphan();
}
}
public class PlanParameterDefaultValue
{
public virtual PlanParameter PlanParameter { get; set; }
public virtual object DefaultValue { get; set; }
public virtual string DefaultValueString
{
get
{
return DefaultValue == null ? null : DefaultValue.ToString();
}
set
{
DefaultValue = value == null ? null : Convert.ChangeType(value, PlanParameter.ParameterContext.Type);
}
}
}
class PlanParameterDefaultValueMap : ClassMap<PlanParameterDefaultValue>
{
public PlanParameterDefaultValueMap()
{
Id().GeneratedBy.Identity().Column("ID");
Map(x => x.DefaultValueString);
References(x => x.PlanParameter).Column("PlanParameterID");
}
}
My problem is very specific to the mapping of the
IDictionary<VariationType, PlanParameterDefaultValue> DefaultValues
The enum for some reason will not save, all that's saved in it's column is null
my only solution so far was to add a VariationType Property to the Entity and map it with a lambda formula, but i really don't need the VariationType in the Entity
Am i doing something wrong?
Thanks very much
The Inverse() tells NHibernate that the "mapped-to"-entity will take care of saving the key, and that the collection owner should not persist the Key. Try removing that flag.

NHibernate.Mapping.ByCode Many-to-Many relations

I've created 2 objects:
public class Set
{
public Set()
{
_sorts = new List<Sort>();
}
public virtual int Id { get; set; }
public virtual string Code { get; set; }
private ICollection<Sort> _sorts;
public virtual ICollection<Sort> Sorts
{
get { return _sorts; }
set { _sorts = value; }
}
}
public class Sort
{
public Sort()
{
_sets = new List<Set>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
private ICollection<Set> _sets;
public virtual ICollection<Set> Sets
{
get { return _sets; }
set { _sets = value; }
}
}
And 2 mappings:
public class SetMapping: ClassMapping<Set>
{
public SetMapping()
{
Table("Sets");
Id(x => x.Id, map => map.Generator(IdGeneratorSelector.CreateGenerator()));
Property(x => x.Code, map =>
{
map.Length(50);
map.NotNullable(false);
});
Bag(x => x.Sorts, map =>
{
map.Key(k =>
{
k.Column("SetId");
k.NotNullable(true);
});
map.Cascade(Cascade.All);
map.Table("SetsToSorts");
map.Inverse(true);
}, r => r.ManyToMany(m => m.Column("SortId")));
}
}
public class SortMapping: ClassMapping<Sort>
{
public SortMapping()
{
Table("Sorts");
Id(x => x.Id, map => map.Generator(IdGeneratorSelector.CreateGenerator()));
Property(x => x.Name, map =>
{
map.Length(50);
map.NotNullable(false);
});
}
}
usage:
Set can have many sorts
Sort can belong to many sets.
And I would like to use this as:
var set = new Set() {Code = "001"};
var sort = new Sort {Name = "My name"};
set.Sorts.Add(sort);
sort.Sets.Add(set);
Somehow the relations are not working yet because when I try to use the above code to add sorts to set for example and commit then I don't see any records saved to the SetsToSorts linked table.
Does anyone have a clue what I'm missing in my mapping? Or otherwise doing wrong?
Thank you,
Joost
Your mapping says that Set's Sort collection is inverse (map.Inverse(true)). That means the other side of the bidirectional association is responsible for persisting changes.
But your Sort class mapping doesn't have any collection mapping. Remove map.Inverse(true) on SetMapping or add noninverse collection mapping to SortMapping.

Fluent NHibernate Mapping Compent with Many-To-Many and Extra Fields

I have been spinning around in the same place trying to figure this issue with a many-to-many table with extra columns. I am swallowing my pride and asking more experienced Fluent NHibernate experts if you can help out here.
I have been trying to use a component for a many-to-many table but I don't seem to find where to point the component to cause a join on a specific column. I guess as long as I populate the completion information object, the presence and usage of a component is not a requirement for me.
This is my simplified schema.
User
...................
Id
Name
Task
...................
Id
Name
Job
...................
Id
Name
JobTask
...................
JobId
TaskId
CompletedByUserId
CompletionDate
CompletionNotes
Then I have objects like so:
public class Job
{
long Id { get; set; }
IList<Task> Tasks { get; set; }
}
public class Task
{
long Id { get; set; }
string Name { get; set; }
CompletionInfo CompletionInfo { get; set; }
}
public class CompletionInfo
{
User User { get; set; }
DateTime CompletionDate { get; set; }
}
Can you help with your ideas how to implement this in fluent nhibernate?
I had this mapping before on the JobMap and it would work but only for the many-to-many columns ( TaskId, JobId). I need the extra information related to that relationship(dateCompleted, userCompleted,etc)
HasManyToMany<Task>(job => job.Tasks)
.Table(ManyToManyTable)
.ParentKeyColumn("JobId")
.ChildKeyColumn("TaskId")
.Not.LazyLoad()
.Cascade.Delete();
In order to simplify things with the many-to many I thought to create an object to represent the relationship and encapsulate a task. Like so:
And I would add a proxy in the Job task to modify the actual Task properties. But that is not going anywhere.
This must be something common out there, I am very surprised there not much regarding this issue.
Naturally I would have loved a way to extend the HasManyToMany but not sure how, hence this post.
public class JobTask : Entity<long, JobTask>
{
public virtual Job ParentJob { get; set; }
public virtual Task Task { set;set; }
public virtual TaskCompletionDetails CompletionInformation
{
get
{
if (this.Task == null)
return null;
return Task.CompletionInformation;
}
set
{
if (this.Task == null)
return;
this.Task.CompletionInformation = value;
}
}
public virtual string CompletionNotes
{
get
{
if (this.Task == null || this.Task.CompletionInformation == null)
return null;
return Task.CompletionInformation.Notes;
}
set
{
if (this.Task == null)
return;
this.Task.CompletionInformation.Notes = value;
}
}
public virtual DateTime? CompletionDate
{
get
{
if (this.Task == null || this.Task.CompletionInformation == null)
return null;
return Task.CompletionInformation.Date;
}
set
{
if (this.Task == null)
return;
this.Task.CompletionInformation.Date = value;
}
}
public virtual IUser User
{
get
{
if (this.Task == null || this.Task.CompletionInformation == null)
return null;
return Task.CompletionInformation.User;
}
set
{
if (this.Task == null || value == null)
return;
if (this.Task.CompletionInformation != null)
this.Task.CompletionInformation.User = value;
}
}
}
}
This would be the map direction I have started for it:
public class JobTaskMap : ClassMap<JobTask>
{
private const string ModelTable = "[JobTasks]";
public JobTaskMap()
{
Table(ModelTable);
Id(jobTask => jobTask.Id)
.Column("Id")
.GeneratedBy.Identity();
References<Job>( jobTask => jobTask.ParentJob)
.Column("JobId")
.Fetch.Join();
Component<Task>( jobTask => (Task) jobTask.Task,
// This is the task
comp =>
{
// This is the task completion information
comp.Component<TaskCompletionDetails>(
task => (TaskCompletionDetails)task.CompletionInformation,
compInfo =>
{
compInfo.Map(info => info.Date)
.Column("CompletionDate")
.Nullable();
compInfo.Map(info => info.Notes)
.Column("CompletionNotes")
.Nullable();
compInfo.References<User>(info => info.User)
.Column("CompletedByUserId")
.Nullable()
.Fetch.Join();
});
});
}
These are some other related reading I have followed without success:
Reference:
http://wiki.fluentnhibernate.org/Fluent_mapping
Fluent Nhibernate Many-to-Many mapping with extra column
heres a way which depends on the id generation strategy used. if Identity is used then this won't do (at least NH discourages use of Identity for various reasons), but with every strategy that inserts the id itself it would work:
class JobMap : ClassMap<Job>
{
public JobMap()
{
Id(x => x.Id);
HasMany(x => x.Tasks)
.KeyColumn("JobId");
}
}
class TaskMap : ClassMap<Task>
{
public TaskMap()
{
Table("JobTask");
Id(x => x.Id, "TaskId");
Component(x => x.CompletionInfo, c =>
{
c.Map(x => x.CompletionDate);
c.References(x => x.User, "CompletedByUserId");
});
Join("Task", join =>
{
join.Map(x => x.Name, "Name");
});
}
}

Fluent NHibernate compositeid to mapped class

I'm trying to figure out how to use CompositeId to map another class. Here's a test case:
The tables:
TestParent:
TestParentId (PK)
FavoriteColor
TestChild:
TestParentId (PK)
ChildName (PK)
Age
The classes in C#:
public class TestParent
{
public TestParent()
{
TestChildList = new List<TestChild>();
}
public virtual int TestParentId { get; set; }
public virtual string FavoriteColor { get; set; }
public virtual IList<TestChild> TestChildList { get; set; }
}
public class TestChild
{
public virtual TestParent Parent { get; set; }
public virtual string ChildName { get; set; }
public virtual int Age { get; set; }
public override int GetHashCode()
{
return Parent.GetHashCode() ^ ChildName.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is TestChild)
{
var toCompare = obj as TestChild;
return this.GetHashCode() != toCompare.GetHashCode();
}
return false;
}
}
The Fluent NHibernate maps:
public class TestParentMap : ClassMap<TestParent>
{
public TestParentMap()
{
Table("TestParent");
Id(x => x.TestParentId).Column("TestParentId").GeneratedBy.Native();
Map(x => x.FavoriteColor);
HasMany(x => x.TestChildList).KeyColumn("TestParentId").Inverse().Cascade.None();
}
}
public class TestChildMap : ClassMap<TestChild>
{
public TestChildMap()
{
Table("TestChild");
CompositeId()
.KeyProperty(x => x.ChildName, "ChildName")
.KeyReference(x => x.Parent, "TestParentId");
Map(x => x.Age);
References(x => x.Parent, "TestParentId"); /** breaks insert **/
}
}
When I try to add a new record, I get this error:
System.ArgumentOutOfRangeException :
Index was out of range. Must be
non-negative and less than the size of
the collection. Parameter name: index
I know this error is due to the TestParentId column being mapped in the CompositeId and References calls. However, removing the References call causes another error when querying TestChild based on the TestParentId.
Here's the code that does the queries:
var session = _sessionBuilder.GetSession();
using (var tx = session.BeginTransaction())
{
// create parent
var p = new TestParent() { FavoriteColor = "Red" };
session.Save(p);
// creat child
var c = new TestChild()
{
ChildName = "First child",
Parent = p,
Age = 4
};
session.Save(c); // breaks with References call in TestChildMap
tx.Commit();
}
// breaks without the References call in TestChildMap
var children = _sessionBuilder.GetSession().CreateCriteria<TestChild>()
.CreateAlias("Parent", "p")
.Add(Restrictions.Eq("p.TestParentId", 1))
.List<TestChild>();
Any ideas on how to create a composite key for this scenario?
I found a better solution that will allow querying and inserting. The key is updating the map for TestChild to not insert records. The new map is:
public class TestChildMap : ClassMap<TestChild>
{
public TestChildMap()
{
Table("TestChild");
CompositeId()
.KeyProperty(x => x.ChildName, "ChildName")
.KeyReference(x => x.Parent, "TestParentId");
Map(x => x.Age);
References(x => x.Parent, "TestParentId")
.Not.Insert(); // will avoid "Index was out of range" error on insert
}
}
Any reason you can't modify your query to just be
_sessionBuilder.GetSession().CreateCriteria<TestChild>()
.Add(Restrictions.Eq("Parent.TestParentId", 1))
.List<TestChild>()
Then get rid of the reference?