SQL to LINQ troubles - c#

I have some SQL which returns two columns, the X column and Y column:
SELECT TOP (100) PERCENT
dbo.SurveyAnswer.QuestionAnswer AS [Y],
COUNT(dbo.SurveyAnswer.QuestionAnswer) AS [X]
FROM
dbo.SurveyAnswer
INNER JOIN dbo.SurveyQuestion ON
dbo.SurveyAnswer.QuestionID = dbo.SurveyQuestion.QuestionID
INNER JOIN dbo.FieldAgentCall ON
dbo.SurveyAnswer.JobId = dbo.FieldAgentCall.JobId AND
dbo.SurveyAnswer.ObjectiveId = dbo.FieldAgentCall.ObjectiveID AND
dbo.SurveyAnswer.AgentId = dbo.FieldAgentCall.AgentID
INNER JOIN dbo.SurveyQuestionaire ON
dbo.FieldAgentCall.JobId = dbo.SurveyQuestionaire.JobId and
dbo.SurveyQuestion.QuestionaireID = dbo.SurveyQuestionaire.QuestionaireID and
WHERE
(dbo.SurveyQuestion.QuestionNo = 9) AND (dbo.SurveyQuestion.QuestionaireID = 1) AND
dbo.SurveyAnswer.QuestionAnswer <>'NA'
GROUP BY
dbo.SurveyAnswer.QuestionAnswer
ORDER BY
[Y]
The SQL searches through a range of tables and returns all the answers to a question and groups then, so the results would look similar to.
X | Y
No | 234
Yes | 43
The SQL works fine, I got that working without a problem, due to the length of the query and different parameters being sent in, the query got to an unmanageable size and decided it's time it became LINQ.
So I am trying to get the basic LINQ working to get results out, but being fairly new to LINQ, I can't quite get it working
var query = (from answers in db.SurveyAnswerModels.ToList()
join question in db.SurveyQuestion.Where(i => i.QuestionID == 9 && i.QuestionaireID == 1).ToList() on answers.QuestionID equals question.QuestionID
join questionnaire in db.SurveyQuestionnaire.ToList() on question.QuestionaireID equals questionnaire.QuestionaireID
join fieldagent in db.FieldAgentCall.ToList() on questionnaire.JobId equals fieldagent.JobId
group answers.QuestionAnswer by answers.QuestionAnswer into results
select new { X = results.Count(), Y = results });
The result I am getting for this is the wrong amount of counts for X and the Y data isn't group
[{"Xs":2814,"Ys":["No","No","No","No",
Though it's the wrong amount because I assume I've not added the right parameters yet, so that's something I can sort, the main problem I am having though is the group by, I tried to replicate it as much as possible but failed.
The "No's" should just be a "No" with the count of how many No's there are, which it's going with the counter as it says there are 2,814 No's, I just need it to only say say "No" once.
Any advice would be great too, like where I am going wrong.

Try this:
var query = (from answers in db.SurveyAnswerModels
join question in db.SurveyQuestion on answers.QuestionID equals question.QuestionID
join questionnaire in db.SurveyQuestionnaire on question.QuestionaireID equals questionnaire.QuestionaireID
join fieldagent in db.FieldAgentCall on questionnaire.JobId equals fieldagent.JobId
where question.QuestionID == 9 && question.QuestionaireID == 1
group answers.QuestionAnswer by answers.QuestionAnswer into results
select new { Count = results.Count(), Answer = results.Key });
Differences from yours:
The ToLists() are removed (this is at best unnecessary and at worst will screw up the C#-expression-to-SQL translation)
Moved the Where() down to the bottom (unnecessary, but makes it easier to follow)
Select results.Key as the answer. Key is the the "grouped by" value for reach result group.
I think 3. is possibly the only step necessary to get it working.

Related

Left/outer join with linq on c# with where condition clause

I have 2 tables that I need to join in a query.
The first table is the Entries table which contain certain events such as Dance, Speak, Sing, Play, etc.
Id|Name
1|Dance
2|Sing
3|Speak
4|Play
5| etc.
The other table contains userEntries which indicates each user's score on each of the events
Id| UserId|EntryId|Score
1|898128 | 1 |200
2|827329 | 2 |120
3|898128 | 2 |100
Now I want a linq query to first of all get all the entries and then get the scores for a given user for the entries retunining null for the entry score whete the user has noscore
Example
for user 898128, I want to see something like this
Dance:200,Speak:null,Sing:120 from the result
I have tried the following linq query and I get an empty result
var userScores =
(from e in db.Entries join se in db.UserEntries
on e.Id equals se.EntryId
into ese from se in
ese.DefaultIfEmpty()
where se.UserId == "898128"
select new
{
EntryLabel=e.Label,
EntryValue=se.ValueAmount,
}).ToList();
ViewData["userScores "] = userScores;
I am running on ASP.NET core 2.0, entity framework core on a Windows 10 machine with Visual Studio 2017 15.6.3
I will appreciate any guide to getting the query right to give me an outer join so I can get all the entries for each user even where the user does not have any score.
Please note that this is different from this question errorneously marked by #Mahmoud as its duplicate. The difference lies in the presence of the WHERE condition clause.
Thank you
Try this query. it should fix your issue.
var userScores =(from e in db.Entries
join se in db.UserEntries on e.Id equals se.EntryId into ese
from nullse in ese.DefaultIfEmpty()
where (nullse==nulll ||(nullse!=null && nullse.UserId == "898128"))
select new
{
EntryLabel = e.Name,
EntryValue = nullse != null ? nullse.ValueAmount:"null"
}).ToList();
I have found the answer from this SO question. From there, I realized that the position of the where clause is the problem. See the working code revision below
var userScores =
(from e in db.Entries join se in db.UserEntries.Where(o => o.UserId ==
"898128"
on e.Id equals se.EntryId
into ese from se in
ese.DefaultIfEmpty()
select new
{
EntryLabel=e.Label,
EntryValue=se.ValueAmount,
}).ToList();
ViewData["userScores "] = userScores;
Thank you #Hazarath for your guide

Multiple joins with multiple on statements using Linq Lambda expressions [duplicate]

Suppose I have a list of {City, State}. It originally came from the database, and I have LocationID, but by now I loaded it into memory. Suppose I also have a table of fast food restaurants that has City and State as part of the record. I need to get a list of establishments that match city and state.
NOTE: I try to describe a simplified scenario; my business domain is completely different.
I came up with the following LINQ solution:
var establishments = from r in restaurants
from l in locations
where l.LocationId == id &&
l.City == r.City &&
l.State == r.State
select r
and I feel there must be something better. For starters, I already have City/State in memory - so to go back to the database only to have a join seems very inefficient. I am looking for some way to say {r.City, r.State} match Any(MyList) where MyList is my collection of City/State.
UPDATE
I tried to update based on suggestion below:
List<CityState> myCityStates = ...;
var establishments =
from r in restaurants
join l in myCityStates
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
and I got the following compile error:
Error CS1941 The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.
UPDATE 2
Compiler didn't like anonymous class in the join. I made it explicit and it stopped complaining. I'll see if it actually works in the morning...
It seems to me that you need this:
var establishments =
from r in restaurants
join l in locations.Where(x => x.LocationId == id)
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
Well, there isn't a lot more that you can do, as long as you rely on a table lookup, the only thing you can do to speed up things is to put an index on City and State.
The linq statement has to translate into a valid SQL Statement, where "Any" would translate to something like :
SELECT * FROM Restaurants where City in ('...all cities')
I dont know if other ORM's give better performance for these types of scenarios that EF, but it might be worth investigating. EF has never had a rumor for being fast on reads.
Edit: You can also do this:
List<string> names = new List { "John", "Max", "Pete" };
bool has = customers.Any(cus => names.Contains(cus.FirstName));
this will produce the necessary IN('value1', 'value2' ...) functionality that you were looking for

How to Get Top 5 Rated User in 2 Tables with LINQ using C#

I have 2 tables, one is Posts another is Comments. These tables contain "RatedPoint" field.
I want to take 5 users who have the highest point.
For example, user ID =1 and its total point 50 in Post table
and it's total point is 25 in Comment table, so its total point is 75
so, i have to look whole members and after choose 5 highest point
It seems a bit complicated, i hope its clear..
I tried something like that
var abc= csEntity.Users.Where(u => csEntity.Posts.Any(p => u.Id == p.UserId)).
Take(userCount).OrderByDescending(u => u.Posts.Count).ToList();
or..
var xyz = csEntity.Posts.Where(p => csEntity.Comments.Any(c => c.UserId == p.UserId));
I dont want to use 2 different list if possible.. is it possible to do it in one query?
I could do it with 2 for loops, but i think its a bad idea..
Post TABLE
Comments TABLE
As you see, these two tables contain userID and each user has RatedPoint...
I think now its clear
EDIT: Maybe a user never write a comment or never write a post just write a comment.. then i think we musnt make equal posts.userId=comments.UserId
Here is a LINQ expression that does what you seem to be asking for:
var result = from p in posts
join c in comments on p.Id equals c.Id
select new { Id = p.Id, Total = p.Points + c.Points };
That provides the actual joined data. Then you can pick the top 5 like this:
result.OrderByDescending(item => item.Total).Take(5)
Note that the above does assume that both tables always have each user, even if they didn't post or comment. I.e. they would simply have a point count of 0. Your updated question clarifies that in your case, you have potentially disjoint tables, i.e. a user can be in one table but not the other.
In that case, the following should work for you:
var leftOuter = from p in posts
join c in comments on p.Id equals c.Id into groupJoin
let c = groupJoin.SingleOrDefault()
select new { Id = p.Id, Total = p.Points + (c == null ? 0 : c.Points) };
var rightAnti = from c in comments
join p in posts on c.Id equals p.Id into groupJoin
let p = groupJoin.SingleOrDefault()
where p == null
select new { Id = c.Id, Total = c.Points };
var result = leftOuter.Concat(rightAnti);
The first LINQ expression does a left outer join. The second LINQ expression does a left anti-join (but I call it "right" because it's effectively the right-join of the original data :) ). I'm using SingleToDefault() to ensure that each user is in each table once at most. The code will throw an exception if it turns out they are present more than once (which otherwise would result in that user being represented in the final result more than once).
I admit, I don't know whether the above is the most efficient approach. I think it should be pretty close, since the joins should be optimized (in objects or SQL) and that's the most expensive part of the whole operation. But I make no promises regarding performance. :)

How can I use sum with group by in LINQ?

I have the following many companies. Some of the companies have subjects and others do not. Something like this:
CompanyID Subjects
1 2
2 4
3 1
4 0
I am trying to create a LINQ report that will give me this information. This is what I have so far. It correctly does an outer join so that even companies with no subjects are include in the list. Once I have that data then I group the date by company title. The problem is that the last select does not work correctly. Can someone suggest how I can get the sum. I was able to use count() but I need a sum as the way I have set things up is that when there are no subjects a value of 0 goes into Subjects and where there is 1 a value of one goes there. So by summing the count of Subjects at each break in the group I should be able to find out how many subjects are assigned to the company.
var test1 = from c in companies
join s in subjects
on "0000" + c.RowKey equals s.PartitionKey into outer
from s in outer.DefaultIfEmpty()
select new
{
Title = c.Title,
Subjects = ((s == null) ? 0 : 1)
} into split
group split by split.Title into g
select new
{
Title = g.Key,
total = g.sum(s => s.Subjects)
};
You wrote the "sum" method without the capital "s". C# is case sensitive, so I think that's your problem, if you're getting a compile error.
Otherwise include the actual result of your query and what you expect, so we can compare it and try to find the problem.

LINQ to SQL: Complicated query with aggregate data for a report from multiple tables for an ordering system

I want to convert the following query into LINQ syntax. I am having a great deal of trouble managing to get it to work. I actually tried starting from LINQ, but found that I might have better luck if I wrote it the other way around.
SELECT
pmt.guid,
pmt.sku,
pmt.name,
opt.color,
opt.size,
SUM(opt.qty) AS qtySold,
SUM(opt.qty * opt.itemprice) AS totalSales,
COUNT(omt.guid) AS betweenOrders
FROM
products_mainTable pmt
LEFT OUTER JOIN
orders_productsTable opt ON opt.products_mainTableGUID = pmt.guid
LEFT OUTER JOIN orders_mainTable omt ON omt.guid = opt.orders_mainTableGUID AND
(omt.flags & 1) = 1
GROUP BY
pmt.sku, opt.color, opt.size, pmt.guid, pmt.name
ORDER BY
pmt.sku
The end result is a table that shows me information about a product as you can see above.
How do I write this query, in LINQ form, using comprehension syntax ?
Additionally, I may want to add additional filters (to the orders_mainTable, for instance).
Here is one example that I tried to make work, and was fairly close but am not sure if it's the "correct" way, and was not able to group it by size and color from the orders_productsTable.
from pmt in products_mainTable
let Purchases =
from opt in pmt.orders_productsTable
where ((opt.orders_mainTable.flags & 1) == 1)
where ((opt.orders_mainTable.date_completedon > Convert.ToDateTime("01/01/2009 00:00:00")))
select opt
orderby pmt.sku
select new {
pmt.guid,
pmt.sku,
pmt.name,
pmt.price,
AvgPerOrder = Purchases.Average(p => p.qty).GetValueOrDefault(0),
QtySold = Purchases.Sum(p => p.qty).GetValueOrDefault(),
SoldFor = Purchases.Sum(p => p.itemprice * p.qty).GetValueOrDefault()
}
*Edit:
To be a little more explicit so you can understand what I am trying to do, here is some more explanation.
Products are stored in products_mainTable
Orders are stored in orders_mainTable
Products That Have Been Ordered are stored in orders_productsTable
I want to create several reports based on products, orders, etc. drilling into the data and finding meaningful bits to display to the end user.
In this instance, I am trying to show which products have been purchased over a period of time, and are the most popular. How many sold, for what price, and what is the breakout per order. Maybe not the best order, but I'm just experimenting and picked this one.
All of the tables have relationships to other tables. So from the product table, I can get to what orders ordered that product, etc.
The largest problem I am having, is understanding how LINQ works, especially with grouping, aggregate data, extensions, subqueries, etc. It's been fun, but it's starting to get frustrating because I am having difficulty finding detailed explanations on how to do this.
I'm also a beginner in LINQ. I don't know if this is the right way of grouping by several fields but I think you have to transform these grouping fields into a representing key. So, assuming that all your grouping fields are strings or ints you can make a key as follows:
var qry = from pmt in products_mainTable
join opt in orders_productsTable on pmt.guid equals opt.products_mainTableGUID
join omt in orders_mainTable on opt.orders_mainTableGUID equals omt.guid
where (opt.orders_mainTable.flags & 1) == 1
group omt by pmt.sku + opt.price + opt.size + pmt.guid + pmt.name into g
orderby g.sku
select new
{
g.FirstOrDefault().guid,
g.FirstOrDefault().sku,
g.FirstOrDefault().name,
g.FirstOrDefault().color,
g.FirstOrDefault().price,
AvgPerOrder = g.Average(p => p.qty).GetValueOrDefault(0),
QtySold = g.Sum(p => p.qty).GetValueOrDefault(),
SoldFor = g.Sum(p => p.itemprice * p.qty).GetValueOrDefault()
};
I didn't test this so please see if this helps you in any way.
Bruno, thank you so much for your assistance! The FirstOrDefault() was probably the largest help. Following some of what you did, and another resource I came up with the following that seems to work beautifully! This LINQ query below gave me nearly an exact replication of the SQL I posted above.
Here's the other resource I found on doing a LEFT OUTER JOIN in LINQ: Blog Post
Final Answer:
from pmt in products_mainTable
join opt in orders_productsTable on pmt.guid equals opt.products_mainTableGUID into tempProducts
from orderedProducts in tempProducts.DefaultIfEmpty()
join omt in orders_mainTable on orderedProducts.orders_mainTableGUID equals omt.guid into tempOrders
from ordersMain in tempOrders.DefaultIfEmpty()
group pmt by new { pmt.sku, orderedProducts.color, orderedProducts.size } into g
orderby g.FirstOrDefault().sku
select new {
g.FirstOrDefault().guid,
g.Key.sku,
g.Key.size,
QTY = g.FirstOrDefault().orders_productsTable.Sum(c => c.qty),
SUM = g.FirstOrDefault().orders_productsTable.Sum(c => c.itemprice * c.qty),
AVG = g.FirstOrDefault().orders_productsTable.Average(c => c.itemprice * c.qty),
Some = g.FirstOrDefault().orders_productsTable.Average(p => p.qty).GetValueOrDefault(0),
}
This was very helpful to me thanks. I had a similar issue I was trying to sort through only my case was much simpler as I didn't have any joins in it. I was simply trying to group one field, get the min of another, and the count. (min and count in the same query)
Here is the SQL I wanted to recreate in Linq syntax:
select t.Field1, min(t.Field2), COUNT(*)
from SomeTable t
group by t.Field1
order by t.Field1
Thanks to your post I eventually managed to come up with this:
from t in SomeTable
group t by new { t.Field1 } into g
orderby g.Key.Field1
select new
{
g.Key.Field1,
code = g.Min(c => c.Field2),
qty = g.Count()
}
Which creates the following SQL behind the scenes:
SELECT [t1].[Field1], [t1].[value] AS [code], [t1].[value2] AS [qty]
FROM (
SELECT MIN([t0].[Field2]) AS [value], COUNT(*) AS [value2], [t0].[Field1]
FROM [SomeTable] AS [t0]
GROUP BY [t0].[Field1]
) AS [t1]
ORDER BY [t1].[Field1]
Perfect, exactly what I was looking to do. The key for me was that you showed it possible to do this inside the new {} which is something I had never considered trying. This is huge, I now feel like I have a significantly better understanding going forward.

Categories

Resources