I want to print Count using Groupby and Left join in linq - c#

I having two list or table as per below:
Query:
var q = db.tbl_User_to_CustomerMast
.Where(i => i.fk_Membership_ID == m.MembershipID)
.Join(
db.tbl_CustomerMast,
u => u.fk_Customer_ID,
c => c.CustomerID,
(u, c) => new { UserCustomer = u, Customer = c })
.Where(i => i.UserCustomer.fk_Store_ID == shopid).ToList();
Output:
List A:
User_Customer_ID Name
===================================
1 XYZ
2 ABC
Query:
var rewards = q.Join(
db.tbl_RewardAwardMast,
i => i.UserCustomer.User_Customer_ID,
j => j.fk_Customer_UserID,
(i, j) => new { Customer = i, Reward = j })
.Where(i => i.Reward.RewardDate >= i.Customer.UserCustomer.Membership_Start)
.GroupBy(i => i.Reward.fk_Customer_UserID)
.Select(i => new { CustomerID = i.Key, RewardCount = i.Count()})
.ToList();
Output:
List B:
User_Customer_ID RewardCount
===================================
1 5
Here is final Output Table
User_Customer_ID Name RewardCount
===============================================
1 XYZ 5
2 ABC 0
If I want to check that which user_customer_ID has less than 5 Reward Count, How I will Check:
Query:
var final = q.GroupJoin(
rewards,
i => i.UserCustomer.User_Customer_ID,
j => j.CustomerID,
(i, j) => new { Customer = i, Reward = j.DefaultIfEmpty() })
.Select(i => new { Count = i.Reward, id = i.Customer.UserCustomer.User_Customer_ID })
.ToList();
var final1 = final.Where(i => i.Count < m.MembershipMinVisit.Value).ToList();
Error:
Operator '<' cannot be applied to operands of type 'System.Collections.Generic.IEnumerable' and 'int'

You don't need a group join here as for each customer you need a single result (reward). Also because you need only customers with rewards < 5, an inner join using that condition wil give you what you want:
var final = q.Join( // Join instead of GroupJoin
rewards.Where(r => r.RewardCount < 5), // filter out rewards >= 5
i => i.UserCustomer.User_Customer_ID,
j => j.CustomerID,
(i, j) => new { Customer = i, Reward = j })
.Select(i => new {
Reward = i.Reward, // 'Count' is a bad name
// it is still the reward object
id = i.Customer.UserCustomer.User_Customer_ID
})
.ToList();
In your original query, Count (bad name) is a collection (IEnumerable) of awards, that's why you get that error. To fix it, you have to check that the single returned reward is not null (to filter out users without rewards at all, because you use a left join) and that it has a RewardCount less that 5:
var final1 = final.Where(i => i.Count.Single() != null &&
i.Count.Single().RewardCount < 5)
.ToList();

Related

Linq: differences between C# and VB.NET

While converting some of my code from VB to C# I'm having a hard time with this query:
Dim r = From v In VTab
Group v By v.ID Into g = Group
Select
ID,
Value = g.Where(Function(x) [...]).Sum(Function(x) x.Value),
Ann = (g.Where(Function(X) [...]).Count > 0)
From f In FTab.Where(Function(x) Value >= x.Min And Value <= x.Max).DefaultIfEmpty
Select New Result With {
.ID = ID,
.Value = Value,
.Ann = Ann,
.Data = f.Data
}
This basically groups values from VTab, obtaining one row per ID with a sum and a boolean, then joins this partial result with FTab and gives the final result.
This is the closest result I could get:
var r = from v in VTab
group v by v.ID into g
select g.Where(x => [...]).Sum(x => x.Value) into Value
from f in FTab.Where(x => Value >= x.Min && Value <= x.Max)
select new Result
{
Value = Value
};
I don't know how to specify multiple fields in the first select.
This is what I tried:
select g.Where(x => [...]).Sum(x => x.Value) into Value
select g.Where(x => [...]).Count() > 0 into Ann // g doesn't exist in the current context
// Syntax error
select g.Where(x => [...]).Sum(x => x.Value) into Value, g.Where(x => [...]).Count() > 0 into Ann
// Syntax error
select g.Where(x => [...]).Sum(x => x.Value) into Value,
select g.Where(x => [...]).Count() > 0 into Ann
// ; expected at the end of the line and I can't continue with the query
select new { Value = g.Where(x => [...]).Sum(x => x.Value), Ann = g.Where(x => [...]).Count() > 0}
How about this:
var r = VTab
.GroupBy(x => x.ID)
.Select(x => new {
ID = x.Key,
Value = x.Where(y => true).Sum(y => 1),
Ann = x.Where(y => true).Count() > 0
})
.Select(x => new Result() {
ID = x.ID,
Value = x.Value,
Ann = x.Ann,
Data = FTab.Where(y => x.Value >= y.Min && x.Value <= y.Max).FirstOrDefault()?.Data
});
After some more trial and error, I've been able to translate my query:
var r = from v in VTab
group v by v.ID into g
select new
{
ID = g.Key,
Value = g.Where(x => [...]).Sum(x => x.Value),
Ann = g.Where(x => [...]).Count() > 0}
} into t
from f in FTab.Where(x => t.Value >= x.Min && t.Value <= x.Max).DefaultIfEmpty()
select new Result
{
ID = t.ID,
Value = t.Value,
Ann = t.Ann,
Data = f?.Data
};
The key here is to use "into t" to identify the object created in the first select, and then refer to it in the second one.

