Conditional row count in linq to nhibernate doesn't work - tsql

I want to translate following simple sql query into Linq to NHibernate:
SELECT NewsId
,sum(n.UserHits) as 'HitsNumber'
,sum(CASE WHEN n.UserHits > 0 THEN 1 ELSE 0 END) as 'VisitorsNumber'
FROM UserNews n
GROUP BY n.NewsId
My simplified UserNews class:
public class AktualnosciUzytkownik
{
public virtual int UserNewsId { get; set; }
public virtual int UserHits { get; set; }
public virtual User User { get; set; } // UserId key in db table
public virtual News News { get; set; } // NewsId key in db table
}
I've written following linq query:
var hitsPerNews = (from n in Session.Query<UserNews>()
group n by n.News.NewsId into g
select new { NewsId = g.Key, HitsNumber = g.Sum(x => x.UserHits),
VisitorsNumber = g.Count(x => x.UserHits > 0) }).ToList();
But generated sql just ignores my x => x.UserHits > 0 statement and makes unnecessary 'left outer join':
SELECT news1_.NewsId AS col_0_0_,
CAST(SUM(news0_.UserHits) AS INT) AS col_1_0_,
CAST(COUNT(*) AS INT) AS col_2_0_
FROM UserNews news0_
LEFT OUTER JOIN News news1_
ON news0_.NewsId=news1_.NewsId
GROUP BY news1_.NewsId
How Can I fix or workaround this issue? Maybe this can be done better with QueryOver syntax?

