How To Write A More Complex LINQ Query - c#

Assuming I have:
public class Cluster
{
List<Host> HostList = new List<Host>();
}
public class Host
{
List<VDisk> VDiskList = new List<VDisk>();
}
public class VDisk
{
public string Name {get; set}
}
I need all the hosts from a Cluster object that have a VDisk of a given name. I can do it with a foreach but would rather have a LINQ query. I tried a SelectMany() but it is returning the VDisk and not the Hosts. Do I need to implement a custom Comparer to do this?
Here's what I Tried:
Cluster CurrentCluster = new Cluster();
// add some hosts here
VDisk vdisk = new VDisk();
vdisk.Name="foo";
so now I want all the hosts that have a Vdisk named "foo"
this returns the vdisk, not the hosts:
CurrentCluster.Hosts.SelectMany(h => h.VDisks.Where(v => v.Name == vdisk.Name));

SelectMany will indeed return the inner collections, flattened into one large collection. You want your predicate to be on Hosts, not on VDisks, since what you're looking for is a list of Hosts.
This might work:
CurrentCluster.Hosts.Where(h => h.VDisks.Any(v => v.Name == vdisk.Name));
It basically says, "Return all hosts Where any of the VDisks match the condition v.Name == vdisk.Name.
I've also seen developers who don't know about Any write something like this:
CurrentCluster.Hosts.Where(h => h.VDisks.Count(v => v.Name == vdisk.Name) > 0);
Sometimes I feel there's a certain readability advantage to the latter, if one thinks that Count is a more intuitive name than Any. Both should do the job, I just prefer the former.

First you need to declare VDiskList in Host as public or internal.
Then you can use this code:
var hostList = new List<Host>();
var givenVDiskName = "sample name";
var selectedHosts = (from h in hostList
where h.VDiskList.Any(vd => vd.Name == givenVDiskName)
select h).ToList();

Related

How do I copy projected results into another variable in c#?

The code below wont run, as it complains that I am trying to add a type Anon into a type of Clients. How can I store certain results in another variable after projecting them originally and having lost the original Type.
(PS. I have made my example simple but am actually dealing with a more complex case. Not projecting is not an option in my actual case. Edited to provide clarification.)
var clients = Clients.Where(c => c.FirstName.StartsWith("Mark"))
.Select(c => new {
LastName = c.LastName.ToUpper(),
c.DateAdded,
c.FirstName,
})
.ToList();
var certainClients = new List<Clients> { };
foreach (var client in clients)
{
if(client.DateAdded.Date < DateTime.Today) {
certainClients.Add(client);
}
}
certainClients.Dump();
There are two options.
First. Instead of using an anon data type, use Clients datatype. As in effect you are creating Clients object -
var clients = Clients.Where(c => c.FirstName.StartsWith("Mark"))
.Select(c => new Clients{
LastName = c.LastName.ToUpper(),
c.DateAdded,
c.FirstName,
})
Second. Create a list of object and assign whatever custom/anon data type to it -
var certainClients = new List<object> { };
The best way is to project to a custom business entity class.
This means that we actually define the class ourselves. For example.
public class ClientEntity
{
public string LastName;
public DateTime DateAdded;
// etc for custom fields or properties you want
}
Then we can simply project to our custom made class
var clients = Clients.Where(c => c.FirstName.StartsWith("Mark"))
.Select(c => new ClientEntity{
LastName = c.LastName.ToUpper(),
DateAdded = c.DateAdded,
etc
})
This way it avoids List <object> which is not type safe and doesn't need to be similar to the original client class for example if we want the length of the original name.

Converting this List of Lists to Dictionary using LINQ