Ranking Search Results With LINQ/.NET MVC

I have a task where I need to rank the search results based on which column the search term was found.
So for example, if the search term is found in column A of table 1, it ranks higher than if it was found in column A of table 2.
Right now, I have a linq query that joins multiple tables and searches for the search term in certain columns. I.E.
var results = db.People
.Join(db.Menu, p => p.ID, m => m.PersonID, (p, m) => new { p = p, m = m })
.Join(db.Domain, m => m.m.DomainID, d => d.ID, (m, d) => new { m = m, d = d })
.Where(d => searchTermArray.Any(x => d.m.p.p.Name.Contains(x)) || searchTermArray.Any(x => d.m.p.p.Biography.Contains(x)) || searchTermArray.Any(x => d.d.domain.Contains(x)))
.Select(p => p).Distinct();
So if the search term is found in db.People, column Name, that row/Person will rank higher than if found in db.People, column Biography, which will rank higher than if found in db.Domain, column domain.
This will order your result by the "rank". You can manipulate the query further if you also want to return the rank and not only the aggregate:
var results = db.People
.Join(db.Menu, p => p.ID, m => m.PersonID, (p, m) => new { p = p, m = m })
.Join(db.Domain, m => m.m.DomainID, d => d.ID, (m, d) => new { m = m, d = d })
.Select(d => new
{
rank = searchTermArray.Any(x => d.m.p.p.Name.Contains(x)) ? 3 : searchTermArray.Any(x => d.m.p.p.Biography.Contains(x)) ? 2 : searchTermArray.Any(x => d.d.domain.Contains(x)) ? 1 : 0,
m = d
})
.Where(a => a.rank > 0)
.OrderByDescending(a => a.rank)
.Select(a => a.m).Distinct();
Note: I take no responsibility for poor performance, that's LINQ after all.

How to rank a list with original order in c#

I want to make a ranking from a list and output it on original order.
This is my code so far:
var data = new[] { 7.806468478, 7.806468478, 7.806468478, 7.173501754, 7.173501754, 7.173501754, 3.40877696, 3.40877696, 3.40877696,
4.097010736, 4.097010736, 4.097010736, 4.036494085, 4.036494085, 4.036494085, 38.94333318, 38.94333318, 38.94333318, 14.43588131, 14.43588131, 14.43588131 };
var rankings = data.OrderByDescending(x => x)
.GroupBy(x => x)
.SelectMany((g, i) =>
g.Select(e => new { Col1 = e, Rank = i + 1 }))
.ToList();
However, the result will be order it from descending:
What I want is to display by its original order.
e.g.: Rank = 3, Rank = 3, Rank = 3, Rank = 4, Rank = 4, Rank = 4, etc...
Thank You.
Using what you have, one method would be to keep track of the original order and sort a second time (ugly and potentially slow):
var rankings = data.Select((x, i) => new {Item = x, Index = i})
.OrderByDescending(x => x.Item)
.GroupBy(x => x.Item)
.SelectMany((g, i) =>
g.Select(e => new {
Index = e.Index,
Item = new { Col1 = e.Item, Rank = i + 1 }
}))
.OrderBy(x => x.Index)
.Select(x => x.Item)
.ToList();
I would instead suggest creating a dictionary with your rankings and joining this back with your list:
var rankings = data.Distinct()
.OrderByDescending(x => x)
.Select((g, i) => new { Key = g, Rank = i + 1 })
.ToDictionary(x => x.Key, x => x.Rank);
var output = data.Select(x => new { Col1 = x, Rank = rankings[x] })
.ToList();
As #AntonínLejsek kindly pointed out, replacing the above GroupBy call with Distinct() is the way to go.
Note doubles are not a precise type and thus are really not a good candidate for values in a lookup table, nor would I recommend using GroupBy/Distinct with a floating-point value as a key. Be mindful of your precision and consider using an appropriate string conversion. In light of this, you may want to define an epsilon value and forgo LINQ's GroupBy entirely, opting instead to encapsulate each data point into a (non-anonymous) reference type, then loop through a sorted list and assign ranks. For example (disclaimer: untested):
class DataPoint
{
decimal Value { get; set; }
int Rank { get; set; }
}
var dataPointsPreservingOrder = data.Select(x => new DataPoint {Value = x}).ToList();
var sortedDescending = dataPointsPreservingOrder.OrderByDescending(x => x.Value).ToList();
var epsilon = 1E-15; //use a value that makes sense here
int rank = 0;
double? currentValue = null;
foreach(var x in sortedDescending)
{
if(currentValue == null || Math.Abs(x.Value - currentValue.Value) > epsilon)
{
currentValue = x.Value;
++rank;
}
x.Rank = rank;
}
From review of the data you will need to iterate twice over the result set.
The first iteration will be to capture the rankings as.
var sorted = data
.OrderByDescending(x => x)
.GroupBy(x => x)
.Select((g, i) => new { Col1 = g.First(), Rank = i + 1 })
.ToList();
Now we have a ranking of highest to lowest with the correct rank value. Next we iterate the data again to find where the value exists in the overall ranks as:
var rankings = (from i in data
let rank = sorted.First(x => x.Col1 == i)
select new
{
Col1 = i,
Rank = rank.Rank
}).ToList();
This results in a ranked list in the original order of the data.
A bit shorter:
var L = data.Distinct().ToList(); // because SortedSet<T> doesn't have BinarySearch :[
L.Sort();
var rankings = Array.ConvertAll(data,
x => new { Col1 = x, Rank = L.Count - L.BinarySearch(x) });