I finally found answer to my question, my solution is based on an answer to this question:
My QueryOver code (I still don't know how to do it in Linq to NHibernate):
UserHitsDto adDtoAlias = null;
var userHits = Session.QueryOver<UserNews>()
.Select(Projections.Group<UserNews>(c => c.News.NewsId)
.WithAlias(() => adDtoAlias.NewsId),
Projections.Sum<UserNews>(x => x.UserHits)
.WithAlias(() => adDtoAlias.HitsNumber),
Projections.Sum(Projections.Conditional(
Restrictions.Where<UserNews>(f => f.UserHits > 0),
Projections.Constant(1),
Projections.Constant(0)
)).WithAlias(() => adDtoAlias.VisitorsNumber)
)
.TransformUsing(Transformers.AliasToBean<UserHitsDto>())
.List<UserHitsDto>();
It produces following tsql:
SELECT this_.NewsId AS y0_,
SUM(this_.UserHits) AS y1_,
SUM((
CASE
WHEN this_.UserHits > #p0
THEN #p1
ELSE #p2
END)) AS y2_
FROM UserNews this_
GROUP BY this_.NewsId
where #p0 = 0, #p1 = 1, #p2 = 0

Related

EF Core Filter results based on child data

Given tables CatalogItem and Option
Where CatalogItem has 0..many Option
In SQL I can do this...
Select * from
CatalogItem C
inner join [Option] O on O.CatalogItemId = C.Id
Where
O.Quantity > 0
So simple. In EF I have entities
public class CatalogItem
{
public int Id {get; set;}
public virtual ICollection<Option> Options { get; set; }
...
}
public class Option
{
public int Id {get; set;}
public int CatalogItem Id { get; set; }
public int Quantity { get; set; }
...
}
In EF I've tried
var q = context.CatelogItems
.Include(i => i.Options)
.Where(i => i.Options.Any(o => o.Quantity > 0)).ToList();
And various other things, but so far I am not able to get the equivalent results as I am from the above SQL. Namely, all catalog items that have at least one option that had a quantity > 0
I've seen some other posts looking for the same with a join, with a projection that includes both sets of objects as an anonymous type, etc. but I'm looking for a List that has the Options collection populated IF there are options with a quantity... all in one DB query.
Any help?
var q=from c in context.catalogItem
join o in context.options on c.id equal o.CatalogItemId
where o.quantity>0
But I think you need to use Group by as below:
var cQuery=from c in context.catalogItem select c;
var oQuery=from o in context.options
group o by new {o.CatalogItemId}into groups
select new {CatalogItemId=groups.Key.Id,TQuantity=groups.Sum(t => t.Quantity)};
var q=from c in cQuery
join o in oQuery on c.id=o.CatalogItemId
where o.TQuantity>0
You can use third party package to use IncludeFilter
Query IncludeFilter
var q = context.CatelogItems
.IncludeFilter(i => i.Options.Where(o => o.Quantity > 0))
.ToList();

EF LInq Left outer join sorted taking fist

I have this left outer join query
var queryString = (from entity in _dbContext.Links
from translations in _dbContext.LinksTrs.Where(p => p.LinkId == entity.Id).OrderBy(p => p.LanguageCode).Take(1).DefaultIfEmpty()
select new LinksAdminListModel()
{
Id = entity.Id,
Name = (translations == null) ? "" : translations.Name,
Url = entity.Url,
Active = entity.Active
});
As you can see I'm getting just one row from the right side of the query while I return all the left rows.
What I need is, instead sorting just by LanguageCode, I need to tell the query to sort by a specifid LanguageCode first and then the rest.
If I add this:
.OrderBy(p => (p.LanguageCode == currentLanguage) ? 0 : 1)
where currentLanguage is just a string containing the language "en", "fr"...
so the query is:
var queryString = (from entity in _dbContext.Links
from translations in _dbContext.LinksTrs.Where(p => p.LinkId == entity.Id).OrderBy(p => (p.LanguageCode == currentLanguage) ? 0 : 1).Take(1).DefaultIfEmpty()
select new LinksAdminListModel()
{
Id = entity.Id,
Name = (translations == null) ? "" : translations.Name,
Url = entity.Url,
Active = entity.Active
});
I get the following error:
"Unknown column 'Extent1.Id' in 'where clause'"
I need help since I need to return the current language if it exists, if not the next one.
Thanks.
EDIT
The problem is the combination of the ORDER with the TAKE(1), but I do need to take just the first one...
EDIT 2
The probelm seems to be what I commented above...this is the query I'm using:
var queryString = (from entity in _dbContext.Links
from translations in _dbContext.LinksTrs.Where(p => p.LinkId == entity.Id).OrderByDescending(p => p.LanguageCode == currentLanguage).ThenBy(p => p.LanguageCode).Take(1)
select new LinksAdminListModel()
{
Id = entity.Id,
Name = (translations == null) ? "" : translations.Name,
Url = entity.Url,
Active = entity.Active
});
And this is the SQL it is generating...in case someone can see something in here:
SELECT
`Apply1`.`Id`,
`Apply1`.`Name`,
`Apply1`.`Url`,
`Apply1`.`Active`
FROM (SELECT
`Extent1`.`Id`,
`Extent1`.`OrderPos`,
`Extent1`.`Url`,
`Extent1`.`Active`,
`Extent1`.`DateCreated`,
`Extent1`.`DateModified`,
`Extent1`.`UserIdModified`,
(SELECT
`Project1`.`C1`
FROM (SELECT
CASE WHEN ((`Extent2`.`LanguageCode` = 'es-ES') OR ((`Extent2`.`LanguageCode` IS NULL) AND ('es-ES' IS NULL))) THEN (1) WHEN (NOT ((`Extent2`.`LanguageCode` = 'es-ES') AND ((CASE WHEN (`Extent2`.`LanguageCode` IS NULL) THEN (1) ELSE (0) END) = (CASE WHEN ('es-ES' IS NULL) THEN (1) ELSE (0) END)))) THEN (0) END AS `C1`,
`Extent2`.`Name`,
`Extent2`.`LanguageCode`
FROM `LinkTr` AS `Extent2`
WHERE `Extent2`.`LinkId` = `Extent1`.`Id`) AS `Project1`
ORDER BY
`Project1`.`C1` DESC,
`Project1`.`LanguageCode` ASC LIMIT 1) AS `C1`,
(SELECT
`Project1`.`Name`
FROM (SELECT
CASE WHEN ((`Extent2`.`LanguageCode` = 'es-ES') OR ((`Extent2`.`LanguageCode` IS NULL) AND ('es-ES' IS NULL))) THEN (1) WHEN (NOT ((`Extent2`.`LanguageCode` = 'es-ES') AND ((CASE WHEN (`Extent2`.`LanguageCode` IS NULL) THEN (1) ELSE (0) END) = (CASE WHEN ('es-ES' IS NULL) THEN (1) ELSE (0) END)))) THEN (0) END AS `C1`,
`Extent2`.`Name`,
`Extent2`.`LanguageCode`
FROM `LinkTr` AS `Extent2`
WHERE `Extent2`.`LinkId` = `Extent1`.`Id`) AS `Project1`
ORDER BY
`Project1`.`C1` DESC,
`Project1`.`LanguageCode` ASC LIMIT 1) AS `Name`,
(SELECT
`Project1`.`LanguageCode`
FROM (SELECT
CASE WHEN ((`Extent2`.`LanguageCode` = 'es-ES') OR ((`Extent2`.`LanguageCode` IS NULL) AND ('es-ES' IS NULL))) THEN (1) WHEN (NOT ((`Extent2`.`LanguageCode` = 'es-ES') AND ((CASE WHEN (`Extent2`.`LanguageCode` IS NULL) THEN (1) ELSE (0) END) = (CASE WHEN ('es-ES' IS NULL) THEN (1) ELSE (0) END)))) THEN (0) END AS `C1`,
`Extent2`.`Name`,
`Extent2`.`LanguageCode`
FROM `LinkTr` AS `Extent2`
WHERE `Extent2`.`LinkId` = `Extent1`.`Id`) AS `Project1`
ORDER BY
`Project1`.`C1` DESC,
`Project1`.`LanguageCode` ASC LIMIT 1) AS `LanguageCode`
FROM `Link` AS `Extent1`) AS `Apply1`
EDIT 3
The entities are:
public class Link : EntityBase
{
public int OrderPos { get; set; }
public string Url { get; set; }
public bool Active { get; set; }
public virtual IEnumerable<LinkTr> Translations { get; set; }
}
public class LinkTr : EntityBaseTr
{
public string Name { get; set; }
public string Summary { get; set; }
//[ForeignKey("Link")]
public int LinkId { get; set; }
public virtual Link Link { get; set; }
//[ForeignKey("Language")]
public string LanguageCode { get; set; }
public virtual Language Languages { get; set; }
}
public class EntityBase
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] //[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? DateModified { get; set; }
public int UserIdModified { get; set; }
}
public class EntityBaseTr
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime? DateCreated { get; set; }
}
Cannot duplicate with the information provided.
The only thing I've noticed during the testing was that the expression (translations == null) ? "" : translations.Name was generating null check for Translations table Id column, not sure if you have such in your entity.
In any case, it's worth trying the following alternative which does not include such a check:
var queryString = (
from entity in _dbContext.Links
let translationName = _dbContext.LinksTrs
.Where(p => p.LinkId == entity.Id)
.OrderBy(p => p.LanguageCode == currentLanguage ? 0 : 1)
.Select(p => p.Name)
.FirstOrDefault();
select new LinksAdminListModel()
{
Id = entity.Id,
Name = translationName ?? "",
Url = entity.Url,
Active = entity.Active
});
UPDATE:
It turns out that this is a current MySql EF query provider issue (bug?!).
After playing a while, the only way I was able to make it work is as follows
var queryString =
(from entity in db.Links
let translations =
db.LinksTrs.Where(p => p.LinkId == entity.Id && p.LanguageCode == currentLanguage).FirstOrDefault() ??
db.LinksTrs.Where(p => p.LinkId == entity.Id).OrderBy(p => p.LanguageCode).FirstOrDefault()
select new LinksAdminListModel
{
Id = entity.Id,
Name = (translations == null) ? "" : translations.Name,
Url = entity.Url,
Active = entity.Active
});

