NHibernate LINQ query with GroupBy - linq

I am struggling with converting SQL to NHibernate HQL.
SQL Query
SELECT Posts.id, Count(Comments.id) FROM Posts LEFT JOIN Comments ON Posts.id=Comments.fk_post GROUP BY Posts.id
LINQ
Session.Query<Post>().Fetch(x => x.Comments).GroupBy(x => x.id, x => x.Comments)
.Select(x => new { PostId = x.Key, CommentCount = x.Single().Count }).ToList();
This is still failing with exception:
Parameter 'inputInfo' has type 'Remotion.Linq.Clauses.StreamedData.StreamedSingleValueInfo' when type 'Remotion.Linq.Clauses.StreamedData.StreamedSequenceInfo' was expected.
What is wrong with my query?

So you have tables of Posts and Comments. There is a one-to-many relation between Posts and Comments: every Post has zero or more Comments, every Comment belongs to exactly one Post, namely the Post that the foreign key Comments.fk_post refers to.
You want to fetche the Id of every Post, together with the number of Comments for this Post.
Whenever you need to select "items with their zero or more sub-items", like Schools with their Students, Customers with their Orders, or in your case Posts with their Comments, consider to use one of the overloads of Queryable.GroupJoin.
You can also see that a GroupJoin is the most obvious solution, if you see a SQL Left Outer Join followed by a GroupBy.
Whenever you see a SQL left outer join followed by a GroupBy, it is almost certain that you need a GroupJoin.
If you want something else than juse "items with their sub-items", use the overload that has a parameter resultSelector.
I don't know nHibernate, I assume that Session, Query, Fetch are used to get the IQueryables. As this is not part of the question, I leave it up to you to get the IQueryables:
IQueryable<Post> posts = ...
IQueryable<Comment> comments = ...
// GroupJoin Posts with Comments
var postIdsWithCommentsCount = posts.GroupJoin(comments,
post => post.Id, // from every Post take the primary key
comment => comment.fk_post, // from every Comment take the foreign key to Post
// parameter resultSelector: from every Post, with all its zero or more Comments,
// make one new
(post, commentsOfThisPost) => new
{
Id = post.Id,
Count = commentsOfThisPost.Count(),
});

Try this query:
var query =
from p in Session.Query<Post>()
from c in p.Comments.DefaultIfEmpty()
group c by p.Id into g
select new
{
PostId = g.Key,
CommentCount = g.Sum(x => (int?)c.Id == null ? 0 : 1)
}
var result = query.ToList();;

Related

How to write SQL translateable linq code that groups by one property and returns distinct list

I want to change code below to be sql translateable because now i get exception.
Basicallly i want list of customers from certain localisation and there could be more than one customer with the same CustomerNumber so i want to take the one that was most recently added.
In other words - distinct list of customers from localisation where "distinct algorithm" works by taking the most recently added customer if there is conflict.
The code below works only if it is client side. I could move Group By and Select after ToListAsync but i want to avoid taking unnecessary data from database (there is include which includes list that is pretty big for every customer).
var someData = await DbContext.Set<Customer>()
.Where(o => o.Metadata.Localisation == localisation)
.Include(nameof(Customer.SomeLongList))
.GroupBy(x => x.CustomerNumber)
.Select(gr => gr.OrderByDescending(x => x.Metadata.DateAdded).FirstOrDefault())
.ToListAsync();
Short answer:
No way. GroupBy has limitation: after grouping only Key and Aggregation result can be selected. And you are trying to select SomeLongList and full entity Customer.
Best answer:
It can be done by the SQL and ROW_NUMBER Window function but without SomeLongList
Workaround:
It is because it is not effective
var groupingQuery =
from c in DbContext.Set<Customer>()
group c by new { c.CustomerNumber } into g
select new
{
g.Key.CustomerNumber,
DateAdded = g.Max(x => x.DateAdded)
};
var query =
from c in DbContext.Set<Customer>().Include(x => x.SomeLongList)
join g in groupingQuery on new { c.CustomerNumber, c.DateAdded } equals
new { g.CustomerNumber, g.DateAdded }
select c;
var result = await query.ToListAsync();

NotSupportedException for LINQ Queries