Translating SQL to lambda with groupby

I'm trying to translate this sql statement
SELECT row, SUM(value) as VarSum, AVG(value) as VarAve, COUNT(value) as TotalCount
FROM MDNumeric
WHERE collectionid = 6 and varname in ('C3INEV1', 'C3INEVA2', 'C3INEVA3', 'C3INVA11', 'C3INVA17', 'C3INVA19')
GROUP BY row
into an EF 4 query using lambda expressions and am missing something.
I have:
sumvars = sv.staticvararraylist.Split(',');
var aavresult = _myIFR.MDNumerics
.Where(r => r.collectionid == _collid)
.Where(r => sumvars.Contains(r.varname))
.GroupBy(r1 =>r1.row)
.Select(rg =>
new
{
Row = rg.Key,
VarSum = rg.Sum(p => p.value),
VarAve = rg.Average(p => p.value),
TotalCount = rg.Count()
});
where the staticvararraylist has the string 'C3INEV1', 'C3INEVA2', 'C3INEVA3', 'C3INVA11', 'C3INVA17', 'C3INVA19' (without single quotes) and the _collid variable = 6.
While I'm getting the correct grouping, my sum, average, & count values aren't correct.
You didn't post your error message, but I suspect it's related to Contains. I've found that Any works just as well.
This should get you quite close:
var result =
from i in _myIFR.MDNumerics
where i.collectionid == _collid && sumvars.Any(v => i.varname == v)
group i by i.row into g
select new {
row = g.Key,
VarSum = g.Sum(p => p.value),
VarAve = g.Average(p => p.value),
TotalCount = g.Count()
};
Try this:
var aavresult = _myIFR.MDNumerics
.Where(r => r.collectionid == _collid && sumvars.Contains(r.varname))
.GroupBy(r1 =>r1.row,
(key,res) => new
{
Row = key,
VarSum = res.Sum(r1 => r1.value),
VarAve = res.Average(r1 => r1.value),
TotalCount = res.Count()
});

Convert to LINQ lambda expression

Simple line:
var x = (from a in arr select a).First();
Console.WriteLine(“First" + x);
How to convert to Lambda expression?
So you want to convert the LINQ query from using query syntax to plain extension method calls?
// var first = (from a in arr select a).First();
var first = arr.First();
// var last = (from a in arr select a).Last();
var last = arr.Last();
// var filtered = (from a in arr where a == 10 select a).First();
// there are a couple of ways to write this:
var filtered1 = arr.Where(a => a == 10)
.First();
var filtered2 = arr.First(a => a == 10); // produces the same result but obtained differently
// now a very complex query (leaving out the type details)
// var query = from a in arr1
// join b in arr2 on a.SomeValue equals b.AnotherValue
// group new { a.Name, Value = a.SomeValue, b.Date }
// by new { a.Name, a.Group } into g
// orderby g.Key.Name, g.Key.Group descending
// select new { g.Key.Name, Count = g.Count() };
var query = arr1.Join(arr2,
a => a.SomeValue,
b => b.AnotherValue,
(a, b) => new { a, b })
.GroupBy(x => new { x.a.Name, x.a.Group },
x => new { x.a.Name, Value = x.a.SomeValue, x.b.Date })
.OrderBy(g => g.Key.Name)
.ThenByDescending(g => g.Key.Group)
.Select(g => new { g.Key.Name, Count = g.Count() });
When you have an expression of the form (from y in x select y), you can almost always write x instead.

Categories

Resources