I need help with using linq - c#

I want to filter the objects that I have by their topic.
I have many topics: Arts, Economics, Business, Politics. Each topic is a property within the object that I try to classify from a list of those objects.
Here is part of my objects:
public class AllQuestionsPresented
{
public string Name{ get; set; }
public string ThreadName { get; set; }
public string Topic { get; set; }
public string Subtopic { get; set; }
public int Views { get; set; }
public int Replies { get; set; }
public int PageNumber { get; set; }
public DateTime Time { get; set; }
// snip
I created many of those objects feed their properties with different values and put them into a List:
List<AllQuestionsPresented> forumData;
Now I want to group them all into linq by their topics..
var groupedByPages =
from n in forumData
group n by forumData
select .....
Basically i dont know how to continue cause i am not used to deal with linq.. what i want to get is some dictionary..
Dictionary<string,AllQuestionsPresented> dictionary..
If i dont use linq, and add to a dictionary every topic, it will put several "AllQuestionsPresented" objects with the same topic..which will throw an exception..so i have to use group by..but dont know how to achieve that manipulation

You can use ToLookup, which will give you a key/list of values collection. Your key will be the Topic, and you will get a list of AllQuestionsPresented for each key.
var lookup = forumData.ToLookup(f => f.Topic);
Reference on ToLookup

var groupedByTopics =
from n in forumData
group n by forumData.Topic into g
select new { Topic = forumData.Topic, Questions = g }
You may also want to keep this around for reference :-)
http://msdn.microsoft.com/en-us/vcsharp/aa336746

The grouped results are returned as an IEnumerable<IGrouping<TKey, T>>, which in your case will be IEnumerable<IGrouping<string, AllQuestionsPresented>>.
The code below shows how you can access the data in the grouping.
var groupedByTopic = from question in forumData
group question by question.Topic;
foreach (var group in groupedByTopic)
{
Console.WriteLine(group.Key);
foreach (var question in group)
{
Console.WriteLine("\t" + question.Name);
}
}
To create a dictionary from the above you can do the following
var groupingDictionary = groupedByTopic.ToDictionary(q=>q.Key, q=>q.ToList());
Which will give you a Dictionary<string, List<AllQuestionsPresented>>
If you went the LookUp route, which is nicely demonstrated by #wsanville
, then you can get the dictionary the same way
var lookup = forumData.ToLookup(q => q.Topic);
var groupingDictionary = lookup.ToDictionary(q => q.Key, q => q.ToList());

You can just call ToDictionary. The parameters are a function to select the keys and another to select the values:
var groupedByPages =
from n in forumData
group n by n.Topic;
IDictionary<string, IEnumerable<AllQuestionsPresented>> dictionary =
groupedByPages.ToDictionary(x => x.Key, x => x.AsEnumerable());
But if all you need from the IDictionary interface is the indexing operation, it's easier to just use a ILookup:
ILookup<string, AllQuestionsPresented> groupedByPages = forumData.ToLookup(x => x.Topic);

var groupedByPages =
from n in forumData
group n by forumData.Topic
select n;

Related

Quickest way to search for objects in a very large list by string C#