Entity Framework - select group by, select max date

I have a NoteBrief
public int Id { get; set; }
public string Title { get; set; }
public DateTime Created { get; set; }
public int ParentNoteId { get; set; }
Data looks something like
1 Title1 03/31/1987 1
2 Title1 03/31/1988 1
3 Title3 01/01/2000 3
4 Title4 01/01/2001 4
5 Title4 01/01/2005 4
I want to do:
SELECT t1.*
FROM Notes AS t1 LEFT JOIN Notes AS t2
ON (t1.ParentNoteId = t2.ParentNoteId AND t1.Created < t2.Created)
WHERE t2.Created IS NULL;
Right now i have:
public IQueryable<NoteBrief> GetNotes()
{
return _ctx.Notes.Select(r => new NoteBrief
{
Id = r.Id,
Title = r.Title,
Created = r.Created,
ParentNoteId = r.ParentNoteId,
});
}
I'm happy with this, but really don't need the older revisions of a parentNoteId, just need the one that was created last so i can link to it.
I've read many examples, some of which use FirstOrDefault and some that use max. Everytime i try to implement an example though, it doesn't work for me.
Entity Framework creating IQuerable of the most recent
This is what finally worked for me:
return from e in _ctx.Notes
group e by e.ParentNoteId into g
select g.OrderByDescending(e => e.Created).FirstOrDefault() into r
select new NoteBrief
{
Id = r.Id,
Title = r.Title,
Created = r.Created,
ParentNoteId = r.ParentNoteId,
};
Also edited my original post with correct query i was going for.
Thanks.
Try following
return _ctx.Notes.Select(r => new NoteBrief
{
Id = r.Id,
Title = r.Title,
Created = r.Created,
ParentNoteId = r.ParentNoteId,
}).OrderBy(x=>x.Created).GroupBy(x=>new {Id=x.Id, Title=x.Title}).Select(x=>x.First()).AsQueryable();
}

