I have the following entities defined in my Entity Model:
public class MyContainer
{
public virtual ICollection<Base> Subs { get; set; }
}
public abstract class Base
{
public virtual Guid Id { get; set; }
}
public abstract class Sub1 : Base
{
public virtual int MyValue { get; set; }
}
public abstract class Sub2 : Base
{
public virtual int MyValue { get; set; }
}
and the following FluentNHibernate mappings for the above entities:
public sealed class BaseMap : ClassMap<Base>
{
public BaseMap()
{
Table("BaseTable");
Id(e => e.Id);
}
}
public sealed class Sub1Map : SubClassMap<Sub1>
{
public Sub1Map()
{
Table("Sub1Table");
KeyColumn("BaseId");
Map(e => e.Myvalue);
}
}
public sealed class Sub2Map : SubClassMap<Sub2>
{
public Sub2Map()
{
Table("Sub2Table");
KeyColumn("BaseId");
Map(e => e.Myvalue);
}
}
When I run the following HQL:
select sub
from MyContainer container
join fetch container.Subs sub
where sub.MyValue = :p1
the SQL generated only applies a constraint in the WHERE clause for one of the sub-classes, however, the generated JOINS are correct, i.e., the following skeletal SQL is generated:
SELECT ...
FROM BaseTable bt
INNER JOIN Sub1Table st1 ON ...
INNER JOIN Sub2Table st2 ON ...
WHERE st1.MyValue = #p1
where as I'm expecting an additional OR in the WHERE clause:
SELECT ...
FROM BaseTable bt
INNER JOIN Sub1Table st1 ON ...
INNER JOIN Sub2Table st2 ON ...
WHERE st1.MyValue = #p1
OR st2.MyValue = #p2
Is there something I'm missing, or is there a way to re-write the HQL so that I can reference each sub-class in the WHERE clause and apply the constraint directly (assuming that it would then generate the additional constraint in the generated SQL)?
I'm using NHibernate 3.0.0.
MyValue should be declared and mapped in Base. It is not possible to filter base class by properties that are defined in subclasses without casting to the particular class:
where (b.class = Sub1 and b.MyValue = :p1) or (b.class = Sub2 and b.MyValue = :p1)
EDIT:
Or in FNH1.2 union subclassing can be used:
public class BaseMap : ClassMap<Base>
{
public BaseMap()
{
UseUnionSubclassForInheritanceMapping();
Table("BaseTable");
Id(e => e.Id);
}
}
Related
Consider following class structure.
public abstract class Animal
{
public string SomeData { get; set; }
public IList<AnimalProperty> AnimalProperties { get; set; }
public IList<OtherAnimalProperty> OtherAnimalProperties { get; set; }
}
public class Dog: Animal
{
public DogProperty DogProperties { get; set; }
}
With following mapping for the Dog class
HasOne(x => x.DogProperties).ForeignKey("Id");
I now have following query to retrieve a list of objects of type Animal.
list = session.QueryOver<Animal>()
.WhereRestrictionOn(e => e.SomeData).IsIn(someList.ToArray())
.Fetch(e => e.AnimalProperties).Default
.Fetch(e => e.OtherAnimalProperties).Default
.List();
My list now contains an object of type Dog (as expected), but the DogProperties property is null. Meaning, the one-to-one class defined in the mapping is not initialized.
However, NHibernate generated a nice joined query that retrieves also the DogProperty data from the database. It just does not create the class through this query.
SELECT this_.Id as Id0_1_, this_.Name as Name0_1_, this_.DogName as DogName0_1_,
this_.AnimalType as AnimalType0_1_, dogpropert2_.Id as Id1_0_,
dogpropert2_.MyProperty as MyProperty1_0_
FROM [Animal] this_ left outer join [DogProperties] dogpropert2_
on this_.Id=dogpropert2_.Id
Can anyone elaborate what I'm missing here? Or is this a bug/default behavior of NHibernate QueryOver?
I use NH 3.3.1.
Suppose simple classes:
public class TestBase
{
public virtual int Id { get; set; }
public virtual string A { get; set; }
}
public class Test : TestBase
{
public virtual string B { get; set; }
}
and mappings for them:
public sealed class TestBaseMap : ClassMap<TestBase>
{
public TestBaseMap()
{
this.Polymorphism.Explicit();
this.Id(a => a.Id).GeneratedBy.Identity();
this.Map(a => a.A);
}
}
public sealed class TestMap :SubclassMap<Test>
{
public TestMap()
{
this.Map(a => a.B);
}
}
Even with Polymorphism.Explicit() specified, NH still left joins Test when querying for TestBase.
var a = this.Session.Get<TestBase>(1);
I don't really need this joining 'cuz will have lots of subclasses.
I checked xml generated by fluent. it's ok, "explicit" clause is there. What am i doing wrong?
I guess that explicit polymorphism is only used in queries, not for session.Get. But I couldn't find any references for this.
Try to not query for the base class, but always have an concrete subclass (which is in most cases a better design anyway):
public abstract class TestBase
{
public virtual int Id { get; set; }
public virtual string A { get; set; }
}
public class TestA : TestBase
{
public virtual string B { get; set; }
}
public class TestB : TestBase
{
public virtual string B { get; set; }
}
var a = this.Session.Get<TestA>(1);
Ok. I've got it. As Stefan supposed, i made abstract TestBase. But since I really needed to query TestBase table without many left joins, i introduced stub class:
public class TestStub : TestBase
{
// nothing
}
This class is absolutely empty. Map:
public sealed class TestStubMap : SubclassMap<TestStub>
{
public TestStubMap()
{
this.Table("TestBase");
this.KeyColumn("Id");
}
}
Now I can query:
var a = this.Session.Get<TestStub>(1)
It produces only one join (TestBase join TestBase). So now i can get my TestBase from db without overhead. I don't like hacks, but if built-in logic doesn't work (polymorphism=explicit), what is left to do.
I'm wanting to have a 1 to many relationship in NHibernate where the Child table only has access to it's parentsId. Or the foreign key in the DB.
I've tried the following setup:
public class ParentTable
{
public ParentTable()
{
_childRecords = new List<ChildTable>();
}
public virtual int ParentId { get; set; }
private IList<ChildTable> _childRecords;
public virtual IEnumerable<ChildTable> ChildRecords
{
get { return _childRecords; }
}
public void AddChildTable(string value)
{
_childRecords.Add(new ChildTable{ StringField = value });
}
}
public class ChildTable
{
public virtual int ChildTableId { get; set; }
public virtual string StringField { get; set; }
public virtual int ParentId { get; set; }
}
Mappings:
public class ParentTableMap : ClassMap<ParentTable>
{
public ParentTableMap()
{
Not.LazyLoad();
Id(x => x.ParentId);
HasMany(x => x.ChildRecords)
.Not.LazyLoad()
.KeyColumn("ParentId").Cascade.All()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
}
}
public class ChildTableMap : ClassMap<ChildTable>
{
public ChildTableMap()
{
Not.LazyLoad();
Id(x => x.ChildTableId);
Map(x => x.StringField);
Map(x => x.ParentId).Not.Nullable();
}
}
The following test fails as it's trying to insert 0 into the ParentId column?
[TestFixture]
public class Tests
{
[Test]
public void SaveOrUpdate_ParentWithChildren_WillCreateParentWithChildRecordsHavingMatchingParentId()
{
int id;
using (var sessionForInsert = SessionProvider.SessionFactory.OpenSession())
{
using (var trx = sessionForInsert.BeginTransaction())
{
//Assign
var parent = new ParentTable();
parent.AddChildTable("Testing");
parent.AddChildTable("Testing2");
sessionForInsert.SaveOrUpdate(parent); // Fails here with DB constraint error
id = parent.ParentId;
}
}
using (var sessionForSelect = SessionProvider.SessionFactory.OpenSession())
{
//Action
var result = sessionForSelect.Get<ParentTable>(id);
Assert.AreEqual(id, result.ParentId);
Assert.AreEqual(id, result.ChildRecords.First().ParentId);
Assert.AreEqual(id, result.ChildRecords.Last().ParentId);
}
}
}
This is what it's trying to do:
exec sp_executesql N'INSERT INTO ChildTable (StringField, ParentId) VALUES (#p0, #p1); select SCOPE_IDENTITY()',N'#p0 nvarchar,#p1 int',#p0='Testing;,#p1=0
I realise I could set-up a reference to the Parent Class in the Child Class. However I'd like to avoid this if at all possible, due to circular references and the problems that will cause when serializing and de-serializing these classes.
Has anyone successfully set-up and 1 to many relationship like the above?
Thanks
Dave
I think you either need to:
Make the ParentId on ChildTable nullable, or
Change your id generators to something NHibernate can generate.
The second option is nice. Switch to Guid.Comb for your id's. There's a restriction on what object relational mappers can do. Specifically, it is recommended to let NHibernate generate the id's instead of the database. I think this (long) blog post explains it in detail: http://fabiomaulo.blogspot.com/2009/02/nh210-generators-behavior-explained.html.
Good luck!
The problem is that you are attempting to insert a parent and its children in one operation. To do this, NHibernate wants to insert the child records with a null ParentId then update ParentId after the parent record is inserted. This foreign key constraint causes this to fail.
The best solution is to map the relationship from child to parent. You don't have to publicly expose the parent, you could just expose its ParentId as int? if desired.
If that's unacceptable, you should be able to accomplish this by changing the order of operations. First, I would require the ParentId in ChildTable's constructor. Then change the operation order in the test to get it to pass.
public class ChildTable
{
public ChildTable(int parentId) { ParentId = parentId; }
public virtual int ChildTableId { get; set; }
public virtual string StringField { get; set; }
public virtual int ParentId { get; private set; }
}
using (var trx = sessionForInsert.BeginTransaction())
{
//Assign
var parent = new ParentTable();
sessionForInsert.Save(parent);
sessionForInsert.Flush(); // may not be needed
parent.AddChildTable("Testing");
parent.AddChildTable("Testing2");
trx.Commit();
id = parent.ParentId;
}
EDIT:
public class ChildTable
{
private ParentTable _parent;
public ChildTable(Parent parent) { _parent = parent; }
public virtual int ChildTableId { get; set; }
public virtual string StringField { get; set; }
public virtual int? ParentId
{
get { return _parent == null : null ? _parent.ParentId; }
}
}
public class ChildTableMap : ClassMap<ChildTable>
{
public ChildTableMap()
{
Not.LazyLoad();
Id(x => x.ChildTableId);
Map(x => x.StringField);
// From memory, I probably have this syntax wrong...
References(Reveal.Property<ParentTable>("Parent"), "ParentTableId")
.Access.CamelCaseField(Prefix.Underscore);
}
}
I am having trouble using CreateCriteria to add an outer join to a criteria query while using Fluent NHibernate with automapping.
Here are my entities -
public class Table1 : Entity
{
virtual public int tb1_id { get; set; }
virtual public DateTime tb1_date_filed { get; set; }
.
.
.
virtual public IList<Table2> table2 { get; set; }
}
public class Table2: Entity
{
public virtual int tb2_id { get; set; }
public virtual int tb2_seqno { get; set; }
.
.
.
public virtual Table2 table2 { get; set; }
}
I try to use the following to add an outer join to my criteria query -
CreateCriteria("Table2", NHibernate.SqlCommand.JoinType.LeftOuterJoin);
But I am getting an error -
{"EIX000: (-217) Column (tbl1_id) not found in any table in the query (or SLV is undefined)."}
So it seems that it is trying to automatically set the id of the second table, but doesn't know what to set it to. Is there a way that I can specifically set the id? Here is my Session -
var persistenceModel = AutoMap.AssemblyOf<Table1>()
.Override<Table1>(c => {
c.Table("case");
c.Id(x => x.id).Column("tbl1_id");
})
.Where(t => t.Namespace == "MyProject.Data.Entities")
.IgnoreBase<Entity>();
Hope that makes some sense. Thanks for any thoughts.
You seem to have answered your own question so I'm just going to spout some recommendations...
One of the nice things about fluent nhibernate is that it follows conventions to automatically create mappings. Your entities seem to be very coupled to the names of your database tables.
In order to map to a different database convention while keeping idealistic names for entities and columns you can use some custom conventions:
public class CrazyLongBeardedDBATableNamingConvention
: IClassConvention
{
public void Apply(IClassInstance instance)
{
instance.Table("tbl_" + instance.EntityType.Name.ToLower());
}
}
public class CrazyLongBeardedDBAPrimaryKeyNamingConvention
: IIdConvention
{
public void Apply(IIdentityInstance instance)
{
string tableShort = TableNameAbbreviator.Abbreviate(instance.EntityType.Name);
instance.Column(tableShort + "_id");
}
}
class CrazyLongBeardedDBAColumnNamingConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
string name = Regex.Replace(
instance.Name,
"([A-Z])",
"_$1").ToLower();
var tableShort = TableNameAbbreviator.Abbreviate(instance.EntityType.Name);
instance.Column(tableShort + name);
}
}
TableNameAbbreviator is a class that would know how to abbreviate your table names.
These would map from:
public class Table1 : Entity
{
virtual public int Id { get; set; }
virtual public DateTime DateFiled { get; set; }
}
To a table like:
CREATE TABLE tbl_table1 {
tbl1_id INT PRIMARY KEY
tbl1_date_filed datetime
}
I added a HasMany option to the override for my first table to define the relationship to my second table. I then added an override for my second table which defines the id column for that table.
Thank
I have this mapping:
public sealed class EntityMap : ClassMap<Entity>
{
public EntityMap ()
{
...
Component(entity => entity.StateHistory,
m => m.HasMany<HistoryItem<EntityState>>
(Reveal.Property<EntityStateHistory>("Items"))
.Table("EntityStateHistory")
.KeyColumn("IDEntity")
.Component
( m2 =>
{
m2.Map(esh => esh.Item, "State")
.CustomType(typeof(EntityState)).Not.Nullable();
m2.Map(esh=> esh.Date, "TransitionDate").Not.Nullable();
}
)
.Cascade.AllDeleteOrphan());
...
}
}
I want to make a query where i get a specific date (TransitionDate) in an EntityStateHistory entry.
If it was possible it would be something like this:
"select e from Entity e where e.StateHistory.Items.Date = :date"
but i can't do this, i don't know how i can access an History record date, knowing that History is a component that has itself a collection of components, and i need to access one of the properties of those components in the collection.
The object model is something like this:
public class Entity
{
private int ID {get; set;}
etc
...
public virtual EntityStateHistory StateHistory{ get; private set; }
}
public class EntityStateHistory: History<EntityState>
{
//some wraped properties and methods
public IList<HistoryItem<EntityState>> StateRecords
{
get { return base.Items;}
}
public bool ContainsStateRecord(EstadoOT state)
{
return base.Items.Count(i => i.Item.Equals(state)) > 0;
}
etc ...
}
public class History<T>
{
protected virtual IList<HistoryItem<T>> Items { get; private set; }
public History()
{
Items = new List<HistoryItem<T>>();
}
protected virtual HistoryItem<T> AddHistoryItem(DateTime data, T item)
{
...
}
}
public class ItemHistory<T>
{
#region NHibernate
private int ID { get; set; }
#endregion
public virtual DateTime Date { get; private set; }
public virtual T Item { get; private set; }
...
}
I know i will probably have to change the mapping for entity, and create a map for EntityStateHistory, but i would like to avoid that, because that means one more table. The way i have it is the most canonical mapping because has no need to map HistoryItem or EntityStateHistory, that means i only use one table to map EntityStateHistory:
Table EntitiStateHistory:
-IDEntity
-TransitionDate
-State
So is it possible with the current mapping to query the database for a Entity that has a specific history record date?
Thanks
It was so easy...
The problem was in the path:
"select e from Entity e where e.StateHistory.Items.Date = :date"
if i do this:
"select e from Entity e join e.StateHistory.Items i where i.Date = :date"
it works