Linq FirstOrDefault List inside a query - linq

I have this linq query
var numberGroups =
from n in VISRUBs.Where(a => a.VISANA.VISITE.DATEVIS <= d && a.VISANA.VISITE.PANUM == p)
group n by n.RUBRIQUE into g
select new {
RemainderCHAPLIB = g.Key.ANALYSE.CHAPITRE.LIBELLE,
RemainderLIB = g.Key.LIBELLE,
RemainderRUNUM = g.Key.RUNUM,
vals = from vlist in g.OrderByDescending(a=>a.VISANA.VISITE.DATEVIS)
select vlist.VALEUR
};
which gives me this result in Linqpad
What I want is to select the first and second item from the last field (vals) which is a List<string>.
I have tried this:
var numberGroups =
from n in VISRUBs.Where(a => a.VISANA.VISITE.DATEVIS <= d && a.VISANA.VISITE.PANUM == p)
group n by n.RUBRIQUE into g
select new {
RemainderCHAPLIB = g.Key.ANALYSE.CHAPITRE.LIBELLE,
RemainderLIB = g.Key.LIBELLE,
RemainderRUNUM = g.Key.RUNUM,
vals = from vlist in g.OrderByDescending(a => a.VISANA.VISITE.DATEVIS)
select vlist.VALEUR
};
var lst = from n in numberGroups
select new
{
RemainderCHAPLIB = n.RemainderCHAPLIB,
RemainderLIB = n.RemainderLIB,
RemainderRUNUM = n.RemainderRUNUM,
VAL = n.vals.FirstOrDefault()
};
but it didn't work, I got an exception:
Dynamic SQL ErrorSQL error code = -104Token unknown - line 54, column 1OUTER

found it !
var lst = from n in numberGroups.ToList()
select new
{
RemainderCHAPLIB = n.RemainderCHAPLIB,
RemainderLIB = n.RemainderLIB,
RemainderRUNUM = n.RemainderRUNUM,
VAL = n.vals.FirstOrDefault(),
ANT = n.vals.Skip(1).FirstOrDefault()
};

Related

LINQ Grouping: Is there a cleaner way to do this without a for loop

I am trying to create a very simple distribution chart and I want to display the counts of tests score percentages in their corresponding 10's ranges.
I thought about just doing the grouping on the Math.Round((d.Percentage/10-0.5),0)*10 which should give me the 10's value....but I wasn't sure the best way to do this given that I would probably have missing ranges and all ranges need to appear even if the count is zero. I also thought about doing an outer join on the ranges array but since I'm fairly new to Linq so for the sake of time I opted for the code below. I would however like to know what a better way might be.
Also note: As I tend to work with larger teams with varying experience levels, I'm not all that crazy about ultra compact code unless it remains very readable to the average developer.
Any suggestions?
public IEnumerable<TestDistribution> GetDistribution()
{
var distribution = new List<TestDistribution>();
var ranges = new int[] { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110 };
var labels = new string[] { "0%'s", "10%'s", "20%'s", "30%'s", "40%'s", "50%'s", "60%'s", "70%'s", "80%'s", "90%'s", "100%'s", ">110% "};
for (var n = 0; n < ranges.Count(); n++)
{
var count = 0;
var min = ranges[n];
var max = (n == ranges.Count() - 1) ? decimal.MaxValue : ranges[n+1];
count = (from d in Results
where d.Percentage>= min
&& d.Percentage<max
select d)
.Count();
distribution.Add(new TestDistribution() { Label = labels[n], Frequency = count });
}
return distribution;
}
// ranges and labels in a list of pairs of them
var rangesWithLabels = ranges.Zip(labels, (r,l) => new {Range = r, Label = l});
// create a list of intervals (ie. 0-10, 10-20, .. 110 - max value
var rangeMinMax = ranges.Zip(ranges.Skip(1), (min, max) => new {Min = min, Max = max})
.Union(new[] {new {Min = ranges.Last(), Max = Int32.MaxValue}});
//the grouping is made by the lower bound of the interval found for some Percentage
var resultsDistribution = from c in Results
group c by
rangeMinMax.FirstOrDefault(r=> r.Min <= c.Percentage && c.Percentage < r.Max).Min into g
select new {Percentage = g.Key, Frequency = g.Count() };
// left join betweem the labels and the results with frequencies
var distributionWithLabels =
from l in rangesWithLabels
join r in resultsDistribution on l.Range equals r.Percentage
into rd
from r in rd.DefaultIfEmpty()
select new TestDistribution{
Label = l.Label,
Frequency = r != null ? r.Frequency : 0
};
distribution = distributionWithLabels.ToList();
Another solution if the ranges and labels can be created in another way
var ranges = Enumerable.Range(0, 10)
.Select(c=> new {
Min = c * 10,
Max = (c +1 )* 10,
Label = (c * 10) + "%'s"})
.Union(new[] { new {
Min = 100,
Max = Int32.MaxValue,
Label = ">110% "
}});
var resultsDistribution = from c in Results
group c by ranges.FirstOrDefault(r=> r.Min <= c.Percentage && c.Percentage < r.Max).Min
into g
select new {Percentage = g.Key, Frequency = g.Count() };
var distributionWithLabels =
from l in ranges
join r in resultsDistribution on l.Min equals r.Percentage
into rd
from r in rd.DefaultIfEmpty()
select new TestDistribution{
Label = l.Label,
Frequency = r != null ? r.Frequency : 0
};
This works
public IEnumerable<TestDistribution> GetDistribution()
{
var range = 12;
return Enumerable.Range(0, range).Select(
n => new TestDistribution
{
Label = string.Format("{1}{0}%'s", n*10, n==range-1 ? ">" : ""),
Frequency =
Results.Count(
d =>
d.Percentage >= n*10
&& d.Percentage < ((n == range - 1) ? decimal.MaxValue : (n+1)*10))
});
}

