Linq to SQL brief question - c#

I have a query below. although can anyone point out what "from p" means? and also "var r"?
DataClasses1DataContext db = new DataClasses1DataContext();
var r = from p in db.Products
where p.UnitPrice > 15 // If unit price is greater than 15...
select p; // select entries

r is the composed query - an IQueryable<Product> or similar; note the query has not yet executed - it is just a pending query. var means "compiler, figure out the type of r from the expression on the right". You could have stated it explicitly in this case, but not all. But it wouldn't add any value, so var is fine.
p is a convenience marker for each product; the query is "for each product (p), restricting to those with unit price greater than 15 (where p > 15), select that product (select p) as a result.
Ultimately this compiles as:
IQueryable<Product> r =
db.Products.Where(p => p.UnitPrice > 15);
(in this case, a final .Select(p => p) is omitted by the compiler, but with a non-trivial projection, or a trivial query, the .Select(...) is retained)

The p means each specific item in the collection referenced (db.Products). See from on MSDN.
var is syntactic sugar - it resolves to the type returned from the LINQ query, assigning the type to the variable r. See var on MSDN.
For better understanding of LINQ, I suggest reading through 101 LINQ Samples.

from p means any record from db.Product and var r means the collection of p
overall whole statements means give me all those records(p) from db.Products where p.UnitPrice is greater than 15
see this question to know more about var

Related

Understanding DefaultIfEmpty in LINQ