How to perform a left outer join using Entity Framework using navigation properties

I'm trying to find out how I would define the code first navigation properties on these two classes to perform something similiar to this query:
SELECT USERID, FIRSTNAME, LASTNAME, COURSEID, NAME
FROM User
LEFT OUTER JOIN Course ON User.USERID = Course.USERID
WHERE COURSEID = 1
So I'm trying to find a list of users together with if they have attended a certain course.
public class User
{
public int UserId {get;set; }
public string FirstName {get;set;}
public string LastName {get;set;}
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public int CourseId { get;set; }
public int UserId { get;set; }
public string Name { get;set; }
public virtual User User {get;set;}
}
If I was to write a query to achieve this
var u = Users.Where(x => x.Courses.Any(x => x.CourseId = 1));
This does a subquery, which is not what I wanted (as people who didnt attend the course would not show).
How would the navigation property be defined?
HasMany(t => t.Courses).WithOptional(t => t.User).HasForeignKey(t => t.UserId);
Check this link:
http://msdn.microsoft.com/en-us/library/vstudio/bb397895.aspx
Left outer joins in LINQ are done via DefaultIfEmpty method.
var u = Users.Select ( x => new {
User = x,
AttendedCourse = x.Courses.Any()
} );
For specific course id,
var u = Users.Select ( x => new {
User = x,
AttendedCourse = x.Courses.Any( c => c.CourseID == 1 )
} );
Sub query is the only way to write related queries, However, EF will choose the best suitable join type and will give you correct results. And EF can manage mostly all kinds of queries without doing joins.

Entity Framework code first - many to many filtering