How can I intersect more than two sets/lists of values?

Here is an example that works in Linqpad. The problem is that I need it to work for more than two words, e.g. searchString = "headboard bed railing". This is a query against an index and instead of "Match Any Word" which I've done, I need it to "Match All Words", where it finds common key values for each of the searched words.
//Match ALL words for categories in index
string searchString = "headboard bed";
List<string> searchList = new List<string>(searchString.Split(' '));
string word1 = searchList[0];
string word2 = searchList[1];
var List1 = (from i in index
where i.word.ToUpper().Contains(word1)
select i.category.ID).ToList();
var List2 = (from i in index
where i.word.ToUpper().Contains(word2)
select i.category.ID).ToList();
//How can I make this work for more than two Lists?
var commonCats = List1.Intersect(List2).ToList();
var category = (from i in index
from s in commonCats
where commonCats.Contains(i.category.ID)
select new
{
MajorCategory = i.category.category1.description,
MinorCategory = i.category.description,
Taxable = i.category.taxable,
Life = i.category.life,
ID = i.category.ID
}).Distinct().OrderBy(i => i.MinorCategory);
category.Dump();
Thanks!
Intersection of an intersection is commutative and associative. This means that (A ∩ B ∩ C) = (A ∩ (B ∩ C)) = ((A ∩ B) ∩ C), and rearranging the order of the lists will not change the result. So just apply .Intersect() multiple times:
var commonCats = List1.Intersect(List2).Intersect(List3).ToList();
So, to make your code more general:
var searchList = searchString.Split(' ');
// Change "int" if this is not the type of i.category.ID in the query below.
IEnumerable<int> result = null;
foreach (string word in searchList)
{
var query = from i in index
where i.word.ToUpper().Contains(word1)
select i.category.ID;
result = (result == null) ? query : result.Intersect(query);
}
if (result == null)
throw new InvalidOperationException("No words supplied.");
var commonCats = result.ToList();
To build on #cdhowie's answer, why use Intersect? I would think you could make it more efficient by building your query in multiple steps. Something like...
if(string.IsNullOrWhitespace(search))
{
throw new InvalidOperationException("No word supplied.");
}
var query = index.AsQueryable();
var searchList = searchString.Split(' ');
foreach (string word in searchList)
{
query = query.Where(i => i.word.ToUpper().Contains(word));
}
var commonCats = query.Select(i => i.category.ID).ToList();

C# EF Linq bitwise question