I don't understand how DefaultIfEmpty method works. It is usually used to be reminiscent of left-outer join in LINQ.
DefaultIfEmpty() method must be run on a collection.
DefaultIfEmpty() method cannot be run on null collection reference.
A code example I don't understand some points that
Does p, which is after into keyword, refer to products?
Is ps the group of product objects? I mean a sequence of sequences.
If DefaultIfEmpty() isn't used, doesn't p, from p in ps.DefaultIfEmpty(), run into select? Why?
,
#region left-outer-join
string[] categories = {
"Beverages",
"Condiments",
"Vegetables",
"Dairy Products",
"Seafood"
};
List<Product> products = GetProductList();
var q = from c in categories
join p in products on c equals p.Category into ps
from p in ps.DefaultIfEmpty()
select (Category: c, ProductName: p == null ? "(No products)" : p.ProductName);
foreach (var v in q)
{
Console.WriteLine($"{v.ProductName}: {v.Category}");
}
#endregion
Code from 101 Examples of LINQ.
I ain't generally answer my own question, however, I think some people might find the question somewhat intricate.
In the first step, the working logic of the DefaultIfEmpty method group should be figured out(LINQ doesn't support its overloaded versions, by the by).
class foo
{
public string Test { get; set; }
}
// list1
var l1 = new List<foo>();
//l1.Add(null); --> try the code too by uncommenting
//list2
var l2 = l1.DefaultIfEmpty();
foreach (var x in l1)
Console.WriteLine((x == null ? "null" : "not null") + " entered l1");
foreach (var x in l2)
Console.WriteLine((x == null ? "null" : "not null") + " entered l2");
When being run, seeing that it gives null entered l2 out result out.
What if l1.Add(null); is commented in? It is at your disposal, not hard to guess at all.
l2 has an item which is of null since foo is not one of the building block types like Int32, String, or Char. If it were, default promotion would be applied to, e.g. for string, " "(blank character) is supplied to.
Now let's examine the LINQ statement being mentioned.
Just for a remembrance, unless an aggregate operator or a To{a
collection}() is applied to a LINQ expression, lazy evaluation(honor
deferred) is carried out.
The followed image, albeit not belonging to C#, helps to get what it means.
In the light of the lazy evaluation, we are now wisely cognizant of the fact that the LINQ using query expression is evaluated when requested, that is, on-demand.
So, ps contains product items iff the equality expressed at on keyword of join is satisfied. Further, ps has different product items at each demand of the LINQ expression. Otherwise, unless DefaultIfEmpty() is used, select is not hit thereby not iterating over and not yielding any Console.WriteLine($"{productName}: {category}");. (Please correct me at this point if I'm wrong.)
Answers
Does p refer to products after into keyword?
The p in the from clause is a new local variable referring to a single product of one category.
Is ps the group of product objects? I mean a sequence of sequences.
Yes, ps is the group of products for the category c. But it is not a sequence of sequences, just a simple IEnumerable<Product>, just like c is a single category, not all categories in the group join.
In the query you only see data for one result row, never the whole group join result. Look at the final select, it prints one category and one product it joined with. That product comes from the ps group of product that one category joined with.
The query then does the walking over all categories and all their groups of products.
If DefaultIfEmpty() isn't used, doesn't p, from p in ps.DefaultIfEmpty(), run into select? Why?
It is not equal to a Select, because the from clause creates a new join with itself, which turns into SelectMany.
Structure
Taking the query by parts, first the group join:
from c in categories
join p in products on c equals p.Category into ps
After this only c and ps are usable, representing a category and its joined products.
Now note that the whole query is in the same form as:
from car in Cars
from passenger in car.Passengers
select (car, passenger)
Which joins Cars with its own Passengers using Cars.SelectMany(car => car.Passengers, (car, passenger) => (car, passenger));
So in your query
from group_join_result into ps
from p in ps.DefaultIfEmpty()
creates a new join of the previous group join result with its own data (lists of grouped products) ran through DefaultIfEmpty using SelectMany.
Conclusion
In the end the complexity is in the Linq query and not the DefaultIfEmpty method. The method is simply explained on the MSDN page i posted in comment. It simply turns a collection with no elements into collection that has 1 element, which is either the default() value or the supplied value.
Compiled source
This is approximately the C# code the query gets compiled to:
//Pairs of: (category, the products that joined with the category)
IEnumerable<(string category, IEnumerable<Product> groupedProducts)> groupJoinData = Enumerable.GroupJoin(
categories,
products,
(string c) => c,
(Product p) => p.Category,
(string c, IEnumerable<Product> ps) => (c, ps)
);
//Flattening of the pair collection, calling DefaultIfEmpty on each joined group of products
IEnumerable<(string Category, string ProductName)> q = groupJoinData.SelectMany(
catProdsPair => catProdsPair.groupedProducts.DefaultIfEmpty(),
(catProdsPair, p) => (catProdsPair.category, (p == null) ? "(No products)" : p.ProductName)
);
Done with the help of ILSpy using C# 8.0 view.

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. :)

Help Converting T-SQL to LINQ

Have the following (non-straightforward) T-SQL query, which i'm trying to convert to LINQ (to be used in a L2SQL expression):
declare #IdAddress int = 481887
select * from
(
select top 3 p.*
from tblProCon p
inner join vwAddressExpanded a
on p.IdPrimaryCity = a.IdPrimaryCity
where a.AddressType = 3
and p.IsPro = 1
and a.IdAddress = #IdAddress
order by AgreeCount desc
) as Pros
union
select * from
(
select top 3 p.*
from tblProCon p
inner join vwAddressExpanded a
on p.IdPrimaryCity = a.IdPrimaryCity
where a.AddressType = 3
and p.IsPro = 0
and a.IdAddress = #IdAddress
order by AgreeCount desc
) as Cons
order by ispro desc, AgreeCount desc
In a nutshell, i have an #IdAddress - and i'm trying to find the top 3 pro's and top 3 con's for that address.
The above query does work as expected. I'm not entirely sure how to convert it to a LINQ query (never done unions before with LINQ). I don't even know where to start. :)
Query-style/Lambda accepted (prefer query-style, for readability).
Also - i have LinqPad installed - but i'm not sure how to "convert T-SQL to Linq" - is there an option for that? Bonus upvote will be awarded for that. :)
The above T-SQL query performs well, and this L2SQL query will be executed frequently, so it needs to perform pretty well.
Appreciate the help.
var baseQuery = (from p in db.tblProCon
join a in db.vwAddresssExpanded
on p.IdPrimaryCity equals a.IdPrimaryCity
where a.AddressType == (byte) AddressType.PrimaryCity &&
a.IdAddress == idAddress
order by p.AgreeCount descending
select p);
var pros = baseQuery.Where(x=> x.IsPro).Take(3);
var cons = baseQuery.Where(x=> !x.IsPro).Take(3);
var results = pros
.Union(cons)
.OrderByDescending(x => x.IsPro)
.ThenByDescending(x => x.AgreeCount)
.ToList();
You can call (some query expression).Union(other query expression).
You can also (equivalently) write Enumerable.Union(some query expression, other query expression).
Note that both expressions must return the same type.
AFAIK, there are no tools that automatically convert SQL to LINQ.
(For non-trivial SQL, that's a non-trivial task)

Confusion over storing a type in a temp variable in a LINQ query

I am writing a LINQ query which has a sort of subquery. It looks like this:
var x = from p in pc
let Cntrs = p.GetCounters().Where(args => args.CounterName == CounterName)
select Cntrs;
Cntrs is a PerformanceCounter object. However, the intellisense returns it as a collection with collection methods.
I don't see the properties such as NextValue, etc (which is what I want).
What am I missing? Also, and I know this must have been asked before, what's the difference between the let and into keywords?
Thanks
This means that the Where method is returning more than one instance, in other words you have more than one counter whose name equals the value of your CounterName variable.
If you don't care which one you get you can use the FirstOrDefault extension method to retrieve the first item from the sequence that is being returned from the Where like this:
var x = from p in pc
let Cntrs = p.GetCounters()
.Where(args => args.CounterName == CounterName)
.FirstOrDefault()
select Cntrs;
Don't use the var keyword, and the mystery will be resolved.
x is an IEnumerable<PerformanceCounter>
Also, and I know this must have been
asked before, what's the difference
between the let and into keywords?
The keyword let introduces a new query variable into scope.
from c in Customer
let firstOrder = c.Orders.First()
select new {c, firstOrder}
The keyword into introduces a new query variable into scope and removes all previous query variables from scope.
from c in Customer
select new {c.Name, c.Orders} into smallerCustomer
select smallerCustomer.Name
c is not in scope in that last select clause.
from c in Customer
group c by c.Name[0] into g
select g.First()

Categories

Resources