How to Group the elements within group in Linq - c#

I have an Employee Collection and i want to filter in such a way that first 2 columns only should be filtered and the third column values should be appended and the final result should be in a single row
The below is my code
List<Employe> Employeecollection = new List<Employe>();
Employeecollection.Add(new Employe("Employee1", "Dept1","Language1"));
Employeecollection.Add(new Employe("Employee2", "Dept2", "Language2"));
Employeecollection.Add(new Employe("Employee3", "Dept3", "Language3"));
Employeecollection.Add(new Employe("Employee3", "Dept3", "Language3"));
Employeecollection.Add(new Employe("Employee1", "Dept1", "Language2"));
foreach (Employe item in Employeecollection.GroupBy(x => new { fName = x.EmpName, lName = x.EmpDept, mName = x.KnownLanguages }).Select(g => g.First()))
{
Console.WriteLine(item.EmpName + " " + item.EmpDept + " " + item.KnownLanguages);
}
but i would like to display the results like below
Employee1 Dept1 Language1,Language2
Employee2 Dept2 Language2
Employee3 Dept3 Language3

Group employees by name and department, then select joined string of known languages for each employee:
from e in Employeecollection
group e by new { e.Name, e.EmpDept } into g
select new {
g.Key.Name,
g.Key.EmpDept,
Languages = String.Join(",", g.Select(x => x.KnownLanguages))
}
If you want results as single row, then do following projection instead:
select String.Format("{0} {1} {2}",
g.Key.Name, g.Key.EmpDept, String.Join(",", g.Select(x => x.KnownLanguages)))
BTW I think its a weird property name KnownLanguages for property which holds single language