for example i have a class like below :
public class MasterRecord
{
public int Id { get; set; }
public string UniqueId{ get; set; }
}
public class DetailRecord
{
public int Id { get; set; }
public int MasterRecordId { get; set; }
public string UniqueId{ get; set; }
}
and i also 2 list which are:
MasterList and DetailList
MasterList will have around 300,000 records,
DetailList will have around 7,000,000 records
What i need is loop for every record in the Master List and search the records which has same Name in DetailList.
Here are my code :
foreach (var item in MasterList)
{
var matchPersons = DetailList.Where(q => q.UniqueId == item .UniqueId).ToList();
if (matchPersons != null && matchPersons.Count() > 0)
{
foreach (var foundPerson in matchPersons)
{
//Do something with foundPerson
foundPerson.MasterRecordId = item.Id;
}
}
}
My code running very slow now , each search cost me 500 millisecond to finish , so with 300k records, it will take 2500 minutes :( to finish .
Is there any other way to fast up this function ?
Thanks and forgive for my poor English .
Updated code for make it more clearer of what i want to do.
Using some hash structure would be one of the best options:
var detailLookup = DetailList.ToLookup(q => q.Name);
foreach (var person in MasterList)
{
foreach (var foundPerson in detailLookup[person.Name])
{
//Do something with foundPerson
}
}
Lookup returns empty sequence if the key is not present, so you do not have to test it.
You could use a Join on Name.
var result = masterList.Join(detailedList,m=>m.Name,d=>d.Name,(m,d)=>d);
If you need to handle "MasterRecords with their DetailRecords", don't use a normal join, use a GroupJoin. This will internally create something similar to a LookupTable.
The nice thing is that this will also work with databases, CSV-files, or whatever method that you use to get your records. You don't have to convert them into lists first.
// Your input sequences, if desired: use IQueryable
IEnumerable<MasterRecord> masterRecords = ...
IEnumerable<DetailRecord> detailRecords = ...
// Note: query not executed yet!
// GroupJoin these two sequences
var masterRecordsWithTheirDetailRecords = masterRecord.GroupJoin(detailRecords,
masterRecord => masterRecord.Id, // from masterRecord take the primary key
detailRecord => detailRecord.MasterRecordId // from detailRecord take the foreign key
// ResultSelector: from every MasterRecord with its matching DetailRecords select
(masterRecord, detailRecords) => new
{
// select the properties you plan to use:
Id = masterRecord.Id,
UniqueId = maserRecord.UniqueId,
...
DetailRecords = detailRecords.Select(detailRecord => new
{
// again: select only the properties you plan to use
Id = detailRecord.Id,
...
// not needed, you know the value:
// MasterRecordId = detailRecord.MasterRecordId,
}),
// Note: this is still an IEnumerable!
});
Usage:
foreach(var masterRecord in masterRecordsWithTheirDetailRecords)
{
... // process the master record with its detail records
}
The nice thing is, that is you have only need to process some of the MasterRecords
(for instance, after the 1000th you decide that you found what you searched for),
or if you have some MasterRecords of which you don't need all DetailRecords, no more records are processed than necessary. Linq will take care of that

Sorting by Children in Entity Framework doesn't return sorted list

I know that Entity framework doesn't support sort or filter in the children collections yet. What I thought is that first I get the data then use foreach loop to sort it. The result gives me an unsorted list. My goal is to get the Participants (any order) and CurrentHospitaliztions (order by id descending) which is a child of participants. The models and the query is below. Any help will be appriciated.
public class Participant
{
public int Id { get; set; }
.. other fields
public ICollection<CurrentHospitalization> CurrentHospitalizations { get; set; }
public Participant()
{
CurrentHospitalizations = new Collection<CurrentHospitalization>();
}
}
public class CurrentHospitalization
{
public int Id { get; set; }
.. other fields
public Participant Participant { get; set; }
public int ParticipantId { get; set; }
}
The query that I use:
public async Task<IEnumerable<Participant>> GetList()
{
var participants = await context.Participants
.Include(x => x.CurrentHospitalizations)
.ToListAsync();
foreach (var p in participants )
{
var s = p.CurrentHospitalizations;
foreach (var q in s)
{
s.OrderByDescending(u => u.Id);
}
}
return participants ;
}
You sorted the right piece, in the wrong place, and then didn't really do anything with it. You don't need the nested iteration, you can just do it from the single foreach loop like this:
foreach (var p in participants)
p.CurrentHospitalizations = p.CurrentHospitalizations.OrderByDescending( ch => ch.Id ).ToList();
What you ahve here is a failure to understand the basics of LINQ and not reading the manual.
var s = p.CurrentHospitalizations;
foreach (var q in s)
{
s.OrderByDescending(u => u.Id);
}
does effectively nothing else than waste processor time.
You make a variable s. You assign to it the current hospuzatlizations, not sorted.
Then you call OrderByDescending - generating an expression that you COULD execute, except you never DO execute it. So, you created some object tree and - throw it away.
o.CurrentHospitalizations = s.OrderByDescending(u => u.Id).ToList()
would assign s to be a list and execute it. The ToList() is missing - as well as the assigning it, so the sorted result is not just thrown away.
Those are LINQ basics - orderby etc. are not changing order, they return an ordered result, and you must materialize it.

How to compare a List<long> with a comma-separated ids, using linq to entities

I have Interest Ids column in user profile. & a List<long> interest ids. I want to fetch user profiles matching interest ids in List using Entity Framework.
Ex.
Sam 1,5,9,13,4,8
John 2,7,13,9
Kettie 1,4,8,12,15
List: {4,8}
I want output as {Sam,Kettie}
Update: Db Structure-
public class UserProfile
{
public long UserId { get; set; }
public string FullName { get; set; }
public string Interests { get; set; } //Store comma separated Interest Ids here
}
public class Interest
{
public long InterestId { get; set; }
public string Name { get; set; }
}
I achieve this by
var interestIds = db.Interests.Where(i => i.Name.Contains(query))
.Select(i=> i.InterestId)
.ToList();
var profiles = new List<UserProfile>();
foreach (var id in interestIds)
{
profiles.AddRange(db.UserProfiles
.Where(p=> p.Interests.Contains(id.ToString()))
.ToList());
}
return profiles;
But, when dealing with huge records it takes long time to execute so I want help to optimise this.
Apologies if the answer is not exactly what you are looking for as I'm unfamiliar with entity-framework, but looking at it purely from a C# point of view:
For every id in your interestIds you are filtering your entire database every time (and doing this by checking a collection on each entry) - I would imagine you are incurring a performance penalty here.
If possible, you could map each interest id to a list of user names. This only needs to be done once and may perform better - something like:
var dict = new Dictionary<long, List<string>>();
foreach(var user in userProfiles)
{
foreach(var interest in user.Interests)
{
List<string> names;
if(dict.TryGetValue(interest, out names))
names.Add(user.Name);
else
dict.Add(interest, new[] { user.Name }.ToList());
}
}
long[] interestIds = new[] { 4, 8 };
HashSet<string> profiles = new HashSet<string>();
foreach (var interestId in interestIds)
profiles.UnionWith(dict[interestId]);
So iterating over every interest for every user, you then add each interest as a key and add the user to the list of users for that key.
You can then iterate over your list of interestIds and pull the matching list out user names out of the dictionary (note the HashSet - this will stop you getting duplicate names for users who match more than one interest).
This will help you to execute your query in sql server only once .. I checked it using profiler and it's better because when foreach loop iterate .. it open and close the sql the connection each time.. which leads to populate your desire result much slower..
var interestIds = db.Interests.Where(i => i.Name.Contains("interest_name"))
.Select(i => i.InterestId)
.ToList().ConvertAll(i => i.ToString());
//var profiles = new List<UserProfile>();
var allProfiles = (from UserProfile up in db.UserProfiles
from i in interestIds
where up.Interests.Contains(i)
select up).ToList();
return allProfiles;
//string sId = null;
//foreach (var id in interestIds)
//{
// sId = id.ToString();
// profiles.AddRange(db.UserProfiles
// .Where(p => p.Interests.Contains(sId))
// .ToList());
//}
//return profiles;

The fast way to select the elements in a list which a property is in another list

I have a class Like this,
class Test
{
public int ID { get; set; }
public string Name { get; set; }
}
I also have a List ids;
I want to select all the elements in List which its ID is in the list ids. My current solution like this:
var v = from t in tests where ids.Contains(t.ID) select t;
If the count of List is very large, more than 10000 items, is it a effective way?
Thanks
You could try this:
var lookup = ids.ToDictionary(x => x);
var matching = tests.Where(t => lookup.ContainsKey(t.ID));
That will work provided that ids does NOT contain any duplicate values.
Or (faster, as per comments below):
var lookup = new HashSet<int>(ids);
var matching = tests.Where(t => lookup.Contains(t.ID));
This will work even if there are duplicate IDs (again, see comments below).
List items = new List();
items.Find(p => p == "blah");
Should be a better way

Selecting related Article based on common Tags in Entity Framework

I Have these entities:
public class Article {
public int Id { get; set; }
public virtual IList<Tag> Tags { get; set; }
}
public class Tag {
public int Id { get; set; }
public virtual IList<Article> Articles { get; set; }
}
I load an Article by its Tags like this:
var articleByTags = context.Articles.Include(a => a.Tags).FirstOrDefault(a => a.Id == someId);
Now, how can I get a list of articles, that have must common tags with the selected article? Can you help me please?
Good question. Here is solution:
// you should have a list of primitive types to use in SQL IN keyword
var ids = articleByTags.Tags.Select(t => t.Id).ToList();
var query = (from article in context.Articles
// do you want same article again? NO! so remove the current article
where article.Id != articleByTags.Id
// this line would create a IN statement to SQL
// if you don't want to load common tags, you can merge this line
// by the next it, and just create a COUNT()
let commonTags = article.Tags.Where(tag => ids.Contains(tag.Id))
let commonCount = commonTags.Count()
// there as any?
where commonCount > 0
// ascending! not descending! you want most common
orderby commonCount ascending
// create your projection
select new {
Id = article.Id,
Title = article.Title,
Tags = article.Tags,
Commons = commonTags,
CommonCount = commonCount
// or any property you want...
})
// how many you want to take? for example 5
.Take(5)
.ToList();
I think you want something like this:
var listOfMustHaveTags = new List<Tag>(); // Should be filled with Tags
var articleByCommonTags = context.Articles.Where(n => n.Tags.All(listOfMustHaveTags.Contains));
If the requirement says that a least one tag must fit, then change .All() with .Any().

Categories

Resources