I am trying to get a list of a database table called oracleTimeCards whose employee id equals to the employeeID in employees list. Here is what I wrote:
LandornetSQLEntities db = new LandornetSQLEntities();
List<OracleEmployee> employees = db.OracleEmployees.Where(e => e.Office.Contains(officeName) && e.IsActive == true).Distinct().ToList();
var oracleTimeCards = db.OracleTimecards.Where(c => employees.Any(e => c.PersonID == e.PersonID)).ToList();
Anyone has any idea?
I'm going to assume you're using Entity Framework here. You can't embed calls to arbitrary LINQ extension methods inside your predicate, since EF might not know how to translate these to SQL.
Assuming you want to find all the timecards for the employees you found in your first query, you have two options. The simplest is to have a navigation property on your Employee class, named let's say TimeCards, that points to a collection of time card records for the given employee. Here's how that would work:
var oracleTimeCards = employees
.SelectMany(e => e.TimeCards)
.ToList();
If you don't want to do this for whatever reason, you can create an array of employee IDs by evaluating your first query, and use this to filter the second:
var empIDs = employees
.Select(e => e.PersonID)
.ToArray();
var oracleTimeCards = db.OracleTimecards
.Where(tc => empIDs.Contains(tc.PersonID))
.ToList();

Linq and Lambda expression for a complex sql query involving joins

