Can i retrieve DISTINCT value of an array field using LINQ? - c#

I have class object (for simplicity sake, let say, class name is Person with fields as FirstName, Lastname, YearBorn)
I get my data from DB but now want to have distinct year(s) in an array, can this be done using LINQ?
I am passing List<person> Data as a parameter to a method and want to return an array of Distinct year from data.
Any help will be appreciated.

Assuming YearBorn is a Datetime (?)
var years = data
.Select(m => m.YearBorn.Year).Distinct();
if it's an int
var years = data.Select(m => m.YearBorn).Distinct();
if it's a nullable DateTime
var years = data
.Where(m => m.YearBorn.HasValue)
.Select(m => m.YearBorn.Value.Year).Distinct();

You need to use Enumerable.Distinct Method (IEnumerable, IEqualityComparer) where like a second parameter you specify equality rule, so Distict will know which entities are considered equal so will skip them in case of more then one.

Use the Distinct standard query operator:
var years = data.Select(p => p.Year).Distinct().ToArray();
If you wish to get all of the people elements that have distinct years, then you can use DistinctBy from the morelinq library.

Bit late but :(
List<Person> persons = new List<Person>();
persons.Add(new Person() { Forename = "Onam", Surname = "Chilwan", DOB = new DateTime(1984, 8, 23) });
persons.Add(new Person() { Forename = "Onam", Surname = "Chilwan", DOB = new DateTime(1972, 8, 23) });
persons.Add(new Person() { Forename = "Onam", Surname = "Chilwan", DOB = new DateTime(1988, 8, 23) });
persons.Add(new Person() { Forename = "Onam", Surname = "Chilwan", DOB = new DateTime(1984, 8, 23) });
int[] years = persons.Select(x => x.DOB.Year).Distinct().ToArray();
public class Person
{
public string Forename;
public string Surname;
public DateTime DOB;
}

Related

How can I get only records with a unique property using entity framework and linq?

public class Person
{
public string firstName;
public string lastName;
}
I want a list of all Persons with a unique first name.
Persons table
Tom Haverford
Tom Baker
Amy Pond
Amy Santiago
Trish Walker
Chidi Anagonye
The query should return
Trish, Chidi
I've tried using Distinct and a combination of GroupBy and Select, but those return Trish, Chidi, Tom, Amy.
Demo on dotnet fiddle
You can Group by then count number of duplicated items. After that, you can get the item with count value equals to 1 like below.
var arr = new []
{
new Person { firstName = "Tom", lastName = "Haverford" },
new Person { firstName = "Tom", lastName = "Baker"},
new Person { firstName = "Amy", lastName = "Pond" },
new Person { firstName = "Amy", lastName = "Santiago"},
new Person { firstName = "Trish", lastName = "Walker"},
new Person { firstName = "Chidi", lastName ="Anagonye" }
};
var result = arr.GroupBy(p => p.firstName).Select(g => new { Name = g.Key, Count = g.Count()});
foreach(var item in result.Where(p => p.Count == 1))
Console.WriteLine(item.Name);
Output
Trish
Chidi
You can use group by and count functionality together for this :
1. Get a list of all persons from DB :
var personList = (from p in db.Person select p).ToList(); // I assumed here that your db obj name is 'db' and table name is 'Person'
2. Now apply group by query to get the count of first names :
var q = from x in personList
group x by x.firstName into g
let count = g.Count()
select new {Count = count, Name = g.First().firstName };
3. Now you can get your final result like this :
var finalResult = (from p in q where p.Count == 1 select p).ToList();
Happy Coding...

Filter List entries

I have a list with 3 entries -
public class entry
{
string name;
string age;
string likes;
}
List<entry> groupEntry = new List<entry>();
Where -
groupEntry[0].name = "john";
groupEntry[0].age= "26";
groupEntry[0].likes= "cats";
groupEntry[1].name = "john";
groupEntry[1].age= "26";
groupEntry[1].likes= "dogs";
groupEntry[2].name = "matt";
groupEntry[2].age= "32";
groupEntry[2].likes= "frogs";
What i am trying to do is make a new list, whereby name is a unique identifier, and just create one entry per name however add the likes together into on string array with the resulting list looking like -
public class filteredEntry
{
string name;
string age;
List<string> likes;
}
List<filteredEntry> filteredGroupEntry = new List<filteredEntry>();
filteredGroupEntry [0].name = "john";
filteredGroupEntry [0].age= "26";
filteredGroupEntry [0].likes= ["cats", "dogs"];
filteredGroupEntry [1].name = "matt";
filteredGroupEntry [1].age= "32";
filteredGroupEntry [1].likes= "frogs";
My thoughts were to do a foreach with groupEntry and each time entry[i].name changes make a new record in filteredGroupEntry however could not get this to work with a new array for likes. How can I achieve this?
First of all, you need to make the fields in your entry class public. Until now your code won't compile.
Second is that likes is already a string so you cannot transform it into an array, but what you could do is just to concatenate all the stuff that a person likes and separate them with a ,. So you would group by name and take all likes values in one string. This code takes only the first age value (assuming that it is always the same John with the same age May be not the smartest solution.
List<entry> filteredList = groupEntry.GroupBy(x => x.name)
.Select(x => new entry
{
name = x.First().name,
age = x.First().age,
likes = String.Join(", ", x.Select(l=>l.likes))
}).ToList();
Later on when you want to separate the like thingies again you could split by , like this:
string [] allOneLikes = filteredList[0].likes.Split(',');
EDIT:
I just saw that you edited your post and added the filteredEntry class.
This changes the situation of course. So you could then use this class in the Select statement:
List<filteredEntry> filteredList = groupEntry.GroupBy(x => x.name)
.Select(x => new filteredEntry
{
name = x.First().name,
age = x.First().age,
likes = x.Select(l=>l.likes).ToList()
}).ToList();
I suggest grouping by via Linq:
List<filteredEntry> filteredGroupEntry = groupEntry
.GroupBy(entry => new { // grouping by name + age
name = entry.name,
age = entry.age})
.Select(chunk => new filteredEntry() {
name = chunk.Key.name,
age = chunk.Key.age,
// collapse all likes into list
likes = chunk.Select(entry => entry.likes).ToList()})
.ToList();

Filter Criteria Search using LINQ

I am 'newer' to LINQ queries have another one of those questions where I have something going but not sure if this is the most effective way to go about it. In my project, I am working in a real DB, but for a sake of simplicity, here I will condense it down to a simple list of employees:
var employees = new List<Employee>
{
new Employee { Id = 0, firstName = "James", LastName = "Bond", Manager = "M", StartDate = DateTime.Now },
new Employee { Id = 1, firstName = "Eric", LastName = "Bond", Manager = "M", StartDate = DateTime.Now },
new Employee { Id = 2, firstName = "Sue", LastName = "Milton", Manager = "Q", StartDate = DateTime.Now },
new Employee { Id = 3, firstName = "Olivia", LastName = "Milton", Manager = "M", StartDate = DateTime.Now },
new Employee { Id = 4, firstName = "Alice", LastName = "Raymond", Manager = "M", StartDate = DateTime.Now },
new Employee { Id = 5, firstName = "James", LastName = "Skywalker", Manager = "M", StartDate = DateTime.Now },
new Employee { Id = 6, firstName = "Luke", LastName = "Skywalker", Manager = "M", StartDate = DateTime.Now },
};
I have to search in this list based on given criteria.. where criteria is combination of various fields with OR and AND operations with in the fields for example get me all employees where:
firstName = "James" OR "eric" AND manager = "Q"
lastname = "bond" OR "Martha"
firstName = "James" AND Lastname = "Bond"
and so on...
This is going to be a web API call and I have to do this in one method. The other challenge is that each search parameter is 'optional" i.e , they can pass me a list of firstnames and a manager name and ignore the last names parameters etc. So here is what I started coded:
public IList<Employee> GetFilteredEmployees(IList<String> firstnames = null,
IList<String> lastnames = null,
IList<String> managers = null)
{
if (firstnames != null && firstnames.Any())
{
foreach (var fn in firstnames)
{
employeeByFn = employees.Where(emp => emp.firstName == fn).ToList<Employee>();
}
}
if (lastnames != null && lastnames.Any())
{
foreach (var ln in lastnames)
{
employeeByLn = employees.Where(emp => emp.LastName == ln).ToList<Employee>();
}
}
..... // code ellided
}
As you can see, this is getting ugly even with a few search criteria parameters. In my real project, I have up to 16 of those. Also at the end of all these sub-queries, I have to merge my results into one employee list and return that keeping in mind that any of the sub-query result may be null.
I am sure this is not a unique problem and I see similar questions asked before but not exactly the same problem. What would be elegant way of doing this that is also easy to maintain .i.e if they decide to add more search criteria later (say by start Date), I want to be able to easily modify my method to handle that.
Thanks a bunch for looking.
You can keep on adding Where() conditions on the same result instead of creating many partial results.
public IList<Employee> GetFilteredEmployees(IList<String> firstnames = null,
IList<String> lastnames = null,
IList<String> managers = null)
{
IQueryable<Employee> result = employees;
if (firstnames != null)
result = result.Where(emp => firstnames.Contains(emp.firstName));
if (lastnames != null)
result = result.Where(emp => lastnames.Contains(emp.LastName));
if (managers != null)
result = result.Where(emp => managers.Contains(emp.Manager));
... // code ellided
return result.ToList();
}

Combining two Lists of an Object into one

I am currently developing an application that requires this senario.
Assuming I have this object
public class ObjectItem
{
public int Id {get;set;}
public int Name {get;set;}
public int Sex {get;set;}
public int Age {get;set;}
public string Complexion {get;set;}
}
If we now have two lists of this object
var studentWithAge = new List<ObjectItem>
{
new ObjectItem {Id = 1, Name = "John", Age = 2},
new ObjectItem {Id = 2, Name = "Smith", Age = 5},
new ObjectItem {Id = 3, Name = "Juliet", Age = 7},
};
var studentWithSexAndComplexion = new List<ObjectItem>
{
new ObjectItem {Id = 1, Name = "John", Sex = "Male", Complexion = "fair"},
new ObjectItem {Id = 2, Name = "Smith", Sex = "Male", Complexion = " "},
new ObjectItem {Id = 3, Name = "Juliet", Sex = "Female", Complexion = "Blonde"},
new ObjectItem {Id = 4, Name = "Shittu", Sex = "Male", Complexion = "fair"},
};
I want to merge these two lists into just one. The end result should look like this.
var CompleteStudentData=new List<ObjectItem>
{
new ObjectItem{Id=1,Name="John",Sex="Male", Complexion="fair",Age=2},
new ObjectItem{Id=2,Name="Smith",Sex="Male", Complexion=" ", Age=5},
new ObjectItem{Id=3,Name="Juliet",Sex="Female", Complexion="Blonde", Age=7},
new ObjectItem{Id=4,Name="Shittu",Sex="Male", Complexion="fair", Age=0},
}
How do i achieve this? Using Union to merge the two list does not produce the desired result. I would appreciate your help.
var result = StudentWithAge.Join(StudentWithSexAndComplexion,
sa => sa.Id,
ssc => ssc.Id,
(sa, ssc) => new ObjectItem
{
Id = sa.Id,
Name = sa.Name,
Age = sa.Age,
Sex = ssc.Sex,
Complexion = ssc.Complexion
}).ToList();
Or, avoiding creation of new objects:
var result = StudentWithAge.Join(StudentWithSexAndComplexion,
sa => sa.Id,
ssc => ssc.Id,
(sa, ssc) =>
{
sa.Sex = ssc.Sex;
sa.Complexion = ssc.Complexion;
return sa;
}).ToList();
And if you want to add students presented only in the second list, than also:
result.AddRange(StudentWithSexAndComplexion.Where(ssc => !StudentWithAge.Any(sa => sa.Id == ssc.Id)));
Since it's possible that your collections will not have a 1-to-1 correspondence, you would have to do a full outer join. See here for how you can compose it that way.
Here's one way you can get similar results.
Collect all the keys (the ids) from both collections, then perform a left join with each of the collections, then combine the results.
var ids = studentWithAge.Select(s => s.Id)
.Union(studentWithSexAndComplexion.Select(s => s.Id));
var query =
from id in ids
from sa in studentWithAge
.Where(sa => sa.Id == id)
.DefaultIfEmpty(new ObjectItem { Id = id })
from ssc in studentWithSexAndComplexion
.Where(ssc => ssc.Id == id)
.DefaultIfEmpty(new ObjectItem { Id = id })
select new ObjectItem
{
Id = id,
Name = sa.Name ?? ssc.Name,
Sex = ssc.Sex,
Age = sa.Age,
Complexion = ssc.Complexion,
};
.Net has a function which is concatenating collections:
var concatenatedCollection = StudentWithAge.Concat(StudentWithSexAndComplexion).ToList();
var StudentWithAge = new List<ObjectItem>()
{
new ObjectItem{Id=1,Name="John",Age=2},
new ObjectItem{Id=2,Name="Smith",Age=5},
new ObjectItem{Id=3,Name="Juliet",Age=7},
};
var StudentWithSexAndComplexion = new List<ObjectItem>()
{
new ObjectItem{Id=1,Name="John",Sex="Male", Complexion="fair"},
new ObjectItem{Id=2,Name="Smith",Sex="Male", Complexion=" "},
new ObjectItem{Id=3,Name="Juliet",Sex="Female", Complexion="Blonde"},
new ObjectItem{Id=4,Name="Shittu",Sex="Male", Complexion="fair"},
};
var concatenatedCollection = StudentWithAge.Concat(StudentWithSexAndComplexion).ToList();

Concat Two IQueryables with Anonymous Types?

I've been wrestling with this a little while and it's starting to look like it may not be possible.
I want to Concat() two IQueryables and then execute the result as a single query. I tried something like this:
var query = from x in ...
select new
{
A = ...
B = ...
C = ...
};
var query2 = from y in ...
select new
{
A = ...
B = ...
C = ...
};
var query3 = query.Concat(query2);
However, the last line gives me the following error:
'System.Linq.IQueryable' does not contain a definition for 'Concat' and the best extension method overload 'System.Linq.ParallelEnumerable.Concat(System.Linq.ParallelQuery, System.Collections.Generic.IEnumerable)' has some invalid arguments
It appears it's expecting an IEnumerable for the argument. Is there any way around this?
It looks like I could resolve both queries to IEnumerables and then Concat() them. But it would be more efficient to create a single query, and it seems like that should be possible.
As you said previously in the comments, it seems that the two queries return different objects:
Query 1 (as per comment):
f__AnonymousTypee<Leo.Domain.FileItem,Leo.Domain.Employ‌​ee,int,string,string>
Query2 is
f__AnonymousTypee<Leo.Domain.FileItem,L‌​eo.Domain.Employee,int?,string,string>
This is why Concat is giving you an error message complaining about invalid arguments.
Anonymous objects will be equivalent types to other anonymous objects with the same property names and types declared in exactly the same order.
Assuming both query and query2 from from the same contexts, you should be able to combine the two, provided they are queries of equivalent types.
Your comment indicates that neither are of the same type.
query returns objects of type Anon<FileItem, Employee, int, string, string>
query2 returns objects of type Anon<FileItem, Employee, int?, string, string>.
You cannot combine the two because they are of different types. You'll need to make sure that both queries return objects of the same type.
var query = from x in ...
select new
{
A = (FileItem)...
B = (Employee)...
C = (int)...
...
};
var query2 = from y in ...
select new
{
A = (FileItem)...
B = (Employee)...
C = (int)...
...
};
The IDE determined query and query2 are of different types, while the IEnumerable<TSource> Concat<TSource>() extension method expects two same types (IEnumerable<TSource>). The three TSource's must be the same.
string[] strA = {"123", "234", "345"};
int[] intA = { 1, 2, 3 };
var query = from s in strA
select s;
var query2 = from i in strA // intA
select i;
var query3 = query.Concat(query2);
Uncomment "// intA" in VS and you'll see the difference.
Are you missing any namespace? Normally I mark my .NET Project Properties to target .net 4.0 for vs 2010. I do not use .net 4.0 Client Profile.
Please make sure that the types of A, B and C is matches in both the query anonymous types. Also the order of A, B and C should also match in both queries.
The following example works like a charm.
namespace Test
{
using System;
using System.Collections.Generic;
using System.Linq;
internal class Employee
{
public string Name { get; set; }
public int Age { get; set; }
public double Salary { get; set; }
public string Address { get; set; }
}
internal class Program
{
private static List<Employee> employees = new List<Employee>();
private static void BuildList()
{
employees.AddRange(
new Employee[]
{
new Employee() {Name = "Tom", Age = 22, Address = "sample1", Salary = 10000},
new Employee() {Name = "Mithun", Age = 27, Address = "sample1", Salary = 20000},
new Employee() {Name = "Jasubhai", Age = 24, Address = "sample1", Salary = 12000},
new Employee() {Name = "Vinod", Age = 34, Address = "sample1", Salary = 30000},
new Employee() {Name = "Iqbal", Age = 52, Address = "sample1", Salary = 50000},
new Employee() {Name = "Gurpreet", Age = 22, Address = "sample1", Salary = 10000},
}
);
}
private static void Main(string[] args)
{
BuildList();
var query = from employee in employees
where employee.Age < 27
select new
{
A = employee.Name,
B = employee.Age,
C = employee.Salary
};
var query2 = from employee in employees
where employee.Age > 27
select new
{
A = employee.Name,
B = employee.Age,
C = employee.Salary
};
var result = query.Concat(query2);
foreach (dynamic item in result.ToArray())
{
Console.WriteLine("Name = {0}, Age = {1}, Salary = {2}", item.A, item.B, item.C);
}
}
}
}

Categories

Resources