Join table on custom columns - nhibernate

I want to join table based NOT on their Database-level relationship (PK, FK), but some custom criteria:
SELECT * FROM Users u inner join Docs d on u.Doc = d.ISBN
and map it to
public class User
{
public Doc Document { get; set; }
}
public class Doc
{
public Int64 Id {get; protected set}
public Int64 Isbn {get; set}
}
Is it possible with only mappings or do I need to call some method on Criteria API (which I use)?
I have tried the following:
public class UserMap : ClassMapping<User>
{
public UserMap()
{
Table("Users");
Id(x => x.Id, p => p.Column("Id"););
ManyToOne(x => x.Document, m =>
{
m.Column("Doc");
m.ForeignKey("ISBN");
m.Fetch(FetchKind.Join);
m.Access(Accessor.Property);
m.Lazy(LazyRelation.NoLazy);
m.NotNullable(true);
});
}
}
public class DocMap : ClassMapping<Doc>
{
public DocMap()
{
Table("Docs");
Id(x => x.Id, p =>
{
p.Column("Id");
p.Generator(Generators.GuidComb);
});
Property(x => x.Isbn, p =>
{
p.Column("ISBN");
p.NotNullable(true);
});
}
}
//2.Criteria API
var criteria = Session.CreateCriteria("User")
// ...
criteria.CreateCriteria("Document", "Docs", JoinType.InnerJoin, docCriterion);
but the result is:
SELECT * FROM Users u
inner join Docs d on u.Doc=d.Id /* docCriterion */
left outer join Docs d2 on u.Doc=d.Id