Ok for example, I am using bitwise like such: Monday = 1, Tuesday = 2, Wednesday = 4, Thursday = 8 etc...
I am using an Entity Framework class of Business.
I am using a class and passing in a value like 7 (Monday, Tuesday, Wednesday).
I want to return records that match any of those days
public List<Business> GetBusinesses(long daysOfWeek)
{
using (var c = Context())
{
return c.Businesses.Where(???).ToList();
}
}
Any help would be appreciated. Thanks!
EDIT
Ok, so I am attempting the following:
var b = new List<Business>();
var b1 = new Business(){DaysOfWeek = 3};
b.Add(b1);
var b2 = new Business() { DaysOfWeek = 2 };
b.Add(b2);
var decomposedList = new[]{1};
var l = b.Where(o => decomposedList.Any(day => day == o.DaysOfWeek)).ToList();
But l returns 0 results assuming in the decomposedList(1) I am looking for monday.
I created b1 to contain Monday and Tuesday.
Use the bitwise and operator & to combine your desired flags with the actual flags in the database and then check for a non-zero result.
var b1 = new { DaysOfWeek = 3 };
var b2 = new { DaysOfWeek = 2 };
var b = new[] { b1, b2 };
var filter = 1;
var l = b.Where(o => (filter & o.DaysOfWeek) != 0);
foreach (var x in l)
{
Console.WriteLine(x);
}
If you have an array of filter values simply combined then with an OR | first:
var filterArray = new []{1, 4};
var filter = filterArray.Aggregate((x, y) => x | y);
You must decompose the long value(bitflagged enum will be better) to it's parts then pass it to Where
return c.Businesses.Where(o=> DecomposeDays(dayParam).Any(day => day==o)).ToList();
EDIT:
decompose method:
private static IEnumerable<byte> DecomposeDays(byte dayParam)
{
var daysOfWeek = new List<byte> { 1, 2, 4, 6, 8 ,16};
return daysOfWeek.Where(o => (o & dayParam) == dayParam);
}

How to calculate multiple averages in one query in linq to entities

How to do this in linq to entities in one query?
SELECT avg(Column1), avg(Column2), ... from MyTable
where ColumnX = 234
??
You could do something like that:
var averages = myTable
.Where(item => item.ColumnX == 234)
.Aggregate(
new { count = 0, sum1 = 0.0, sum2 = 0.0 },
(acc, item) => new { count = acc.count + 1, sum1 = acc.sum1 + item.Column1, sum2 = acc.sum2 + item.Column2 },
acc => new { avg1 = acc.sum1 / acc.count, avg2 = acc.sum2 / acc.count });
Note the call to AsEnumerable() to force Aggregate to be executed locally (as EF probably doesn't know how to convert it to SQL) Actually it seems to work ;)
Alternatively, you could use this query:
var averages =
from item in table
where item.ColumnX == 234
group item by 1 into g
select new
{
Average1 = g.Average(i => i.Column1),
Average2 = g.Average(i => i.Column2)
};
The use of group by here is not very intuitive, but it's probably easier to read than the other solution. Not sure it can be converted to SQL though...

Using GroupBy, Count and Sum in LINQ Lambda Expressions

I have a collection of boxes with the properties weight, volume and owner.
I want to use LINQ to get a summarized list (by owner) of the box information
e.g.
**Owner, Boxes, Total Weight, Total Volume**
Jim, 5, 1430.00, 3.65
George, 2, 37.50, 1.22
Can someone show me how to do this with Lambda expressions?
var ListByOwner = list.GroupBy(l => l.Owner)
.Select(lg =>
new {
Owner = lg.Key,
Boxes = lg.Count(),
TotalWeight = lg.Sum(w => w.Weight),
TotalVolume = lg.Sum(w => w.Volume)
});
var q = from b in listOfBoxes
group b by b.Owner into g
select new
{
Owner = g.Key,
Boxes = g.Count(),
TotalWeight = g.Sum(item => item.Weight),
TotalVolume = g.Sum(item => item.Volume)
};
var boxSummary = from b in boxes
group b by b.Owner into g
let nrBoxes = g.Count()
let totalWeight = g.Sum(w => w.Weight)
let totalVolume = g.Sum(v => v.Volume)
select new { Owner = g.Key, Boxes = nrBoxes,
TotalWeight = totalWeight,
TotalVolume = totalVolume }

Resources