You don't want to group on KnownLanguages. It shouldn't be included in your group selector. The group selector should select all of thing that you want to be the same for all items in a group.
You also need to change how you print your results. Get the common values for each of the items in a group through the Key, and the other values through iterating the group itself.
var query = Employeecollection.GroupBy(x => new
{
x.EmpName,
x.EmpDept
};
foreach (var group in query)
{
string languages = string.Join(", ",
group.Select(employee => employee.KnownLanguages)
.Distinct());
Console.WriteLine(group.Key.EmpName + " " + group.Key.EmpDept + " "
+ languages;
}

Related

How to use linq result input sql where in

I have a linq script
var ID = (from item in ConflictDatas.AsEnumerable()
group item by new
{
ID = item.Field<string>("ID"),
DesignArticle = item.Field<string>("DesignArticle"),
DesignNo = item.Field<string>("DesignNo"),
PatternCode = item.Field<string>("PatternCode")
} into g
where g.Count() >= 2
select new
{
g.Key.ID
}).ToList();
I want to put this result into a sql commnad.
I try:
string sqlwhere;
sqlwhere = string.Join(",", ID);
tsql = #"
Insert ConflictDesignArticle
Select * from ReadyworkData where ID in (" + sqlwhere + #") ";
After compile:
Insert ConflictDesignArticle
Select * from ReadyworkData where ID in ({ ID = SPSOS17040113 },{ ID =
SPSOS17040115 },{ ID = SPSOS17040114 })
How to modify my code. Thanks.
Thank you for Lei Yang help
var ID = (from item in ConflictDatas.AsEnumerable()
group item by new
{
ID = item.Field<string>("ID"),
DesignArticle = item.Field<string>("DesignArticle"),
DesignNo = item.Field<string>("DesignNo"),
PatternCode = item.Field<string>("PatternCode")
} into g
where g.Count() >= 2
select new
{
g.Key.ID
}).Select(x => x.ID).ToList();

Get database row from selected item in Combobox

I have a Combobox which gets its data from my database.
var people = (from x in db.Person select new { Value = x.Id, Names = x.Namn + " " + x.EfterNamn }).ToList();
cbpeople.DataSource = people;
cbpeople.DisplayMember = "Names";
cbpeople.ValueMember = "Value";
cbpeople.SelectedIndex = -1;
And I have the SelectedIndex function
int id = cbpeople.SelectedIndex + 1;
string namn = (from x in db.Person where x.Id == id select x.Namn).ToString();
lblNamn.Text = namn;
So as you can see, I'm trying to have it select the information from the same row in the database and put them in labels. (The "cbpeople.SelectedIndex + 1;" is because I had no other way to get the ID from the SelectedValue).
But all it prints out is this long thing instead of the Name (on the label)
"SELECT \r\n [Extent1].[Namn] AS [Namn]\r\n FROM [dbo].[Person] AS [Extent1]\r\n WHERE [Extent1].[Id] = #p__linq__0"
What am I doing wrong?
You calling ToString() over IQueryable object. Of course, it will return it's SQL representation. To execute query you can do this:
string namn = (from x in db.Person where x.Id == id select x.Namn).Single();

LINQ - GROUP BY IEnumerable

Using LINQ, how can I group this list results by firstThree? My group method is not working.
var findOtherZipCodes = (from z in zipCodes
from results in zipFirstThree
where (results.region.ToUpper() == z.City
&& results.state == z.State)
select new ZipCodeFirstThree
{
region = z.City,
state = z.State,
firstThree = z.ZipCode1.ToString().Substring(0, 3),
zipCode = z.ZipCode1.ToString()
}).ToList();
findOtherZipCodes.GroupBy(x => x.firstThree).ToList();
foreach (var item in findOtherZipCodes) {
Console.WriteLine(item.region + " " + item.firstThree + " " + item.zipCode);
Thread.Sleep(500);
}
The reason your GroupBy is not working is that you are ignoring its results. It should be like this:
var groups = findOtherZipCodes.GroupBy(x => x.firstThree).ToList();
Change the foreach loop like this to complete the fix:
foreach (var group in groups) {
var firstInGroup = group.First();
Console.WriteLine(firstInGroup.region + " " + group.Key + " " + firstInGroup.zipCode);
Thread.Sleep(500);
}

Optimizing a value based search algorithm with LINQ

I want to build a value based search algorithm. What this means is that once I'm given a list of words I would like to search for entries on the database using those words. However depending on what column/property those words match, I want to alter the value of results returned.
Here is a lazy algorithm that achieves that but is very slow.
//search only active entries
var query = (from a in db.Jobs where a.StatusId == 7 select a);
List<SearchResult> baseResult = new List<SearchResult>();
foreach (var item in search)
{
//if the company title is matched, results are worth 5 points
var companyMatches = (from a in query where a.Company.Name.ToLower().Contains(item.ToLower()) select new SearchResult() { ID = a.ID, Value = 5 });
//if the title is matched results are worth 3 points
var titleMatches = (from a in query where a.Title.ToLower().Contains(item.ToLower()) select new SearchResult() { ID = a.ID, Value = 3 });
//if text within the body is matched results are worth 2 points
var bodyMatches = (from a in query where a.FullDescription.ToLower().Contains(item.ToLower()) select new SearchResult() { ID = a.ID, Value = 2 });
//all results are then added
baseResult = baseResult.Concat(companyMatches.Concat(titleMatches).Concat(bodyMatches)).ToList();
}
// the value gained for each entry is then added and sorted by highest to lowest
List<SearchResult> result = baseResult.GroupBy(x => x.ID).Select(p => new SearchResult() { ID = p.First().ID, Value = p.Sum(i => i.Value) }).OrderByDescending(a => a.Value).ToList<SearchResult>();
//the query for the complete result set is built based on the sorted id value of result
query = (from id in result join jbs in db.Jobs on id.ID equals jbs.ID select jbs).AsQueryable();
I'm looking for ways to optimize this. I am new to LINQ query so I was hoping I could get some help. If there is away I can create the LINQ query that achieves all of this in one go instead of checking for company name and then title and the body text and bringing it all together and creating a sorted list and running it again against the database to get full listing it would be great.
It's best if I study the problem first. My previous answer was optimizing the wrong thing. The primary problem here is going over the results list multiple times. We can change that:
foreach (var a in query)
{
foreach (var item in search)
{
itemLower = item.ToLower();
int val = 0;
if (a.Company.Name.ToLower.Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 5});
if (a.Title.ToLower.Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 3});
if (a.FullDescription.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 2});
}
}
After that, you have your base result and you can continue with your processing.
That reduces it to a single query rather than three queries for each search item.
I wasn't sure if you wanted unique items in your baseResult, or if there was some reason you allowed duplicates and then used the sum of the values to order them. If you want unique items, you could make baseResult a Dictionary, with the ID as the key.
Edit after comment
You could reduce the number of items in the list by doing:
int val = 0;
if (a.Company.Name.ToLower.Contains(itemLower))
val += 5;
if (a.Title.ToLower.Contains(itemLower))
val += 3;
if (a.FullDescription.ToLower().Contains(itemLower))
val += 2;
if (val > 0)
{
baseResult.Add(new SearchResult { ID = a.ID, Value = val });
}
That won't eliminate duplicates altogether, though, because the company name could match one search term, and the title might match another search term. But it would reduce the list somewhat.
Thanks to Jim's answer and some tweeking on my side I managed to reduce the time it takes to complete the search by 80%
Here is the final solution:
//establish initial query
var queryBase = (from a in db.Jobs where a.StatusId == 7 select a);
//instead of running the search against all of the entities, I first take the ones that are possible candidates, this is done through checking if they have any of the search terms under any of their columns. This is the one and only query that will be run against the database
if (search.Count > 0)
{
nquery = nquery.Where(job => search.All(y => (job.Title.ToLower() + " " + job.FullDescription.ToLower() + " " + job.Company.Name.ToLower() + " " + job.NormalLocation.ToLower() + " " + job.MainCategory.Name.ToLower() + " " + job.JobType.Type.ToLower()).Contains(y))); // + " " + job.Location.ToLower() + " " + job.MainCategory.Name.ToLower() + " " + job.JobType.Type.ToLower().Contains(y)));
}
//run the query and grab a list of baseJobs
List<Job> baseJobs = nquery.ToList<Job>();
//A list of SearchResult object (these object act as a container for job ids and their search values
List<SearchResult> baseResult = new List<SearchResult>();
//from here on Jim's algorithm comes to play where it assigns points depending on where the search term is located and added to a list of id/value pair list
foreach (var a in baseJobs)
{
foreach (var item in search)
{
var itemLower = item.ToLower();
if (a.Company.Name.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 5 });
if (a.Title.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 3 });
if (a.FullDescription.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 2 });
}
}
List<SearchResult> result = baseResult.GroupBy(x => x.ID).Select(p => new SearchResult() { ID = p.First().ID, Value = p.Sum(i => i.Value) }).OrderByDescending(a => a.Value).ToList<SearchResult>();
//the data generated through the id/value pair list are then used to reorder the initial jobs.
var NewQuery = (from id in result join jbs in baseJobs on id.ID equals jbs.ID select jbs).AsQueryable();