try this
criteria.CreateCriteria("Users")
.CreateCriteria("Document").Add(Restrictions.Eq("Isbn ", ?);
in your query u.Doc = d.ISBN is not correct, cause Doc is Doc entity and ISBN is number

Related

nhibernate child collection limitation

I have these classes:
public class Document
{
public Document()
{
Descriptions = new List<Descriptions>();
}
public virtual int Id { get; set; }
public virtual IList<DocumentDescription> Descriptions { get; set; }
}
public class DocumentDescription
{
public virtual int DocumentId { get; set; }
public virtual int LanguageId { get; set; }
}
and mappings:
public DocumentMap()
{
Id(x => x.Id);
HasMany(x => x.Descriptions).KeyColumn("DocumentId");
}
public DocumentDescriptionMap()
{
CompositeId()
.KeyProperty(x => x.DocumentId)
.KeyProperty(x => x.LanguageId);
}
my query is:
var query = Session.QueryOver<Document>().Where(x => x.Id.IsIn(documentIds)).List();
I need a query over solution to restrict DocumentDescriptions by few languages, which I will get run-time. I don't want to get all DocumentDescriptions for one Document (only few). Is it possible to set filter/limitation for a child collection?
So, I find out how to add additional join clause to my query:
DocumentDescription dd = null;
ICriterion criterion = Restrictions.On<DocumentDescription>(x => x.LanguageId).IsIn(languageIds.ToArray());
var query = Session.QueryOver<Document>().Where(x => x.Id.IsIn(documentIds));
query.Left.JoinQueryOver(x => x.Descriptions, () => dd, criterion);
SQL:
SELECT * FROM tDocument
LEFT OUTER JOIN tDocumentDescription ON tDocumentDescription.DocumentId = tDocument.Id AND tDocumentDescription.LanguageId IN (#languageIds)
WHERE tDocument.Id IN (#documentIds)

NHibernate: Projecting child entities into parent properties throws an exception

I have the following parent entity Department which contains a collection of child entities Sections
public class Department
{
private Iesi.Collections.Generic.ISet<Section> _sections;
public Department()
{
_sections = new HashedSet<Section>();
}
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual ICollection<Section> Sections
{
get { return _sections; }
}
public virtual int Version { get; set; }
}
public partial class Section
{
public Section()
{
_employees = new HashedSet<Employee>();
}
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual Department Department { get; protected set; }
public virtual int Version { get; set; }
}
I would like to transform (flatten) it to the following DTO
public class SectionViewModel
{
public string DepartmentName { get; set; }
public string SectionName { get; set; }
}
Using the following code.
SectionModel sectionModel = null;
Section sections = null;
var result = _session.QueryOver<Department>().Where(d => d.Company.Id == companyId)
.Left.JoinQueryOver(x => x.Sections, () => sections)
.Select(
Projections.ProjectionList()
.Add(Projections.Property<Department>(d => sections.Department.Name).WithAlias(() => sectionModel.DepartmentName))
.Add(Projections.Property<Department>(s => sections.Name).WithAlias(() => sectionModel.SectionName))
)
.TransformUsing(Transformers.AliasToBean<SectionModel>())
.List<SectionModel>();
I am however getting the following exception: could not resolve property: Department.Name of: Domain.Section
I have even tried the following LINQ expression
var result = (from d in _session.Query<Department>()
join s in _session.Query<Section>()
on d.Id equals s.Department.Id into ds
from sm in ds.DefaultIfEmpty()
select new SectionModel
{
DepartmentName = d.Name,
SectionName = sm.Name ?? null
}).ToList();
Mappings
public class DepartmentMap : ClassMapping<Department>
{
public DepartmentMap()
{
Id(x => x.Id, m => m.Generator(Generators.GuidComb));
Property(x => x.Name,
m =>
{
m.Length(100);
m.NotNullable(true);
});
Set(x => x.Sections,
m =>
{
m.Access(Accessor.Field);
m.Inverse(true);
m.BatchSize(20);
m.Key(k => { k.Column("DeptId"); k.NotNullable(true); });
m.Table("Section");
m.Cascade( Cascade.All | Cascade.DeleteOrphans);
},
ce => ce.OneToMany());
}
}
public class SectionMap : ClassMapping<Section>
{
public SectionMap()
{
Id(x => x.Id, m => m.Generator(Generators.GuidComb));
Property(x => x.Name,
m =>
{
m.Length(100);
m.NotNullable(true);
});
ManyToOne(x => x.Department,
m =>
{
m.Column("DeptId");
m.NotNullable(true);
});
}
}
But this throws a method or operation is not implemented.
Seeking guidance on what I am doing wrong or missing.
NHibernate doesn't know how to access a child property's child through the parent entity. A useful thing to remember about QueryOver is that it gets translated directly into SQL. You couldn't write the following SQL:
select [Section].[Department].[Name]
right? Therefore you can't do the same thing in QueryOver. I would create an alias for the Department entity you start on and use that in your projection list:
Department department;
Section sections;
var result = _session.QueryOver<Department>(() => department)
.Where(d => d.Company.Id == companyId)
.Left.JoinQueryOver(x => x.Sections, () => sections)
.Select(
Projections.ProjectionList()
.Add(Projections.Property(() => department.Name).WithAlias(() => sectionModel.DepartmentName))
.Add(Projections.Property(() => sections.Name).WithAlias(() => sectionModel.SectionName))
)
.TransformUsing(Transformers.AliasToBean<SectionModel>())
.List<SectionModel>();
I noticed in your comment you'd like an order by clause. Let me know if you need help with that and I can probably come up with it.
Hope that helps!
This may be now fixed in 3.3.3. Look for
New Feature
[NH-2986] - Add ability to include collections into projections
Not sure but if this is your problem specifically but if you are not using 3.3.3 then upgrade and check it out.
Aslo check out the JIRA
Have you tried a linq query like
from d in Departments
from s in d.Sections
select new SectionModel
{
DepartmentName = d.Name,
SectionName = s == null ? String.Empty : s.Name
}

nHibernate one-to-many best practice persintence to avoid INSERT NULL

I've a One to Many mapping.
An entity with a collection persisted on two table 1:N
This is the class and the mapping:
public class Test
{
public virtual string Id { get; set; }
public virtual string Description { get; set; }
public virtual IList<TestItem> Items { get; set; }
public Test()
{
Items = new List<TestItem>();
}
}
public class TestItem
{
public virtual string Id { get; set; }
public virtual Test Test { get; set; }
public virtual string ItemCode { get; set; }
public virtual string ItemData { get; set; }
}
public class TestMap : ClassMapping<Test>
{
public TestMap()
{
Id(x => x.Id, m => m.Column("IDTest"));
Bag(x => x.Items, c =>
{
c.Key(k =>
{
k.NotNullable(true);
k.Column("IDTest");
});
c.Cascade(Cascade.DeleteOrphans);
c.Lazy(CollectionLazy.NoLazy);
c.Inverse(true);
}, r => r.OneToMany(m =>
{
m.NotFound(NotFoundMode.Exception);
m.Class(typeof(TestItem));
}));
}
}
public class TestItemMap : ClassMapping<TestItem>
{
public TestItemMap()
{
Id(x => x.Id, m => m.Column("IDTestItem"));
ManyToOne(x => x.Test, m =>
{
m.Column("IDTest");
m.NotNullable(false);
m.Lazy(LazyRelation.NoLazy);
});
Property(x => x.ItemCode);
Property(x => x.ItemData);
}
}
And this is the code.
If I remove the marked line I get the error.
var session = SessionFactory.OpenSession();
using (var tr = session.BeginTransaction())
{
var test = new Test();
test.Id = "T01";
test.Description = "Desc TEST 01";
session.SaveOrUpdate(test); // If Removed Get INSERT ERROR on TestItem
var item = new TestItem { Id = "NEW01", ItemCode = "A", Test = test, ItemData = "New T01A" };
test.Items.Add(item);
session.SaveOrUpdate(item);
session.SaveOrUpdate(test);
tr.Commit();
}
My question is:
Is this the best practice to persist a "one to many" relation ?
It is possible to use the code below and save all only saving the header Row (test) and automatically cascading all inserts on the child table??
var session = SessionFactory.OpenSession();
using (var tr = session.BeginTransaction())
{
var test = new Test();
test.Id = "T01";
test.Description = "Desc TEST 01";
//session.SaveOrUpdate(test); // If Removed Get INSERT ERROR on TestItem
var item = new TestItem { Id = "NEW01", ItemCode = "A", Test = test, ItemData = "New T01A" };
test.Items.Add(item);
//session.SaveOrUpdate(item);
session.SaveOrUpdate(test);
tr.Commit();
}
I suppose I need to change something on my mapping code but i do not undestand what!!!
Many days of work, googling, stackoverflowing but nothing change the result.
Thank you!!!
Q: Is this the best practice to persist a "one to many" relation ?
A: If you're talking about a "composition" relationship, the cascading approach can be a good choice.
Q: It is possible to use the code below and save all only saving the header Row (test) and automatically cascading all inserts on the child table??
A: Yes it is possible. But your code should look like this:
public class TestMap : ClassMapping<Test>
{
public TestMap()
{
Id(x => x.Id, m => m.Column("IDTest"));
Bag(x => x.Items, c =>
{
c.Key(k =>
{
k.NotNullable(true);
k.Column("IDTest");
});
c.Cascade(Cascade.All | Cascade.DeleteOrphans); //<- My suggestion
c.Lazy(CollectionLazy.NoLazy);
c.Inverse(true);
}, r => r.OneToMany(m =>
{
m.NotFound(NotFoundMode.Exception);
m.Class(typeof(TestItem));
}));
}
}

NHibernate one-to-one: null id generated for AccountDetail

I got an exception "null id generated for AccountDetail" when mapping one-to-one relationship by using many-to-one with unique constraint.
Here's my SQL tables:
Account(Id, Name)
AccountDetail(AccountId, Remark)
AccountId is both primary and foreign key.
Here's my Domain Model (Account and AccountDetail):
public class Account
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual AccountDetail Detail { get; set; }
public Account()
{
Detail = new AccountDetail
{
Account = this
};
}
}
public class AccountDetail
{
public virtual int AccountId { get; set; }
public virtual Account Account { get; set; }
public virtual string Remark { get; set; }
}
Mapping (NHibenrate 3.3 mapping by code):
class AccountMap : ClassMapping<Account>
{
public AccountMap()
{
Table(typeof(Account).Name);
Id(c => c.Id, m => m.Generator(Generators.Native));
Property(c => c.Name);
OneToOne(c => c.Detail, m =>
{
m.Constrained(true);
m.Cascade(Cascade.All);
m.PropertyReference(typeof(AccountDetail).GetPropertyOrFieldMatchingName("Account"));
});
}
}
class AccountDetailMap : ClassMapping<AccountDetail>
{
public AccountDetailMap()
{
Table(typeof(AccountDetail).Name);
Id(c => c.AccountId, m =>
{
m.Column("AccountId");
m.Generator(Generators.Foreign<AccountDetail>(x => x.Account));
});
Property(c => c.Remark);
ManyToOne(c => c.Account, m =>
{
m.Column("AccountId");
m.Unique(true);
});
}
}
BTW: Can I remove the AccountId property in AccountDetail? That is, only use the Account property. Using both AccountId and Account properties in AccountDetail class looks not so object-oriented.
Thanks!
I can't say what's actually wrong, but comparing with my working one-to-one relation, I would map it like this:
class AccountMap : ClassMapping<Account>
{
public AccountMap()
{
Table(typeof(Account).Name);
// creates a auto-counter column "id"
Id(c => c.Id, m => m.Generator(Generators.Native));
// doesn't require a column, one-to-one always means to couple primary keys.
OneToOne(c => c.Detail, m =>
{
// don't know if this has any effect
m.Constrained(true);
// cascade should be fine
m.Cascade(Cascade.All);
});
}
}
class AccountDetailMap : ClassMapping<AccountDetail>
{
public AccountDetailMap()
{
Id(c => c.AccountId, m =>
{
// creates an id column called "AccountId" with the value from
// the Account property.
m.Column("AccountId");
m.Generator(Generators.Foreign(x => x.Account));
});
// should be one-to-one because you don't use another foreign-key.
OneToOne(c => c.Account);
}
}

NHibernate queryover with many-to-many

The situation is following:
1. Product belongs to many Categories,
2. Category has many Products.
class Product
{
public int Id { get; set; }
public List<Category> Categories { get; set; }
}
class Category
{
public int Id { get; set; }
public List<Product> Products { get; set; }
}
How to have all products, where each of them belongs to category with id = 2 and 3 and 4. How to do with queryover?
At the moment I use dynamically created hql:
select p from Product p where
exists (select c.id from p.Categories c where c.Id = 2)
and exists (select c.id from p.Categories c where c.Id = 3)
and exists (select c.id from p.Categories c where c.Id = 4)
And the mapping is:
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id);
HasManyToMany(x => x.Categories)
.Table("product_category")
.ParentKeyColumn("product_id")
.ChildKeyColumn("category_id");
}
}
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(x => x.Id);
HasManyToMany(x => x.Products)
.Table("product_category")
.ParentKeyColumn("category_id")
.ChildKeyColumn("product_id");
}
}
Try this, maybe it's help:
Category categoryAlias = null;
session.QueryOver<Product>()
.JoinAlias(product => product.Categories, () => categoryAlias)
.WhereRestrictionOn(() => categoryAlias.Id).IsIn(new [] { 2, 3, 4 })
.List();