Linq query GroupBy value and determine if value meets multiple criteria - linq

Scandata has two fields, hostname and pluginID. There are numerous pluginids listed for each hostname.
In this query, I'm trying to return scanstatus as true for a host when both pluginids are assigned to that host. If only one or none is found, return false. I'm missing something here, but I'm not sure what. This returns false whether one or both pluginids are listed for a given host.
var goodscan = scandata.AsEnumerable()
.GroupBy(g => g.hostname)
.Select(s => new
{
hostname = s.Key,
scanstatus = s.All(v => v.pluginid.Contains("19506") && v.pluginid.Contains("117887"))
})
.ToList();

Your Enumerable.All query checks if ALL scans in each host-group have a pluginid which contains "19506" AND "117887"at the same time. Thats incorrect.
You want to know for each hostname-group if there is at least ONE scan with pluginid "19506" and ANOTHER with "117887", so use Any instead of All:
var goodscan = scandata.AsEnumerable()
.GroupBy(g => g.hostname)
.Select(s => new
{
hostname = s.Key,
scanstatus = s.Any(v => v.pluginid.Contains("19506"))
&& s.Any(v => v.pluginid.Contains("117887"))
})
.ToList();

Related

Using Linq, How can I properly append multiple Where clauses so that they appear in the appropriate order?

The order I would like the end result to appear in is Exact Matches first given a input string, followed by other matches that are Contains for a given field. I tried to approach this in a very rudimentary way as shown here in this example:
var raw = Model.SearchResults.Where(m => m.EffectiveDateTime != null).OrderBy(m => m.EffectiveDateTime).ToList();
var exact = raw.Where(m => m.IssueNumber.ToLower() == Model.SearchText.ToLower());
var contains = raw.Where(m => m.IssueNumber.ToLower().Contains(Model.SearchText.ToLower()));
var list = exact.Union(contains);
This approach seems like it'd be a really bad way to do this. In fact, the Union portion seems to effectively crash my application. Is there an opposite to Intersection which would give me the remaining results outside the Exact matches that I could then append to a final list so that the order would be Exact Matches followed by StartsWith matches followed finally by Contains matches in that descending order?
To answer your original question, you can use a temporary expression to classify the match types, then order by the match type and other criteria, and it will translate to SQL as well:
var st = Model.SearchText.ToLower();
var list = Model.SearchResults.Where(m => m.EffectiveDateTime != null)
.Select(m => new {
m,
im = m.IssueNumber.ToLower()
})
.Select(mim => new {
mim.m,
Rank = mim.im == st ? 1 : mim.im.StartsWith(st) ? 2 : mim.im.Contains(st) ? 3 : 4
})
.Where(mr => mr.Rank < 4)
.OrderBy(mr => mr.Rank)
.ThenBy(mr => mr.m.EffectiveDateTime)
.Select(mr => mr.m)
.ToList();
I did the double Select to emulate let from fluent syntax, which I think is a bit clearer than lambda syntax in this case:
var lisx = (from m in Model.SearchResults
where m.EffectiveDateTime != null
let im = m.IssueNumber.ToLower()
let Rank = im == st ? 1 : im.StartsWith(st) ? 2 : im.Contains(st) ? 3 : 4
where Rank < 4
orderby Rank, m.EffectiveDateTime
select m)
.ToList();
Also, if you do the whole query in the database, the ToLower is likely unnecessary, as the default for SQL is probably to be case-insensitive anyway.
Actually, I went back to the drawing board and figured it out. This is a little bit better for me and returns the results I needed.
var list = Model.SearchResults
.Where(e => e.A.ToLower().Contains(Model.SearchText.ToLower()))
.GroupBy(d => new { d.A, d.B, d.C})
.OrderBy(x => x.Key.A)
.ThenBy(x => x.Key.B)
.ThenBy(x => x.Key.C)
.Select(x => new
{
A= x.Key.A,
B= x.Key.B,
C= x.Key.C
})
.ToList();

how to set value in List with out using foreach loop