Using Linq to Entity (Entity Framework) in MVC 3 project.
My model:
Table - Users
UserID (PK)
...
Table - Clients
ClientID (PK)
Table - PropertyItems
PropertyItemID (PK)
Table - MemberContactPreference (Contains PropertyItems selected by Users- many to many)
UserID(FK)
PropertyItemID(FK)
Table ClientProperties (Contains PropertyItems that belong to Clients - many to many)
ClientID (FK)
PropertyItemID (FK)
I want to list all the distinct users that have selected all the properties selected by clients.
My Approach :
I got a list of all properties for a particular client in
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> UsersMatchingClientProperties = GetAllUsers();
foreach (ClientProperty property in clientproperties)
{
UsersMatchingClientProperties = (from uem in UsersMatchingClientProperties
join ucp in GetAllMemberContactPreferences on
ucp.UserID == uem.UserID
where uem.MemberContactPreferences.SelectMany(
mcp => mcp.PropertyItemID == property.PropertyItemID)
select uem).Distinct;
}
It gives the right result only first time. As it doesn't reduce the number of items in UsersMatchingClientProperties with each iteration. actually it replaces the collection with new resultset. I want to filter out this collection with each iteration.
Also, any suggestions to do this in Lambda expression without using Linq.
Thanks
That generation of an iqueryable in a for loop seems like a dangerous thing, which could end up in a monster sql join being executed at once.
Anyway, I don't think you need that. How about something like this?
// for a given client, find all users
// that selected ALL properties this client also selected
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> allUsers= GetAllUsers();
Iqueryable<MemberContactPreference> allMemberContactProperties = GetAllMemberContactPreferences();
Iqueryable<User> UsersMatchingClientProperties = allUsers
.Where(user => allMemberContactProperties
.Where(membP => membP.UserID==user.UserID)
.All(membP => clientProperties
.Select(clientP => clientP.PropertyID)
.Contains(membP.PropertyID)
)
);
Here is an alternative query in case you want the users that selected ANY property for a given client
// for a given client, find all users
// that selected ANY properties this client also selected
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> allUsers= GetAllUsers();
Iqueryable<MemberContactPreference> allMemberContactProperties = GetAllMemberContactPreferences();
Iqueryable<User> UsersMatchingClientProperties = clientproperties
.Join(allMembersContactProperties, // join clientproperties with memberproperties
clientP => clientP.PropertyItemID,
membP => membP.PropertyItemID,
(clientP, membP) => membP)) // after the join, ignore the clientproperties, keeping only memberproperties
.Distinct() // distinct is optional here. but perhaps faster with it?
.Join(allUsers, //join memberproperties with users
membP => membP.UserID,
user => user.UserID,
(membP, user) => user)) // after the join, ignore the member properties, keeping only users
.Distinct();
I trust Hugo did a good job suggesting ways to improve your query (+1). But that does not yet explain the cause of your problem, which is the modified closure pitfall.
I think that after your loop there is some code that actually executes the query in UsersMatchingClientProperties. At that moment the query is executed with the last value of the loop variable property! (The loop variable is the closure in each query delegate that is created in an iteration, and it is modified by each iteration).
Change the loop like this:
foreach (ClientProperty property in clientproperties)
{
var property1 = property;
...
and use property1 in the query. That should solve the cause of the problem. But as said, it looks like the whole process can be improved.

ef and linq extension method

I have this sql that i want to have written in linq extension method returning an entity from my edm:
SELECT p.[Id],p.[Firstname],p.[Lastname],prt.[AddressId],prt.[Street],prt.[City]
FROM [Person] p
CROSS APPLY (
SELECT TOP(1) pa.[AddressId],a.[ValidFrom],a.[Street],a.[City]
FROM [Person_Addresses] pa
LEFT OUTER JOIN [Addresses] AS a
ON a.[Id] = pa.[AddressId]
WHERE p.[Id] = pa.[PersonId]
ORDER BY a.[ValidFrom] DESC ) prt
Also could this be re-written in linq extension method using 3 joins?
Assuming you have set the Person_Addresses table up as a pure relation table (i.e., with no data besides the foreign keys) this should do the trick:
var persons = model.People
.Select(p => new { p = p, a = p.Addresses.OrderByDescending(a=>a.ValidFrom).First() })
.Select(p => new { p.p.Id, p.p.Firstname, p.p.LastName, AddressId = p.a.Id, p.a.Street, p.a.City });
The first Select() orders the addresses and picks the latest one, and the second one returns an anonymous type with the properties specified in your query.
If you have more data in your relation table you're gonna have to use joins but this way you're free from them. In my opinion, this is more easy to read.
NOTE: You might get an exception if any entry in Persons have no addresses connected to them, although I haven't tried it out.

Getting Last rows from the result of Linq to Sql statement

I couldn't get last articles of every writers in this statement.
List<Editor> lstEditors = dataContext.GetTable<Editor>().Where(t => t.M_Active).Select(t => t).ToList();
var lstArticles = from article in DAO.context.GetTable<Article>().ToList()
join editor in lstEditors on article.RefEditorId equals editor.EditorId
select
new
{
article.M_ArticleId,
article.M_Subject,
article.M_Text,
editor.M_EditorId,
editor.M_Member.M_EditorPicture,
M_NameSurname = editor.M_Member.M_Fname + " " + editor.M_Member.M_Lname
};
Be careful, your query is fetching all the contents of both the Editor and the Yazi tables and then performs Linq-to-Objects on it.
I'm not sure what you ask exactly either, do you want to obtain the list of all writers (editors) along with the last article of each one of these writers?
Do you want to get the writers that did not write any articles yet also?
Edit:
explanation of methods causing an immediate query
Any time you call one of the methods listed below on an IQueryable object (tables or other queries), it performs the actual query to SQL server:
ToList(), ToArray(), ToLookup(), ToDictionay()
Count(), Sum(), Avg(), Aggregate(), Min(), Max()
First(), FirstOrDefault(), Last(), LastOrDefault()
getting last article written by each writer
//create a subquery that returns an editor and its last article date
var editorLastArticleDates =
from article in DAO.context.GetTable<Article>()
group article by article.RefEditor into g
let lastArticleDate= g.Max(x => x.Date)
select new
{
Editor = g.Key,
LastArticleDate = lastArticleDate,
};
//Note: We did not do a ToList() here so the query is not executed
// The editorLastArticleDates object is a IQueryable<>
var query =
from article in DAO.context.GetTable<Article>()
join editorLastArticleDate in editorLastArticleDates
on new { article.Editor, article.Date } // 1
equals new { editorLastArticleDate.Editor, // 2
Date = editorLastArticleDate.LastArticleDate } // 3
select new
{
article.M_ArticleId,
article.M_Subject,
article.M_Text,
article.RefEditor.M_EditorId,
article.RefEditor.M_Member.M_EditorPicture,
M_NameSurname = article.RefEditor.M_Member.M_Fname + " "
+ article.RefEditor.M_Member.M_Lname,
};
//Note: We did not do a ToList() yet so the query is not executed
// The query object is a IQueryable<>
Console.WriteLine(query.ToString()); //Displays SQL query on the console
var results = query.ToList(); // SQL query is executed on this line.
In the code above, I left some remarks on things I had problems with:
When using join, the section between new and equals access only variables declared before the join keyword while the section after the equals keyword has access to the variable defined between join and in.
When writing your join condition, make sure you use equals and not ==.
When using new { XXX, YYY } syntax in your join condition, you declare anonymous types. If the property names are not identical on both sides, it will not compile. In order to have identical property names in this sample, I added the Date = before my value.
By the way, you should use LinqPad to test your queries, it is really a nice tool.

Resources