I have a class :
public class Client
{
public Client()
{
TemplateKeys = new List<int>();
}
public List<int> TemplateKeys { get; set; }
}
Then I create say 3 instances :
List<Client> clients = new List<Client>();
Client client = new Client();
client.TemplateKeys.Add(1);
client.TemplateKeys.Add(2);
client.TemplateKeys.Add(3);
clients.Add(client);
//..
Client client1 = new Client();
client1.TemplateKeys.Add(1);
client1.TemplateKeys.Add(3);
clients.Add(client1);
//..
Client client2 = new Client();
client2.TemplateKeys.Add(2);
client2.TemplateKeys.Add(4);
clients.Add(client2);
Then I create a Dictionary:
Dictionary<int, string> templatesInUse = new Dictionary<int, string>();
So what I want to do is take the TemplateKeys used by the users in this clients List, to Distinct() them and them as keys of the templatesInUse Dictionary where the value for now will be string.Empty. The idea is that once I have the keys, I'm gonna query the database for the text associated to each key in the dicitionary. Then i'm gonna replace the string.Empty value witht he result from the database and I'll be able to use the templates for each user without having to query the database for the same template many times.
So what I've done was first to try to extract the distinct values which I managed to do like so:
List<int> res = clients.SelectMany(cl => cl.TemplateKeys)
.Distinct()
.ToList();
Now I want to actually make this LINQ expression return the desired Dictionary<int, string> result. I see the LINQ has built in ToDictionary() extension method but I couldn't find a way to get my result by replacing ToList() with ToDictionary() like so :
templatesInUse = clients.SelectMany(cl => cl.TemplateKeys)
.Distinct()
.ToDictionary(//tried some things here with no success);
So i saw that almost all examples with ToDictionary uses GroupBy() even though I don't need grouping and I would like to see solution that doesn't use it I remade my LINQ like so:
templatesInUse = clients.SelectMany(cl => cl.TemplateKeys)
.Distinct()
.GroupBy(t => t)
.ToDictionary(g => g.Key, g.ToString());
which works to some extenct but instead my desired string.Empty or just "" value I get something strange for value which would work in theory since those values will be replaced but still I would like to get a clean by which I mean that after the execution of the LINQ query I would like to get the TemplateKey as my Dictionary key and empty string as my value. And as I mentioned I really wonder and would like to see a way without using GroupBy() is that a must when using ToDictionary()?
You don't need grouping. Just specify the key as the number and value as string.Empty.
templatesInUse = clients.SelectMany(cl => cl.TemplateKeys).Distinct()
.ToDictionary(x => x, x => string.Empty);

Detecting "near duplicates" using a LINQ/C# query

I'm using the following queries to detect duplicates in a database.
Using a LINQ join doesn't work very well because Company X may also be listed as CompanyX, therefore I'd like to amend this to detect "near duplicates".
var results = result
.GroupBy(c => new {c.CompanyName})
.Select(g => new CompanyGridViewModel
{
LeadId = g.First().LeadId,
Qty = g.Count(),
CompanyName = g.Key.CompanyName,
}).ToList();
Could anybody suggest a way in which I have better control over the comparison? Perhaps via an IEqualityComparer (although I'm not exactly sure how that would work in this situation)
My main goals are:
To list the first record with a subset of all duplicates (or "near duplicates")
To have some flexibility over the fields and text comparisons I use for my duplicates.
For your explicit "ignoring spaces" case, you can simply call
var results = result.GroupBy(c => c.Name.Replace(" ", ""))...
However, in the general case where you want flexibility, I'd build up a library of IEqualityComparer<Company> classes to use in your groupings. For example, this should do the same in your "ignore space" case:
public class CompanyNameIgnoringSpaces : IEqualityComparer<Company>
{
public bool Equals(Company x, Company y)
{
return x.Name.Replace(" ", "") == y.Name.Replace(" ", "");
}
public int GetHashCode(Company obj)
{
return obj.Name.Replace(" ", "").GetHashCode();
}
}
which you could use as
var results = result.GroupBy(c => c, new CompanyNameIgnoringSpaces())...
It's pretty straightforward to do similar things containing multiple fields, or other definitions of similarity, etc.
Just note that your defintion of "similar" must be transitive, e.g. if you're looking at integers you can't define "similar" as "within 5", because then you'd have "0 is similar to 5" and "5 is similar to 10" but not "0 is similar to 10". (It must also be reflexive and symmetric, but that's more straightforward.)
Okay, so since you're looking for different permutations you could do something like this:
Bear in mind this was written in the answer so it may not fully compile, but you get the idea.
var results = result
.Where(g => CompanyNamePermutations(g.Key.CompanyName).Contains(g.Key.CompanyName))
.GroupBy(c => new {c.CompanyName})
.Select(g => new CompanyGridViewModel
{
LeadId = g.First().LeadId,
Qty = g.Count(),
CompanyName = g.Key.CompanyName,
}).ToList();
private static List<string> CompanyNamePermutations(string companyName)
{
// build your permutations here
// so to build the one in your example
return new List<string>
{
companyName,
string.Join("", companyName.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
};
}
In this case you need to define where the work is going to take place i.e. fully on the server, in local memory or a mixture of both.
In local memory:
In this case we have two routes, to pull back all the data and just do the logic in local memory, or to stream the data and apply the logic piecewise. To pull all the data just ToList() or ToArray() the base table. To stream the data would suggest using ToLookup() with custom IEqualityComparer, e.g.
public class CustomEqualityComparer: IEqualityComparer<String>
{
public bool Equals(String str1, String str2)
{
//custom logic
}
public int GetHashCode(String str)
{
// custom logic
}
}
//result
var results = result.ToLookup(r => r.Name,
new CustomEqualityComparer())
.Select(r => ....)
Fully on the server:
Depends on your provider and what it can successfully map. E.g. if we define a near duplicate as one with an alternative delimiter one could do something like this:
private char[] delimiters = new char[]{' ','-','*'}
var results = result.GroupBy(r => delimiters.Aggregate( d => r.Replace(d,'')...
Mixture:
In this case we are splitting the work between the two. Unless you come up with a nice scheme this route is most likely to be inefficient. E.g. if we keep the logic on the local side, build groupings as a mapping from a name into a key and just query the resulting groupings we can do something like this:
var groupings = result.Select(r => r.Name)
//pull into local memory
.ToArray()
//do local grouping logic...
//Query results
var results = result.GroupBy(r => groupings[r]).....
Personally I usually go with the first option, pulling all the data for small data sets and streaming large data sets (empirically I found streaming with logic between each pull takes a lot longer than pulling all the data then doing all the logic)
Notes: Dependent on the provider ToLookup() is usually immediate execution and in construction applies its logic piecewise.

Select from a List<T>

I'm sure there's an wasy way of doing this (I'm guessing one of the extension methods?), but am struggling to find it with Google.
Basically I have a List of custom classes; I want to select some items from this into a new List where one of the properties is equal to any value in another List.
Here's a (simplified) quick example of what I'm trying to do:
public class Job
{
public int Number;
public string ClientCompanyName;
}
List<Job> lstJobs = new List<Job>();
List<Job> lstCompare = new List<Job>();
normally I would do something like:
List<Job> lstFiltered = new List<Job>();
foreach(Job jobThis in lstCompare)
{
foreach(jobComp in lstCompare)
{
if(jobThis.Number = jobComp.Number)
{
lstFiltered.Add(jobThis);
}
}
}
Is there an extension method that neatens this last bit up into (ideally) a single line?
Cheers
You can use Intersect() for this:
http://msdn.microsoft.com/en-us/library/bb460136.aspx
Use Intersect.
For it to work with your custom comparison you either need to implement IEquatable<T> in your class or create a new class the implements IEqualityComparer<T> for your class and pass that to the overload of Intersect.
Jez,
You might be able to use the LINQ intersect function, or try:
var matches = from jobs in lstJobs
join comp in lstCompare on jobs.Number equals comp.Number
select jobs;
or LINQ syntax:
var matches = lstJobs.Join(lstCompare, jobs => jobs.Number,
comp => comp.Number, (jobs, comp) => jobs);
and here was reSharper's version based on your original loop:
List<Job> lstFiltered = (lstJobs.SelectMany(jobThis => lstCompare,
(jobThis, jobComp) => new {jobThis, jobComp})
.Where(#t => #t.jobThis.Number == #t.jobComp.Number)
.Select(#t => #t.jobThis)).ToList();
slightly verbose, but another way to skin the cat.
[edited] as had set to new list, rather than selected elements - doh
var lstFiltered = lstJobs
.Where(job => lstCompare.Any(item => item.Number == job.Number))
.ToList();
The above solution works well if the number of items in the lstCompare is small. For bigger comparison lists you may want to use some hash based collection.
var compareSet = new HashSet<int>(lstCompare.Select(item => item.Number));
var lstFiltered = lstJobs
.Where(job => compareSet.Contains(job.Number))
.ToList();
If the comparison condition is more complex or it is needed in several places, you should create a comparer class that implements IEqualityComparer<T>. Then you could use the Intersect() method as others have already suggested. However, it is not functionally identical with the above solutions. It returns only distinct elements while my solutions return all matching elements. It may be a significant difference in some applications.
My second example can be easily changed to use IEqualityComparer<T> if necessary. The HashSet<T> takes the comparer as second parameter.

Implementing a "like" operator multiple times in one Linq to Entities query

We have a list of strings and we need to filter our results by that list. Example would be find all students who have SSNs that start with 465, 496, or 497 (plus x more)
List<string> list = GetPossibleStartsWithValues();
var qry = from student in entities.Students.WhereStartsWith(x=>x.SSN, list)
select new SearchedStudent
{
Name = student.Name,
SSN = student.SSN,
...
}
The code provided here is close to what we need, but we can't figure out how to impliment the StartsWith that we need using the Expression Class.
Well, you could try this:
public static IQueryable<T> WhereStartsWith<T>(this IQueryable<T> source,
Expression<Func<T, string>> projection,
List<T> list)
{
return source.Where(x => list.Any(y => projection(x).StartsWith(y)));
}
That may well not work, but it would be worth trying before you go into anything more complicated.
EDIT: As you say, the above won't compile - you basically need to build an expression tree representing the bit within the Where clause. Oops. However, before you start doing that, it would be worth seeing whether it'll work in the end. Try this:
List<string> list = GetPossibleStartsWithValues();
var qry = from student in entities.Students
.Where(student => list.Any(y => student.SSN.StartsWith(y)))
select new SearchedStudent
{
Name = student.Name,
SSN = student.SSN,
...
}
If that doesn't work, then making a more general method won't be any use :(
How about using a compound statement such as
var qry = from student in entities.Students.Where(
s => list.Where( x => s.StartsWith(x)).Count() != 0 )

Categories

Resources