I am having two lists and filter based on a group of values.
var UserIdList = response.Users.Select(p => p.Id).ToList();
var filteredRecords =
(from o in om.Organizations join u in om.Users on o.Id equals u.OrganizationId where UserIdList.Contains(u.Id)
select new { Enabled = o.Enabled, Id = u.Id }).ToList();
Now i want to set 'Exists' property in 'response.Users' to true if 'Id' exists in filteredRecords.
Please let me know how can I set value with out using foreach loop.
I have tried with
response.Users.Where(x => x.Exists = (filteredRecords .Any(z => z.Id == x.Id))).ToList();
but could not succeed as it is giving only filter results.
I want full records which are matched and which are not
Linq doesn't really support update scenarios as it's for querying data.
For lists however there's a ForEach extension method:
UserList
.ToList()
.ForEach(item=> item.Exists = filteredRecords.Any(f=> f.Id == item.Id));
Is this clearer to read and easier to understand than a foreach loop...
Edit (after question updates)
// You can't use this... it won't even compile,
// x.Exists = ... is an assignment not a condition
response.Users.Where(x => x.Exists = (filteredRecords .Any(z => z.Id == x.Id))).ToList();
What you want to do is to work with a subset of users and update the values.
response
.Users
.Where(x=> filteredRecords.Any(z => z.Id == x.Id))
.ToList() // This materialises your IEnumerable/IQueryable to allow the ForEach extension method
.ForEach(x => x.Exists = true);
you could of course use:
var usersToEnable = response.Users.Where(x=> filteredRecords.Any(z => z.Id == x.Id);
foreach(var user in usersToEnable)
user.Enabled = true;
As a direct response to:
Please let me know how can I set value with out using foreach loop. I have tried with
response.Users.Where(x => x.Exists = (filteredRecords .Any(z => z.Id == x.Id))).ToList(); but could not succeed as it is giving only
filter results. I want full records which are matched and which are
not
What you've done there is selected a collection of response user items you wish to have Exists set to true.
Now you need to set that bool in this filtered collection, and then return the full response collection afterwards, instead of returning the filtered collection.
I think this is where you're getting confused.

Linq All() / Any() but not empty

I have a Linq expression that is used in a few places. I went down the expression route as there wasn't a logical way to accomplish some searching logic without enumerating a very large table otherwise.
private Expression<Func<Property, bool>> PropertyIsCompliant()
{
return (p) => p.CalculationSets.OfType<SingleDocumentCalculationSet>()
.GroupBy(cs => cs.SourceDocument)
.Select(g => g.OrderByDescending(d => d.DateTime).FirstOrDefault().CalculationResults)
.SelectMany(cr => cr)
.All(cr => cr.Outcome == CalculationOutcome.Success);
}
My models are as such:
A Property has many CalculationSets
Each CalculationSet is also assigned to a Document
Each CalculationSet has a number of CalculationResults
Each CalculationResult has an Outcome
I'm trying to create an expression that will tell me if all the outcomes from the most recent calculationsets grouped by document ordered by most recent (ie the most recent distinct results) are Successful.
I can the SelectMany clause returns all the CalculationResults from the correct CalculationSets.
I just cant figure out how to return true ONLY if the collection isn't empty AND they are all Outcome.Success.
I understand the All operator automatically returns true on an empty collection. I just can't think of a way around it!
So your real condition is that there are not any unsuccessful outcomes. In that case use Any and reverse the condition:
//V-- notice the ! inverse operator here
return (p) => !(p.CalculationSets.OfType<SingleDocumentCalculationSet>()
.GroupBy(cs => cs.SourceDocument)
.Select(g => g.OrderByDescending(d => d.DateTime).FirstOrDefault().CalculationResults)
.SelectMany(cr => cr)
.Any(cr => cr.Outcome != CalculationOutcome.Success));
var countsBySuccess =
...
.GroupBy(cr => cr.Outcome == CalculationOutcome.Success) //group on success
.Select(g => new { IsSuccessful = g.Key, Count = g.Count() });
You can now examine the two result rows to make sure that the unsuccessful count is zero and the successful count is non-zero.
Regarding performance, this will need to materialize the entire result set server-side and aggregate it. But it does so only once.
If you must use the calculation result as part of a bigger query, you must use another trick:
!countsBySuccess.Any(g =>
g.IsSuccessful && Count == 0 ||
!g.IsSuccessful && Count != 0)
This boolean expression determines whether the condition you are looking for holds with one scan of the data.
It is important to only scan the data once. Do not simply write:
myItems.All(cr => cr.Outcome == CalculationOutcome.Success) && myItems.Any()
Because that does two scans. SQL Server does not optimize this out.
I think you're answering your question - if you know that All returns TRUE for empty then you have two checks to make. Excuse my C# (I'm not sure on the var query assignment, hopefully you get the idea) but you could do something like this:
private Expression<Func<Property, bool>> PropertyIsCompliant()
{
var query = (p) => p.CalculationSets.OfType<SingleDocumentCalculationSet>()
.GroupBy(cs => cs.SourceDocument)
.Select(g => g.OrderByDescending(d => d.DateTime).FirstOrDefault().CalculationResults)
.SelectMany(cr => cr);
return (query.Count > 0) & query.All(cr => cr.Outcome == CalculationOutcome.Success);
}
I didn't realise it was possible to use "&&" in expressions. So I've managed to combine 2 separate expressions that give the answer I need. The "&&" only returns true when both expressions evaluate "true"
return (p) =>
p.CalculationSets.OfType<SingleDocumentCalculationSet>()
.GroupBy(cs => cs.SourceDocument)
.Select(g => g.OrderByDescending(d => d.DateTime).FirstOrDefault().CalculationResults)
.SelectMany(cr => cr).Any()
&&
p.CalculationSets.OfType<SingleDocumentCalculationSet>()
.GroupBy(cs => cs.SourceDocument)
.Select(g => g.OrderByDescending(d => d.DateTime).FirstOrDefault().CalculationResults)
.SelectMany(cr => cr)
.All(cr => cr.Outcome == CalculationOutcome.Success);

