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?
Related
I have a parent class and a child class. One Child is always related to just one parent, but a parent can have multiple children:
public class Parent
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Child> Children { get; set; } = new List<Child>();
}
public class Child
{
public virtual int Id { get; set; }
public virtual string ParentId { get; set; }
public virtual string Name { get; set; }
}
I'm using the latest version of NHibernate 5.1.3 and mapping by code:
internal class ParentMapping : ClassMapping<Parent>
{
public ParentMapping()
{
Table("Parent");
Id(x => x.Id);
Property(x => x.Name);
Bag(
x => x.Children,
map =>
{
map.Key(km => km.Column("ParentId"));
map.Lazy(CollectionLazy.NoLazy);
map.Cascade(Cascade.Persist);
map.Inverse(true);
},
x => x.OneToMany());
}
}
internal class ChildMapping : ClassMapping<Child>
{
public ChildMapping()
{
Table("Child");
Id(x => x.Id, x => x.Generator(Generators.Identity));
Property(x => x.ParentId);
Property(x => x.Name);
}
}
Queries work but are quite inefficient. Instead of creating a single JOIN statement to query the children together with their parent, an explicit SELECT is made to retrieve the Child objects.
Even worse, inserts result in the following error:
NHibernate.StaleStateException: 'Batch update returned unexpected row count from update; actual row count: 0; expected: 3'
Here's a sample of a query:
using (var session = _sessionProvider.GetSession())
return session.Query<T>().ToList();
And that's the code to save a new item:
using (var session = _sessionProvider.GetSession())
{
session.Transaction.Begin();
session.Save(newEntity);
session.Transaction.Commit();
}
So everything's pretty easy.
I assume, the Bag() configuration in ParentMapping needs to be fixed. What am I doing wrong?
Change the fetch strategy of the bag mapping to join will generate a join query, like this:
Bag(
e => e.Children,
map => {
map.Key(km => km.Column("ParentId"));
// map.Lazy(CollectionLazy.NoLazy);
// change fetch str
map.Fetch(CollectionFetchMode.Join);
map.Cascade(Cascade.Persist);
map.Inverse(true);
},
x => x.OneToMany()
);
And you have the collection persist as inverse (map.Inverse(true);), you should have a many to one mapping for Parent in your Child class like this:
public class Child {
public virtual int Id { get; set; }
// public virtual string ParentId { get; set; }
public virtual Parent Parent { get; set; }
public virtual string Name { get; set; }
}
Then map the Parent property as ManyToOne like this:
public ChildMapping() {
// other mapping goes here
ManyToOne(
x => x.Parent,
map => {
map.Column("ParentId");
map.Class(typeof(Parent));
map.Fetch(FetchKind.Join);
}
);
}
But nhibernate do not include children of parent by default (maybe it is too heavy), and if you want to query children with parent instance, you can query like this:
using (var session = OpenSession()) {
var query = session.Query<Parent>().Select(p => new {
Parent = p, Children = p.Children
});
var data = query.ToList();
}
For saving entities to database, should do like this:
try {
// save parent first
var parent = new Parent();
parent.Name = "Parent object";
session.Save(parent);
// then save child
var child = new Child();
child.Name = "Child object";
child.Parent = parent;
session.Save(child);
session.Flush();
tx.Commit();
}
catch (Exception) {
tx.Rollback();
throw;
}
I'm trying to query on a simple data structure in nhibernate and MSSQL
dbo.Projects : Id(int, not null)
dbo.Finances : Id(int, not null), ProjectId(int,not null), foreign key references to dbo.projects
I want to get all the records in projects table that are not present in finances table where the finances table has a foreign key reference ProjectId.
I am migrating to (Fluent) Nhibernate 3 from EntityFramework?
//So far I have got here:
public IQueryable<ProjectModel> GetProjectsNotPresentInFinance()
{
var factory = Fluently.Configure()
.Database(MsSqlConfiguration
.MsSql2008
.ConnectionString(m_connectionString))
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<ProjectMap>()
).BuildSessionFactory();
using (var session = factory.OpenSession())
{
var allprojects = session.QueryOver<ProjectModel>();
var projectsToReturn = allprojects.List<ProjectModel>().AsQueryable();
//--- Something like : all the records not in finances table ---------
// .Where( proj => !db.Finances.Where(fin => fin.ProjectId == proj.Id).Any())
// .Select(project => new ProjectModel
// {
// Id=project.Id,
// ProjectName = project.ProjectName,
// });
return projectsToReturn;
}
}
public class FinanceModel
{
public virtual int Id { get; set; }
public virtual int ProjectId { get; set; }
}
public class ProjectModel
{
public virtual int Id { get; set; }
public virtual string ProjectName { get; set; }
}
public class ProjectMap:ClassMap<ProjectModel>
{
public ProjectMap() {
Table("Projects");
Id(x => x.Id);
Map(x => x.ProjectName);
}
}
public class FinanceMap : ClassMap<FinanceModel>
{
public FinanceMap()
{
Table("Finances");
Id(x => x.Id);
References(x => x.ProjectModel);
}
}
//-------------------------------------------------------
//This is an Equivalent working code Using EntityFramework :
public IQueryable<ProjectModel> GetProjectsNotPresentInFinance() {
IQueryable<ProjectModel> projectList = db.Projects
.Where( proj => !db.Finances.Where(fin => fin.ProjectId == proj.Id).Any())
.Select(project => new ProjectModel
{
Id=project.Id,
ProjectName = project.ProjectName,
});
return projectList;
}
//-------------------------------------------------------
On second thought, you may try this without changing anything to your mapping, using a subquery :
var notOrphanProjectIdsSubquery = QueryOver.Of<FinanceModel>()
.Select(x => x.ProjectId);
var orphanProjects = session.QueryOver<ProjectModel>()
.WithSubquery
.WhereProperty(x=>x.Id)
.NotIn(notOrphanProjectIdsSubquery)
.List();
----------------------- Initial answer
Assuming you have a mapped Finances Property in your Project class, and according to https://stackoverflow.com/a/14980450/1236044, it should be something like :
var orphanProjects = session.QueryOver<ProjectModel>()
.WhereRestrictionOn(x => x.Finances).IsEmpty()
.List();
I must confess I am not proficient with FluentNH. I guess the classes and mappings should be something like this, hoping I'm not setting you on the wrong track...
public class FinanceModel
{
public virtual int Id { get; set; }
public virtual int ProjectId { get; set; }
public virtual ProjectModel Project{get;set;}
}
public class ProjectModel
{
public virtual int Id { get; set; }
public virtual string ProjectName { get; set; }
public virtual IList<FinanceModel> Finances { get; set; }
}
public class ProjectMap:ClassMap<ProjectModel>
{
public ProjectMap() {
Table("Projects");
Id(x => x.Id);
Map(x => x.ProjectName);
HasMany(x => x.Finances);
}
}
public class FinanceMap : ClassMap<FinanceModel>
{
public FinanceMap()
{
Table("Finances");
Id(x => x.Id);
References(x => x.Project);
}
}
I want to filter objects from the db by a property that comes from another object but i get an exception:
A first chance exception of type 'System.Collections.Generic.KeyNotFoundException' occurred in mscorlib.dll
A first chance exception of type 'NHibernate.QueryException' occurred in NHibernate.dll
A first chance exception of type 'NHibernate.QueryException' occurred in NHibernate.dll
The program '[5116] Examples.FirstProject.vshost.exe: Managed (v2.0.50727)' has exited with code -532459699 (0xe0434f4d).
This works:
var curves = session.QueryOver<Curve>().WhereRestrictionOn(p => p.Name).IsLike("%CurveName%").List();
foreach (Curve curve in curves)
{
Console.WriteLine(" ID:\t{0}\n Name:\t{1}\n Group:\t{2}\n", curve.Id, curve.Name, curve.Group.Name);
}
This not, it outputs the exception information:
var curves = session.QueryOver<Curve>().WhereRestrictionOn(p => p.Group.Name).IsLike("%GroupName%").List();
foreach (Curve curve in curves)
{
Console.WriteLine(" ID:\t{0}\n Name:\t{1}\n Group:\t{2}\n", curve.Id, curve.Name, curve.Group.Name);
}
These are my mappings:
public class CurveMap : ClassMap<Curve>
{
public CurveMap()
{
Table("CURVES");
Id(x => x.Id).Column("CURVE_ID");
Map(x => x.Name).Column("NAME");
References(x => x.Group).Column("GROUP_ID");
}
}
public class CurveGroupMap : ClassMap<CurveGroup>
{
public CurveGroupMap()
{
Table("GROUPS");
Id(x => x.Id).Column("GROUP_ID");
Map(x => x.Name).Column("NAME");
HasMany(x => x.Curves).KeyColumn("GROUP_ID").Cascade.All().Inverse();
}
}
And these are my objects
public class Curve
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual CurveGroup Group { get; set; }
}
public class CurveGroup
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual IList<Curve> Curves { get; set; }
}
Any idea, how to fix this. I am new to (fluent) nhibernate.
If you join CurveGroup and use Aliases it will work:
CurveGroup cgAlias = null;
var curves = session.QueryOver<Curve>()
.JoinAlias(e => e.Group, () => cgAlias)
.WhereRestrictionOn(() => cgAlias.Name).IsLike("%GroupName%").List();
I'm getting an error with NHibernate when I try to perfrom a ISession.Delete on any table with a One to Many relationship.
NHibernate is trying to set the foreign key to the parent table in the child table to null, rather than just deleting the child table row.
Here is my domain:
public class Parent
{
public Parent()
{
_children = new List<Child>();
}
public int Id { get; set; }
public string SimpleString { get; set; }
public DateTime? SimpleDateTime { get; set; }
private IList<Child> _children;
public IEnumerable<Child> Children
{
get { return _children; }
}
public void AddChild(Child child)
{
child.Parent = this;
_children.Add(child);
}
}
public class Child
{
public int Id { get; set; }
public string SimpleString { get; set; }
public DateTime? SimpleDateTime { get; set; }
[JsonIgnore]
public Parent Parent { get; set; }
}
I have set-up the Fluent NHibernate mappings as follows:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Not.LazyLoad();
Id(x => x.Id);
Map(x => x.SimpleString);
Map(x => x.SimpleDateTime);
HasMany(x => x.Children)
.Not.LazyLoad()
.KeyColumn("ParentId").Cascade.AllDeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Not.LazyLoad();
Id(x => x.Id);
Map(x => x.SimpleString);
Map(x => x.SimpleDateTime);
References(x => x.Parent).Not.Nullable().Column("ParentId").Cascade.All().Fetch.Join();
}
}
I've told NHibernate to Cascade.AllDeleteOrphan() but it's still trying to set the ParentId foriegn key to null here is the test I setup:
public void Delete_GivenTableWithChildren_WillBeDeletedFromDB()
{
int id;
using (var createSession = MsSqlSessionProvider.SessionFactory.OpenSession())
{
var parent = new Parent();
parent.AddChild(new Child { SimpleString = "new child from UI" });
using (var trx = createSession.BeginTransaction())
{
createSession.Save(parent);
trx.Commit();
id = parent.Id;
}
}
using (var firstGetSession = MsSqlSessionProvider.SessionFactory.OpenSession())
{
var result = firstGetSession.Get<Parent>(id);
Assert.IsNotNull(result);
}
using (var deleteSession = MsSqlSessionProvider.SessionFactory.OpenSession())
{
using (var trx = deleteSession.BeginTransaction())
{
deleteSession.Delete("from " + typeof(Parent).Name + " o where o.Id = :Id", id, NHibernateUtil.Int32);
trx.Commit();
}
}
using (var session = MsSqlSessionProvider.SessionFactory.OpenSession())
{
var result = session.Get<Parent>(id);
Assert.IsNull(result);
}
}
Which is failing on the deleteSession.Delete line after attempting the following SQL:
exec sp_executesql N'UPDATE [Child] SET ParentId = null WHERE ParentId = #p0',N'#p0 int',#p0=5
with:
NHibernate.Exceptions.GenericADOException : could not delete collection: [SaveUpdateOrCopyTesting.Parent.Children#5][SQL: UPDATE [Child] SET ParentId = null WHERE ParentId = #p0]
----> System.Data.SqlClient.SqlException : Cannot insert the value NULL into column 'ParentId', table 'SaveUpdateCopyTestingDB.dbo.Child'; column does not allow nulls. UPDATE fails.
The statement has been terminated.
Does anyone know what I've done wrong in my mappings, or know of a way to stop NHibernate from attempting to null the foreign key id?
Thanks
Dave
Try setting .Inverse() on the ParentMap's HasMany, so it looks like:
HasMany(x => x.Children)
.Not.LazyLoad()
.KeyColumn("ParentId").Cascade.AllDeleteOrphan().Inverse()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
I'm not sure if cascade works if you delete with HQL.
Try this:
var parent = deleteSession.Load<Parent>(id)
deleteSession.Delete(parent);
It's a pity that you don't have lazy loading. Load would not need the entity to be read from the database, it would just create a proxy in memory.
i'm trying to remove an item from a one to many list and have it persist in the database. Here are the entities i have defined:
public class SpecialOffer
{
public virtual int SpecialOfferID { get; set; }
public virtual string Title { get; set; }
public virtual IList<SpecialOfferType> Types { get; private set; }
public SpecialOffer()
{
Types = new List<SpecialOfferType>();
}
}
public class SpecialOfferType
{
public virtual SpecialOffer SpecialOffer { get; set; }
public virtual Type Type { get; set; }
public virtual int MinDaysRemaining { get; set; }
#region composite id requirements
public override bool Equals(object obj)
{
if (obj == null || !(obj is SpecialOfferType))
return false;
var t = (SpecialOfferType)obj;
return SpecialOffer.SpecialOfferID == t.SpecialOffer.SpecialOfferID && Type.TypeID == t.Type.TypeID;
}
public override int GetHashCode()
{
return (SpecialOffer.SpecialOfferID + "|" + Type.TypeID).GetHashCode();
}
#endregion
}
public class Type
{
public virtual int TypeID { get; set; }
public virtual string Title { get; set; }
public virtual decimal Price { get; set; }
}
With the following fluent mappings:
public class SpecialOfferMap : ClassMap<SpecialOffer>
{
public SpecialOfferMap()
{
Table("SpecialOffers");
Id(x => x.SpecialOfferID);
Map(x => x.Title);
HasMany(x => x.Types)
.KeyColumn("SpecialOfferID")
.Inverse()
.Cascade.All();
}
}
public class SpecialOfferTypeMap : ClassMap<SpecialOfferType>
{
public SpecialOfferTypeMap()
{
Table("SpecialOfferTypes");
CompositeId()
.KeyReference(x => x.SpecialOffer, "SpecialOfferID")
.KeyReference(x => x.Type, "TypeID");
Map(x => x.MinDaysRemaining);
}
}
public class TypeMap : ClassMap<Type>
{
public TypeMap()
{
Table("Types");
Id(x => x.TypeID);
Map(x => x.Title);
Map(x => x.Price);
}
}
The problem i have is that if i remove an item from the SpecialOffer.Types collection it successfully removes it from the list but when i try to save the session the change is not persisted in the database. I'm assuming this is something to do with the composite id on the join table since i have been able to do this successfully in the past with a standard id.
I'd appreciate it if someone could show me what i'm doing wrong. Thanks
I think you have to 1) Change the cascade setting on SpecialOffer.Types to Cascade.AllDeleteOrphan() and 2) set SpecialOfferType.SpecialOffer = null when you remove it from the collection. Since the collection is the inverse side of the relationship, the many-to-one reference to SpecialOffer on SpecialOfferType has to be set to null to make it an orphan, then Cascade.AllDeleteOrphan will cause it to be deleted.