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();
}
Related
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...
I have this code:
phraseSources.ToList().ForEach(i => i.JmdictMeaning ?? );
What I need to do, and I'm not it's possible using LINQ, is to remove all occurrences of a string looking like this:
[see=????????]
Note the ??? is meant to indicate there can be any amount of characters, except "]".
That appear inside of JmDictMeaning. Note there might be one or more of these but they will always start with "[see=" and end with "]"
In order to remove all [see=...] patterns you can try Regex.Replace:
using System.Text.RegularExpressions;
...
// Add ", RegexOptions.IgnoreCase" if required (if "See", "SEE" should be matched)
Regex regex = new Regex(#"\[see=[^\]]*\]");
// "abc[see=456]789" -> "abc789"
var result = regex.Replace(source, "");
In your case:
Regex regex = new Regex(#"\[see=[^\]]*\]");
var list = phraseSources.ToList();
list.ForEach(item => item.JmdictMeaning = regex.Replace(item.JmdictMeaning, ""));
Same idea if you want to filter out items with such strings:
var result = phraseSources
.Where(item => !regex.IsMatch(item.JmdictMeaning))
.ToList();
phraseSources.ToList().RemoveAll(i => i == "xyz");
Yours I imagine would probably look something like
phraseSources.ToList().RemoveAll(i => i.StartsWith("[see=") && i.EndsWith("]"));
Here's an example dotnetfiddle showing it in action
You can remove like this:
phraseSources.Select(ph => {ph.JmdictMeaning.Replace("[see=????????]", ""; return ph;})
.ToList();
Let me show an example:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
and query should look like this:
IList<Person> persons = new List<Person>()
{
new Person(){FirstName = "one1[see=????????]", LastName = "LastName1" },
new Person(){FirstName = "two1[see=????????]", LastName = "LastName1" },
new Person(){FirstName = "three1", LastName = "LastName1" },
new Person(){FirstName = "one[see=????????]", LastName = "LastName1" },
new Person(){FirstName = "two", LastName = "LastName1" },
};
persons = persons.Select(p => { p.FirstName = p.FirstName.Replace("[see=????????]", "");
return p; })
.ToList();
I've a table with over 100 column (including blobs) and I want to make a copy of object only with a few filled columns.
right now I'm doing it by selecting needed columns and doing a round-trip serialize and deserialize with Json.NET which is not efficient. what's the best way to handle this scenario?
BL.Case mCase;
BL.Case temp = db.Cases.Select(
xx => new
{
CaseID = xx.CaseID,
FirstName = xx.FirstName,
LastName = xx.LastName
}).FirstOrDefault(u => u.CaseID == CaseID);
mCase = Newtonsoft.Json.JsonConvert.DeserializeObject<BL.Case>(Newtonsoft.Json.JsonConvert.SerializeObject(temp));
Use AutoMapper.
Do something like this:
BL.Case mCase = null;
var temp = db.Cases.Select(
xx => new
{
CaseID = xx.CaseID,
FirstName = xx.FirstName,
LastName = xx.LastName
}).FirstOrDefault(u => u.CaseID == CaseID);
if (temp != null)
{
mCase = Mapper.DynamicMap<BL.Case>(temp);
}
Another solution that requires a bit more code (but might perform better) is to do the following:
In case you need a single item:
BL.Case mCase = null;
var temp = db.Cases.Select(
xx => new
{
CaseID = xx.CaseID,
FirstName = xx.FirstName,
LastName = xx.LastName
}).FirstOrDefault(u => u.CaseID == CaseID);
if (temp != null)
{
mCase = new Case()
{
CaseID = temp.CaseID,
FirstName = temp.FirstName,
LastName = temp.LastName,
};
}
If you need multiple items:
var temp = db.Cases.Select(
xx => new
{
CaseID = xx.CaseID,
FirstName = xx.FirstName,
LastName = xx.LastName
}); //Here you can filter your query if you want using Where
var result = temp
.ToList() //This will actually execute the query on the database
.Select(x => new Case() //Now you can do this since now we are working on in-memory data
{
CaseID = x.CaseID,
FirstName = x.FirstName,
LastName = x.LastName
});
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();
I have a linq query that gets all data from customers and customer contacts.
But sometimes i don't want all contacts so i would like to specify a condition that if this value equals get contacts then run the query. Similar too..
switch (options)
{
case CustomerOptions.DefaultContacts:
break;
}
I currently have this linq query
var customersToReturn = new ContentList<CustomerServiceModel>()
{
Total = customers.Total,
List = customers.List.Select(c => new CustomerServiceModel
{
Id = c.Id,
ContractorId = c.ContractorId,
CompanyName = c.CompanyName,
Active = c.Active,
Address = new Address
{
Address1 = c.Address1,
Address2 = c.Address2,
Address3 = c.Address3,
Address4 = c.Address4,
},
CustomerContacts = c.CustomersContacts.Select(a => new ContactServiceModel
{
Name = a.Name,
Telephone = a.Telephone
}).Where(e => e.IsDefault)
}).ToList()
};
Is there a way I can set a condition or do I need to repeat the process twice one just for customers and one for customers and customer contacts?
If I understand it right, you want some of CustomServiceModel objects to have CustomerContacts, while others to have not? Then I'd do it like that
List = customers.List.Select(c => new CustomerServiceModel
{
Id = c.Id,
ContractorId = c.ContractorId,
CompanyName = c.CompanyName,
Active = c.Active,
Address = new Address
{
Address1 = c.Address1,
Address2 = c.Address2,
Address3 = c.Address3,
Address4 = c.Address4,
},
CustomerContacts = condition ?
c.CustomersContacts.Select(a => new ContactServiceModel
{
Name = a.Name,
Telephone = a.Telephone
}).Where(e => e.IsDefault)
:null
}).ToList()
If you need to use switch, create yourself a method that returns bool and put it instead of condition phrase in above example.