LINQ read Select values

I have a LINQ query where I want to select and read the p.Api value.
var api = DataAccessNew.Instance.dcServers.Where(p => p.Ip == IpAddress).Select(p => p.Api);
How do I read the p.Api value?
I have tried api.ToString() but I get SQL instead of actual column value.
You are getting an IEnumerable<> back (and your ToString call is showing you the value of that expression).
If you are expecting a single value, do this:
var api = DataAccessNew.Instance.dcServers
.Where(p => p.Ip == IpAddress)
.Select(p => p.Api)
.Single();
You might be interested to read about the other methods like Single(): SingleOrDefault, First, FirstOrDefault. Which one you used depends on whether you are expecting a single or multiple values returned (Single vs. First) and what you want to happen if there are no values (the *Default methods will return the type default instead of throwing an exception).
Or if you want to look at all the returned values:
var api = DataAccessNew.Instance.dcServers
.Where(p => p.Ip == IpAddress)
.Select(p => p.Api);
foreach (var apiValue in api)
{
// apiValue will have the value you're looking for.
}
Try this snippet of code:
string apiValue = api.FirstOrDefault().ToString();
your syntex seems ok..
By the way try this
string api =DataAccessNew.Instance.dcServers.Where(p => p.Ip == IpAddress).Select(p => p.Api).FirstOrDefault();
if p.Ip is a unique key in your table you could try to add .FirstOrDefault() after your Linq query.
public string getselectedvalue(ListBox l)
{
string vtext="",vval="";
var selectedQueryText = l.Items.Cast<ListItem>().Where(item => item.Selected);
var selectedQueryVal = l.Items.Cast<ListItem>().Where(item => item.Selected).Select(item => item.Value);
vtext= String.Join("','", selectedQueryText ).TrimEnd();
vval= String.Join("','", selectedQueryVal ).TrimEnd();
return v;
}

Pivot in LINQ using lambda expression

I am writing a lambda in linq for getting the pivoted data from the resulting list.For getting the pivoting columns am setting a where condion to get the value.the problem here is am getting default value if the where condtion fails.I dont want the column if the where condition fails.Kindly help me out.
var query = dataList
.GroupBy(c => c.IpAddress)
.Select(g => new
{
IPAddress = g.Key,
AssetType = g.ElementAtOrDefault(0).AssetTypeName,
AssetName = g.ElementAtOrDefault(0).AssetName,
//if where condition fails i dont need this column.
//also am giving c.DsName == "Active Calls" ,how to achieve tis dynamically
**ActiveCalls = g.Where(c => c.DsName == "Active Calls").Sum(c => c.CurrentValue),**
**HoldCalls = g.Where(c => c.DsName == "Hold Calls").Sum(c => c.CurrentValue),**
**CPU = g.Where(c => c.DsName == "CPU").Sum(c => c.CurrentValue),**
});
Why not just create a value to hold the sum and then specify its a type in another column. That way you don't have to deal with null columns. (The assumption is that only one type is valid at a time).
Value = g.Sum(c => c.CurrentValue), // Value as specified by the the DsName property.
DsName = c.DsName

Resources