Small EF question.
I have a many to many relationship mapped in EF. X..Y
So when I have one X there is a property X.Ys.
Now what I want to do is use a Linq Query to get several X's but I don't want to have all Y's inside the selected X's.
I want the Y's filtered on Y.RegistrationDate > Date.Today.
So when I have one X and itterate through .Y's I will only get future Y's.
UPDATE
This works, resulting in S having distinct ug's with it's relationship only containing upcoming events.
But don't tell me this cant be simplified??!!
var t = (from ug in uof.Ugs.All().ToList()
from upcomingEvent in ug.Events
where upcomingEvent.Date >= DateTime.Today
select new
{
ug,
upcomingEvent
}).ToList();
var s = (from ug in t.Select(x => x.ug).Distinct()
select new UG
{
Id = ug.Id,
Name = ug.Name,
Description = ug.Description,
WebSite = ug.WebSite,
Events = ug.Events.Where(x => x.Date >= DateTime.Today).ToList()
}).ToList();
UPDATE2
Added image to show that even with basic context manipulation I'm still getting 2 events, event when I take 1!
exampledebugimage
EF does not support this scenario as you want it, what you can do however is this:
var date = DateTime.Date;
var query = from x in Xs
select new
{
X = x
Ys = x.Ys.Where(i = > i.RegistrationDate > date)
}
Which will give you a collection of X's with their corresponding Y's that match your criteria.
Have you tried?:
var query = Xs
.Select(x => new { x, yCol = x.YCol.Where(y => y.Date >= DateTime.Today) })
.AsEnumerable()
.Select(x => x.x)
.ToList();
See: http://blogs.msdn.com/b/alexj/archive/2009/10/13/tip-37-how-to-do-a-conditional-include.aspx
All those .ToList you use will mean you load the whole table from the db before filtering. So watch out for that.
UPDATE: As fixup doesn't work with Many-To-Many
As Slauma mentioned in the comments make sure you don't use this technique if you are going to submit the changes as the changetracking will think you altered the collection. Or even better make sure you use .AsNoTracking() which will improve performance anyway.
We can use the same solution as above but slightly different for many-to-many. See this example:
[TestClass]
public class ContextTest
{
[TestMethod]
public void FixupTest()
{
Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
using (var db = new Context())
{
db.Groups.Add(new Group
{
Name = "G1",
Users = new List<User>{
new User{ Name = "M"},
new User{Name = "S"}
}
});
db.SaveChanges();
}
using (var db = new Context())
{
var group = db.Groups
.Select(g => new { g, Users = g.Users.Where(u => u.Name == "M") })
.AsEnumerable()
.Select(g => {
g.g.Users = g.Users.ToList();
return g.g;
})
.First();
Assert.AreEqual(1, group.Users.Count);
}
}
}
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Group> Groups { get; set; }
}
public class Group
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<User> Users { get; set; }
}
The test pass and the generated sql is:
SELECT
[Project1].[ID] AS [ID],
[Project1].[Name] AS [Name],
[Project1].[C1] AS [C1],
[Project1].[ID1] AS [ID1],
[Project1].[Name1] AS [Name1]
FROM ( SELECT
[Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Join1].[ID] AS [ID1],
[Join1].[Name] AS [Name1],
CASE WHEN ([Join1].[Group_ID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM [dbo].[Groups] AS [Extent1]
LEFT OUTER JOIN (SELECT [Extent2].[Group_ID] AS [Group_ID], [Extent3].[ID] AS [ID], [Extent3].[Name] AS [Name]
FROM [dbo].[GroupUsers] AS [Extent2]
INNER JOIN [dbo].[Users] AS [Extent3] ON [Extent3].[ID] = [Extent2].[User_ID] ) AS [Join1] ON ([Extent1].[ID] = [Join1].[Group_ID]) AND (N'Mikael' = [Join1].[Name])
) AS [Project1]
ORDER BY [Project1].[ID] ASC, [Project1].[C1] ASC