I have a value object like this:
public class PersonMapping : ComponentMap<Person>
{
public PersonMapping()
{
Map(x => x.FirstName).Not.Nullable().Length(96);
Map(x => x.LastName).Not.Nullable().Length(96);
Map(x => x.MiddleName).Nullable().Length(96);
Map(x => x.NickName).Nullable().Length(64);
}
}
I create a query like so:
var owner = new Person("Anneliese", "ObjectOfficer_1");
var query = NhSession.Query<ServiceContract>()
.Where(s => s.ObjectOfficer.Manager == owner);
contracts = query.ToList();
This creates an instance of Person that has a null MiddleName and Nickname. NHibernate creates a query like so:
select .....
from .....
where (objectoffi1_.FirstName = #p0
and objectoffi1_.LastName = #p1
and objectoffi1_.MiddleName = #p2
and objectoffi1_.NickName = #p3);
#p0 = 'Anneliese' [Type: String (4000)],
#p1 = 'ObjectOfficer_1' [Type: String (4000)],
#p2 = NULL [Type: String (4000)],
#p3 = NULL [Type: String (4000)]
No records match which is wrong.
When I change the generated query and replace the parameters with values like so:
...
where (objectoffi1_.FirstName = 'Anneliese' and objectoffi1_.LastName = 'ObjectOfficer_1'
and objectoffi1_.NickName is null and objectoffi1_.MiddleName is null)
it returns a correct number of results. Does anybody know how to deal with this? If NHibernate sends field = NULL instead field is null to MS SQL Server, no results are returned since it is wrong TSQL. Any ideas?
Turns out, that it is not possible to put value objects with nullable properties in a where clause. You have to care about these potential null values yourself in your code. Once you compare the fields directly field by field it works. Here the 'correct' version:
var query = NhSession.Query<ServiceContract>()
.Where(s => s.ObjectOfficer.Manager.FirstName == owner.FirstName
&& s.ObjectOfficer.Manager.LastName == owner.LastName);
contracts = query.ToList();
I think this is more than just inconvenient and I do not know if other ORMs such as EF have the same problem. Value Objects are an important building block of DDD.
Related
I have a main VendorProfile table and a 1-many VendorHistory table that contains status codes and date stamps. The query below works at retrieving only the latest status (status code and date) for each vendor. However, the view allows the user to select checkboxes of any of the status codes to filter the view. So I need to add a where clause that matches ANY of the checkbox StatusSelections.
Model Diagram
public IEnumerable<BrowseStatusModel> BrowseByStatus(int[] StatusSelections)
{
IQueryable<BrowseStatusModel> query = _db.VendorProfiles
.Include("VendorStatusHistory")
.Include("StatusCodes")
.Select(s => new BrowseStatusModel
{
ProfileID = s.ProfileID,
Name = s.Name,
CompanyName = s.CompanyName,
CompanyDBA = s.CompanyDBA,
DateCreated = s.DateCreated,
Status = s.VendorStatusHistories.OrderByDescending(o => o.DateCreated).FirstOrDefault().Id,
StatusDate = s.VendorStatusHistories.OrderByDescending(o => o.DateCreated).FirstOrDefault().DateCreated
})
.OrderBy(x => x.ProfileID);
foreach (int status in StatusSelections)
{
query = query.Where(x => x.Status == status);
}
return query;
}
The above foreach loop works but, unfortunately creates AND condition where ALL selections must be true instead of ANY. I figured I would have to use a where clause with the following in some way but have been unsuccessful at the correct syntax.
.AsQueryable().Any();
Use contains in the place of that foreach loop
query = query.Where(x => StatusSelections.Contains(x.Status))
Basically I crossed the same problem of Linq provider in this linq-to-nhibernate-produces-unnecessary-joins
List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
where compset.HeadLine == true
&& compset.A.B.CurrentSeason == true
select (new Competitions
{
CompetitionSetID = compset.CompetitionSetID,
Name = compset.Name,
Description = compset.Description,
Area = compset.Area,
Type = compset.Type,
CurrentSeason = compset.A.B.CurrentSeason,
StartDate = compset.StartDate
}
)).ToList();
Which leads to duplicated join in its generated SQL
SELECT fwbcompeti0_.competitionsetid AS col_0_0_,
fwbcompeti0_.name AS col_1_0_,
fwbcompeti0_.DESCRIPTION AS col_2_0_,
fwbcompeti0_.area AS col_3_0_,
fwbcompeti0_.TYPE AS col_4_0_,
fwbseason3_.currentseason AS col_5_0_,
fwbcompeti0_.startdate AS col_6_0_
FROM fwbcompetitionset fwbcompeti0_
INNER JOIN A fwbcompeti1_
ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
INNER JOIN A fwbcompeti2_
ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid
INNER JOIN B fwbseason3_
ON fwbcompeti2_.seasonid = fwbseason3_.seasonid
WHERE fwbcompeti0_.headline = #p0
AND fwbseason3_.currentseason = #p1
Notice these joins, which are totally duplicated and also affect my SQL Server's performence.
INNER JOIN A fwbcompeti1_
ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
INNER JOIN A fwbcompeti2_
ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid
Update1
In the NHibernate 3.2, this LiNQ bug is still valid, and I could not find a simple and reasonable Linq solution.
So I used QueryOver + JoinAlias + TransformUsing finishing the job, workds perfect to me.
FWBCompetitionSet compset = null;
FWBCompetitionSeason compseason = null;
FWBSeason season = null;
IList<Competitions> dtoCompetitions;
dtoCompetitions = session.QueryOver<FWBCompetitionSet>(() => compset)
.JoinAlias(() => compset.FWBCompetitionSeason, () => compseason)
.JoinAlias(() => compseason.FWBSeason, () => season)
.Where(() => compset.HeadLine == true)
.And(() => season.CurrentSeason == true)
.SelectList(
list => list
.Select(c => c.CompetitionSetID).WithAlias(() => compset.CompetitionSetID)
.Select(c => c.Name).WithAlias(() => compset.Name)
.Select(c => c.Description).WithAlias(() => compset.Description)
.Select(c => c.Area).WithAlias(() => compset.Area)
.Select(c => c.Type).WithAlias(() => compset.Type)
.Select(c => season.CurrentSeason).WithAlias(() => season.CurrentSeason)
.Select(c => c.StartDate).WithAlias(() => compset.StartDate)
)
.TransformUsing(Transformers.AliasToBean<Competitions>())
.List<Competitions>();
Yet Another Edit:
I think I finally found out what's going on. It seems that the LINQ to NHibernate provider has trouble navigating associations from the target to the source table and generates a separate join each time it encounters such an association.
Since you don't provide your mapping, I used the mapping from linq-to-nhibernate-produces-unnecessary-joins. This model has a Document with one Job and many TranslationUnits. Each TranslationUnit has many Translation entities.
When you try to find a Translation based on a Job, you are traversing the associations in the reverse order and the LINQ provider generates multiple joins: one for Translation -> TranslationUnit and one for TranslationUnit to Document.
This query will generate redundant joins:
session.Query<TmTranslation>()
.Where(x => x.TranslationUnit.Document.Job == job)
.OrderBy(x => x.Id)
.ToList();
If you reverse the navigation order to Document -> TranslationUnit -> Translation, you get a query that doesn't produce any redundant joins:
var items=(from doc in session.Query<Document>()
from tu in doc.TranslationUnits
from translation in tu.Translations
where doc.Job ==job
orderby translation.Id
select translation).ToList();
Given this quirkiness, QueryOver seems like a better option.
Previous Edit:
I suspect the culprit is compset.A.B.CurrentSeason. The first joined table (fwbcompeti1_) returns A.B while the next two (fwbcompeti2_ and fwbseason3_) are used to return A.B. The LINQ to NHibernate provider doesn't seem to guess that A is not used anywhere else and fails to remove it from the generated statement.
Try to help the optimizer a little by replacing CurrentSeason = compset.A.B.CurrentSeason with CurrentSeason = true from the select, since your where statement returns only items with CurrentSeason == true.
EDIT: What I mean is to change the query like this:
List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
where compset.HeadLine == true
&& compset.A.B.CurrentSeason == true
select (new Competitions
{
CompetitionSetID = compset.CompetitionSetID,
Name = compset.Name,
Description = compset.Description,
Area = compset.Area,
Type = compset.Type,
CurrentSeason = true,
StartDate = compset.StartDate
}
)).ToList();
I simply replace the value compset.A.B.CurrentSeason with true
I've a little problem: I would insert a condition into my QueryOver that checks also the variable value. Something like this:
var qOver = QueryOver.Of<MyModel>(() => myMod)
.JoinAlias(() => myMod.SubMod, () => subMod, JoinType.LeftOuterJoin)
.Where(Restrictions.Or(
Restrictions.On(() => myMod.ID).IsIn(MyIDList)
, Restrictions.On(MyIDList == null))
In SQL sintax something like
WHERE #Variable = '' OR MyTable.MyField = #Variable
So, if I my variable is filled I'll filter on my field. If my variable is empty (or null) I'll select every record without filter any content.
How can I reach this result using QueryOver and Restrinctions?
Thank you!
If the variable is null or not set, dont add it to your query.
var qOver = QueryOver.Of<MyModel>(() => myMod)
.JoinAlias(() => myMod.SubMod, () => subMod, JoinType.LeftOuterJoin);
if( MyIDList != null )
qOver = qOver.Where(Restrictions.Or(Restrictions.On(() => myMod.ID).IsIn(MyIDList))
I have a class with two computed columns. The formulas are select statements that grab counts from other tables, like so:
private const string VOTES_FORMULA = "(select count(v.id) from Votes v where v.BusinessID = Id)";
private const string SURVEY_FORMULA = "(select cast((case when exists (select * from surveys s where s.businessid = Id) then 1 else 0 end) as bit))";
// in my bootstrap code...
mappings.Override<Business>(map =>
{
map.IgnoreProperty(x => x.IsNewRecord);
map.IgnoreProperty(x => x.IdString);
map.Map(x => x.UserPassword).CustomType<EncryptedStringType>();
map.Map(x => x.HasTakenSurvey).Formula(SURVEY_FORMULA).Not.Insert().Not.Update();
map.Map(x => x.Votes).Formula(VOTES_FORMULA).Not.Insert().Not.Update();
});
This was all working fine with Fluent NHibernate 1.1 (using NHibernate 2.1), but I just upgraded to 1.2 (using NH 3.1) and it appears that Fluent NHibernate is ignoring the formulas. I'm getting an "invalid column name" exception for the two fields HasTakenSurvey and Votes because its' trying to query the columns directly rather than executing the formulas as directed. An example query:
exec sp_executesql N'select TOP (#p0) business0_.Id as Id0_, business0_.UserPassword as UserPass2_0_, business0_.HasTakenSurvey as HasTaken3_0_, business0_.Votes as Votes0_, business0_.Origin as Origin0_, business0_.SecurityToken as Security6_0_, business0_.BusinessName as Business7_0_, business0_.BusinessType as Business8_0_, business0_.BusinessImageUrl as Business9_0_, business0_.BusinessDescription as Busines10_0_, business0_.EmployeeCount as Employe11_0_, business0_.OwnerFirstName as OwnerFi12_0_, business0_.OwnerLastName as OwnerLa13_0_, business0_.UserPosition as UserPos14_0_, business0_.BusinessAddress1 as Busines15_0_, business0_.BusinessAddress2 as Busines16_0_, business0_.BusinessCity as Busines17_0_, business0_.BusinessState as Busines18_0_, business0_.BusinessPostal as Busines19_0_, business0_.BusinessCountry as Busines20_0_, business0_.UserBusinessPhone as UserBus21_0_, business0_.UserMobilePhone as UserMob22_0_, business0_.UserEmailAddress as UserEma23_0_, business0_.UserIpAddress as UserIpA24_0_, business0_.OptInReminders as OptInRe25_0_, business0_.OptInOffers as OptInOf26_0_, business0_.OptInSms as OptInSms0_, business0_.Created as Created0_, business0_.Modified as Modified0_ from dbo.Businesses business0_ order by business0_.BusinessName asc',N'#p0 int',#p0=25
Did the implementation change? What am I doing wrong?
As noted in the comments ConventionBuilder.Property.Always(x => x.Column(x.Property.Name)) was adding the column to all properties (and overriding the formula).
Adding .Columns.Clear() to the mapping should remove the column, so:
mappings.Override<Business>(map =>
{
map.IgnoreProperty(x => x.IsNewRecord);
map.IgnoreProperty(x => x.IdString);
map.Map(x => x.UserPassword).CustomType<EncryptedStringType>();
map.Map(x => x.HasTakenSurvey).Formula(SURVEY_FORMULA).Not.Insert().Not.Update().Columns.Clear();
map.Map(x => x.Votes).Formula(VOTES_FORMULA).Not.Insert().Not.Update().Columns.Clear();
});
Columns.Clear() solution provided by #david duffet also didn't work for me. The getter of Formula is private and so you can't filter on the Formula property (you get method-group cannot be converted to value or something like that). NH3.3, FNH 1.3.
My solution - create a custom Attribute in my Model project - IsNHibernateFormulaPropertyAttribute, apply it to my formula properties, and then check for that attribute in my naming convention logic using reflection:
private bool IsFormula(IPropertyInstance instance)
{
var propInfo = instance.Property.DeclaringType.GetProperty(instance.Property.Name);
if (propInfo != null)
{
return Attribute.IsDefined(propInfo, typeof(IsNHibernateFormulaPropertyAttribute));
}
return false;
}
public void Apply(IPropertyInstance instance)
{
if (!IsFormula(instance))
{
instance.Column(Convert(instance.Property.Name));
}
}
how can i achieve this query with Nhibernate Linq?
var l = session.CreateQuery("from Auswahl a where a.Returnkey is not null").List<Auswahl>();
i tried this but it always returns an empty list.
var l = session.Linq<Auswahl>()
.Where(item => !String.IsNullOrEmpty(item.Returnkey))
.Select(item => item)
.ToList();
Have you tried:
var l = session.Linq<Auswahl>()
.Where(item => item.Returnkey != null && item.Returnkey != "")
.Select(item => item)
.ToList();
I'm not sure that using String.IsNullOrEmpty would work, also it checks for two conditions - if it's NULL and if it's a blank empty string, how would that get translated into SQL? Might be worth having a look at SQL Profiler to see the raw SQL query it generates.