Fluent Nhibernate generating incorrect query resulting in Cartesian product instead of single row - nhibernate

After trying numerous things, I am still unable to figure out the right way to query the following relationship using Fluent NHibernate.
This would otherwise have been an easier task if I had to write SQL queries. Hope to get some better advice to avoid N+1 issue and unoptimized auto-generated SQL queries.
I have the following relationship which goes something as below :
Tags can have media content (Images of various predefined sizes, videos, documents etc.) associated with it.
- 1 Tags can have multiple Media Items mapped to it (Lets's say images with dimension 32x32, 64x64, 600x100, 0 or more Videos)
- Every media item is mapped to a media description which helps in identifying the size and type of the media
- The same media item can be used by a different tag. Example, having a generic image for all tags which do not have any icons.
Entities:
Media
public class Media:IEntity
{
private ICollection<TagMedia> _tagMedia;
public virtual int Id { get; set; }
public virtual string FilePath { get; set; }
public virtual MediaType MediaType { get; set; }
public virtual ICollection<TagMedia> TagMedia
{
get { return _tagMedia?? (_tagMedia= new List<TagMedia>()); }
protected set { _tagMedia= value; }
}
}
Tag
public class Tag:IEntity
{
private ICollection<TagMedia> _tagMedia;
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<TagMedia> TagMedia
{
get { return _tagMedia?? (_tagMedia= new List<TagMedia>()); }
set { _tagMedia= value; }
}
}
TagMedia
public class TagMedia :IEntity
{
public virtual int Id { get; set; }
public virtual Media Media { get; set; }
public virtual Tag Tag { get; set; }
public virtual DateTime AddedOn { get; set; }
}
MediaType
public class MediaType:IEntity
{
public virtual int Id { get; set; }
public virtual string Description { get; set; }
}
Mappings
MediaMapping
public class MediaMapping : IAutoMappingOverride<Media>
{
public void Override(AutoMapping<Media> mapping)
{
mapping.Map(c => c.FileName).CustomSqlType("varchar(60)").Not.Nullable();
}
}
TagMapping
public class TagMapping : IAutoMappingOverride<Tag>
{
public void Override(AutoMapping<Tag> mapping)
{
mapping.HasMany<TagMedia>(c => c.TagMedia)
.KeyColumn("TagId")
.Cascade.SaveUpdate()
.BatchSize(25);
mapping.BatchSize(25);
mapping.DynamicUpdate();
mapping.DynamicInsert();
}
}
TagMediaMapping
public class TagMediaMapping : IAutoMappingOverride<TagMedia>
{
public void Override(AutoMapping<TagMedia> mapping)
{
mapping.Map(c=>c.AddedOn);
}
}
Query:
The following query gets the cartesian product of all media mapped to the tag and does not eliminate records other than "Icon-16x16". I expect the ORM to return no more than one row.
Any help would be highly appreciated.
_session.Query<Tag>()
.FetchMany(x => x.TagMedia)
.ThenFetch(x => x.Media)
.ThenFetch(x=>x.MediaType)
.Where(c => c.Id == id
&& c.TaxonomyMedia.Any(x=>x.Media.MediaType.Description== "Icon-16x16"))
.SingleOrDefault();
Generated SQL:
exec sp_executesql N'select *
from [Tag] Tag0_ left outer join [TagMedia] Tagme1_ on Tag0_.TagId=Tagme1_.TagId
left outer join [Media] media2_ on Tagme1_.MediaId=media2_.MediaId
left outer join [MediaType] mediatype3_ on media2_.MediaTypeId=mediatype3_.MediaTypeId
where Tag0_.TagId=#p0
and (exists (select Tagme4_.TagMediaId
from [TagMedia] Tagme4_ inner join [Media] media5_ on Tagme4_.MediaId=media5_.MediaId inner join [MediaType] mediatype6_ on media5_.MediaTypeId=mediatype6_.MediaTypeId where Tag0_.TagId=Tagme4_.TagId and mediatype6_.MediaTypeDescription=#p1))',N'#p0 int,#p1 nvarchar(4000)',#p0=102,#p1=N'Icon-16x16'
go

the SingleOrDefault function is a LINQ function and not implemented by NHibernate. Use .Take(1)
I think the solution is:
_session.Query<Tag>()
.FetchMany(x => x.TagMedia)
.ThenFetch(x => x.Media)
.ThenFetch(x=>x.MediaType)
.Where(c => c.Id == id
&& c.TaxonomyMedia.Any(x=>x.Media.MediaType.Description== "Icon-16x16"))
.Take(1)
.SingleOrDefault();

Related

Including an object with where clause in LINQ

I would like to have a LINQ query that is supposed to return all members with VitalSigns where event in the vital signs is equal to surgery.
My Member.cs class:
public class Member
{
public int Id { get; set; }
public string FullName { get; set; }
public ICollection<VitalSign> VitalSigns { get; set; }
public Member()
{
VitalSigns = new Collection<VitalSign>();
}
}
And my VitalSign.cs class is :
public class VitalSign
{
public int Id { get; set; }
public string Event { get; set; }
// relationships
public Member Member { get; set; }
public int MemberId { get; set; }
}
The LINQ query that I wrote is:
return await context. Members.Include(c => c.VitalSigns.Where(t => t.Event == "post surgery")).ToListAsync();
This returns a self-referenced loop. Because there are some data in the VitalSigns where the event is not equal to "post surgery". Am I writing the query wrong?
The query should be:
context.Members.Where(t => t.VitalSigns.Any(u => u.Event == "post surgery"))
.Include(c => c.VitalSigns)
.ToListAsync()
The Include() is only an hint on what tables should be loaded when the query is executed.
The query is something like:
all the members WHERE there is ANY (at least) one VitalSign with Event == post surgery
together with the Members you'll get, please INCLUDE the VitalSigns (the alternative is that they'll be lazily loaded when you try to access them)
return a List<> (ToListAsync) of the elements in an asynchronous way

NHibernate Query w/ criteria against collection items

I have the following configuration in the database.
Database Schema
I want to be able to query all the Individuals where they are either an employee or vendor. Of the examples I've seen I can't get any of them to work. The code doesn't throw an error, it just doesn't bring back any records.
Here are the DTO's
public class Individual
{
public virtual int Sid { get; set;}
public virtual string Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string MiddleInitial { get; set; }
public virtual ISet<Company> Companies { get; set; }
}
public class Company
{
private bool _inactive;
public virtual int Sid { get; set; }
public virtual string Name { get; set; }
public virtual IList<Individual> Individuals { get; set; }
public virtual bool Active
{
get { return !_inactive; }
set { _inactive = value; }
}
public virtual bool IsVendor { get; set; }
}
public class IndividualCompany
{
public virtual Individual Individual { get; set; }
public virtual Company Company { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
IndividualCompany key = obj as IndividualCompany;
if (key == null)
return false;
if (Individual.Sid == key.Individual.Sid && Company.Sid == key.Company.Sid)
return true;
return false;
}
public override int GetHashCode()
{
int hash = 13;
hash = 7 * hash * Individual.Sid.GetHashCode();
hash = 7 * hash * Company.Sid.GetHashCode();
return hash;
}
}
I have another function that uses these 3 tables and all my Individuals and the collection of Companies gets populated just fine so I know the mapping is working. I just don't know how to add criteria on the child record.
string sql = "from Individual i" +
" inner join fetch i.Companies";
return _session.CreateQuery(sql).List<Individual>();
Here's the query I'd like it to produce.
select i.*
from individual i inner join individual_company_assoc ica
on i.individual_sid = ica.individual_sid
inner join company c
on ica.company_sid = c.company_sid
where c.is_vendor = 0
Here's what I've tried:
public IList<Individual> Get(bool vendorsOnly)
{
try
{
return _session.CreateCriteria<Individual>()
.CreateAlias("Company", "c")
.Add(Restrictions.Eq("c.IsVendor", vendorsOnly))
.List<Individual>();
}
catch (NHibernate.HibernateException)
{
throw;
}
}
I'm not using FluentNHibernate. Any help would be greatly appreciated.
An nHibernate query will not automatically join with other tables, even if you reference columns of a joining table in the query.
You must explicitly state that the query should join with the Company table.
Here's an example which will work. It uses the QueryOver syntax, which is a type-safe wrapper around the Criteria API.
return _session
.QueryOver<Individual>()
.JoinQueryOver<Company>(i => i.Companies>())
.Where(c => c.IsVendor == vendorsOnly)
.TransformUsing(NHibernate.Transform.Transformers.DistinctRootEntity)
.List();

How to do a Fluent NHibernate ClassMap join with extra criteria

I'm having an issue trying to map a property to another table via a join but with extra criteria. My Code below is for the class I am trying to join from, I basically want to join ServerEventLog property by joining on a TestLog table but searching for a specific Message type id in the process which is defined in the test log table.
public class ExecutedTest
{
public virtual int Id { get; set; }
public virtual TestLog TestLog { get; set; }
public virtual TestLog ServerEventLog { get; set; }
}
And then the test log class is the class i want to map to
public class TestLog
{
public virtual int ID { get; set; }
public virtual string Message { get; set; }
public virtual int ExecutedTestId { get; set; }
}
I can get the SQL to generate something like the following using the class map below.
SELECT ...
FROM [ExecutedTest] executedte0_
inner join TestLog executedte0_1_ on
executedte0_.Id=executedte0_1_.ExecutedTestId
WHERE executedte0_.Id=?
public class ExecutedTestMap : ClassMap<ExecutedTest>
{
public ExecutedTestMap()
{
Id(x => x.Id);
References(x => x.TestLog).Column("LogId").Cascade.All();
this.Join(
"TestLog",
x =>
{
x.KeyColumn("ExecutedTestId");
});
}
}
But what I can't work out is how I get sql to be generated like the following through the join criteria (have highlighted the bit i cant generate inbetween the stared bit).
SELECT ...
FROM [ExecutedTest] executedte0_
inner join TestLog executedte0_1_ on
executedte0_.Id=executedte0_1_.ExecutedTestId
**** and executedte0_1_.MessageTypeId = 9 ****
WHERE executedte0_.Id=?
Any help would be greatly appreciated if someone knows how to achieve this through the ClassMap. Cheers
That's not what Join() is meant for. You could devide Testlog into
public virtual EventLog ServerEventLog { get; set; }
public class TestLog
{
public virtual int ID { get; set; }
public virtual string Message { get; set; }
}
public class EventLog : TestLog { }
public class TestLogMap : ClassMap<TestLog>
{
public TestLogMap()
{
Id(x => x.Id);
Map(x => x.Message, "Message");
DiscriminateSubclassesOnColumn("MessageTypeId", 1 /*MessageTypeId of TestLog*/);
}
}
public class EventLogMap : ClassMap<EventLog>
{
public EventLogMap()
{
DiscriminatorValue(9 /*MessageTypeId of EventLog*/);
}
}
or change the ServerEventLog to a Collection
// in ExecutedTestMap
HasMany(x => x.ServerEventLogs)
.KeyColumn("ExecutedTestId")
.Where("ExecutedTestId")
.KeyColumn("MessageTypeId = 9");

fluent nhibernate automap inferring incorrect key

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

get property count with entity using nhibernate

can I am hoping someone can point me to the right direction on how to get count of a property and the entity using a single trip to sql.
public class Category
{
public virtual int Id { get; private set; }
public virtual string Description { get; set; }
public virtual IList<Article> Articles { get; set; }
public virtual int ArticlesCount { get; set; }
public Category()
{
Articles=new List<Article>();
}
public virtual void AddArticle(Article article)
{
article.Category = this;
Articles.Add(article);
}
public virtual void RemoveArticle(Article article)
{
Articles.Remove(article);
}
}
public class CategoryMap:ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Description);
HasMany(x => x.Articles).KeyColumn("CategoryId").Fetch.Join();
Cache.ReadWrite();
}
}
My goal is to get the all Categories and the count of the associated articles if there is any.
I have tried this
ICriteria crit = session.CreateCriteria(typeof(Category));
crit.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Description"), "Description")
.Add(Projections.Count("Articles"), "ArticlesCount"));
crit.SetResultTransformer(Transformers.AliasToBean (typeof(Category)));
var aa=crit.List();
unfortunately the generated sql shows the count of the Category table not the Articles list.
Thanks
You could use a multi-query, multiple sql statements but it is one trip to the database.
Here is an example from the nhibernate documentation:
https://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html/performance.html
IMultiQuery multiQuery = s.CreateMultiQuery()
.Add(s.CreateQuery("from Item i where i.Id > ?")
.SetInt32(0, 50).SetFirstResult(10))
.Add(s.CreateQuery("select count(*) from Item i where i.Id > ?")
.SetInt32(0, 50));
IList results = multiQuery.List();
IList items = (IList)results[0];
long count = (long)((IList)results[1])[0];
Maybe not exactly what you were thinking.