Complex LINQ query

I know LINQ but my knowledge is pretty much only selects, where, orderby and all of the most common functions. Now I have a need to do something that I think is really difficult and
maybe not even possible to do just with LINQ. What I have is a list of people. That's east
to query but I need to create a text string from that list. The text string has to give a letter followed by the name of each person.
IList<person> Person
I need to be able to have a LINQ statement that checks through the Person list. I need
to be able to look for names that appear more than once. So far I have the following. It works okay but doesn't give everything needed:
Person[0] name="Fred" &
Person[1] name="Pete" &
Person[2] name="Tony" the var abc = "a) Fred. b) Pete. c) Tony
var a = "";
foreach (var person in _persons
.Select((data, value) => new { Data = data, Value = value })
{
a = a + (char)(details.Value + 64) + details.name
}
What I need is the additional functionality so that:
Person[0] name="John" then var abc = "a) John."
Person[1] name="John" &
Person[3] name="John" then var abc = "b) & d) John."
Person[1] name="John" &
Person[2] name="John" &
Person[3] name="John" then var abc = "b),c) & d) John."
In other words, get the names and put a character before them that shows what position the name is in the list. However if the name appears twice then instead of a)name1. b)name1 I need to get a),b) name.
It's something I can't really figure out how to do. I would appreciate any advice or pointers that anyone can give me.
Given:
var persons=new[] {"Fred", "John", "John", "Pete", "John"};
You can write:
char id = 'a';
foreach (var row in persons
.Select(w => new { id = id++, name = w })
.GroupBy(w => w.name)
.Select(w => w
.Select(ww => ww.id + ")")
.Aggregate((c, n) => c + "&" + n)
+ " " + w.Key))
{
Console.WriteLine(row);
}
And that gives you:
a) Fred
b)&c)&e) John
d) Pete
I think I understand it, but I deserve a medal if I do.
foreach (var group in _persons
.Select((data, value) => new { Data = data, Value = value }
.GroupBy (x => x.Data))
{
foreach (var item in group)
a = a + (char)(item.Value + 64) + ") ";
a = a + group.Key;
}
What you need is to first group by the names, and then convert them to strings.
var result = names
.Select((name, index) => new { Name = name, Prefix = (char)(index + 'a') + ")" })
.GroupBy(p => p.Name, p => p.Prefix)
.Select(g => string.Join(" & ", g) + " " + g.Key);
This example doesn't entirely do the formatting of the a), b) & c) thing (instead it gives a) & b) & c), but it should get you started.
You can use Count() to achieve this. It can take a function to determine whether something should be counted.

Categories