LINQ - take x objects from database for each object property value - c#

I have an entity:
public class Component
{
public Guid Id { get; set; }
public string Name { get; set; }
public ProductType Type { get; set; }
}
ProductType
public enum ProductType
{
Harddrive = 1,
GraphicCard,
ComputerCase,
}
I'm trying to get list of Product that contains 15 random items (5 per ProductType) in single LINQ.
ComputerCase, GraphicCard and Harddrive inherts from same base class
For now I have something like that:
var response = db.Components
.Select(x => new Product
{
Id = x.Id,
Name = x.Name,
Type = x.Type,
}).ToList();
but I have no idea how could I achive what I need. Can anyone help me with that?

Make groups of Components with the same ProductType. From the resulting collection of groups take the first Component in the group. From that result take the first 5, items.
var result = myComponents. // take the collection of Components
.GroupBy(component => component.Type) // group this into groups of components with same Type
.Select(group => group.FirstOrDefault()) // from every group take the first element
.Take(5) // take only the first five
Of course, if you really want a proper random, you'll have to fetch all Component groups to local memory and use RND to extract random groups and random element from each selected group

Related

How to Sort a List based on the value of a list within the list in C#?

Probably the question is a bit confusing but basically I have a list which inside consisting another list. Like this:
public class Transaction
{
public string TransactionType { get; set; }
public List<TransactionDetails> Details { get; set; } = new List<TransactionDetails>();
}
public class TransactionDetails
{
public string TransactionDate { get; set; }
public double TransactionAmount { get; set; }
public double TransactionBalance { get; set; }
}
Now, I want to sort the Transaction list based on the TransactionDate inside the TransactionDetails list. If let's say I just want to sort it based on TransactionType, I know that this is the way to do it: (assuming I already added the list)
List<Transaction> listTransactions;
List<Transaction> sortedListTransactions;
sortedListTransactions = listTransactions
.OrderBy(x => x.TransactionType)
.ToList();
I tried it and this worked. But, when I applied basically the same method for my case,which is to sort the list based on TransactionDate, it doesnt give me the answer I needed. Please let me know if there is any way I could achieve this..
If you have to sort a list, you have to pick an element which defines the order (like a value, a date, etc.) If the property you like to use for sorting is also a list, you have to either pick one element from that inner list (e.g. min, max, first, last) or to aggregate all inner list values (like sum, average, median, concat) to one value.
One approach in your case could be to find the minimum element within the inner list and use this as criteria for sorting the outer elements. This could be something like this:
var sortedTransactions = transactions
.OrderBy(transaction => transaction.Details
.OrderBy(detail => detail.TransactionDate)
.FirstOrDefault())
.ToList();
From your example another possibility to sort the outer elements from the inner list, could be to take the sum of amount of all transactions, which would be something like this:
var sortedTransactions = transactions
.OrderBy(transaction => transaction.Details
.Sum(detail => detail.TransactionAmount))
.ToList();
Nevertheless, it is up to you to define the criteria of sorting on every level and then let it bubble up to give the sorting criteria back.

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;

Remove all but 1 object in list based on grouping

I have a list of objects with multiple properties in it. Here is the object.
public class DataPoint
{
private readonly string uniqueId;
public DataPoint(string uid)
{
this.uniqueId = uid;
}
public string UniqueId
{
get
{
return this.uniqueId;
}
}
public string ScannerID { get; set; }
public DateTime ScanDate { get; set; }
}
Now in my code, I have a giant list of these, hundreds maybe a few thousand.
Each data point object belongs to some type of scanner, and has a scan date. I want to remove any data points that were scanned on the same day except for the last one for a given machine.
I tried using LINQ as follows but this did not work. I still have many duplicate data points.
this.allData = this.allData.GroupBy(g => g.ScannerID)
.Select(s => s.OrderByDescending(o => o.ScanDate))
.First()
.ToList();`
I need to group the data points by scanner ID, because there could be data points scanned on the same day but on a different machine. I only need the last data point for a day if there are multiple.
Edit for clarification - By last data point I mean the last scanned data point for a given scan date for a given machine. I hope that helps. So when grouping by scanner ID, I then tried to order by scan date and then only keep the last scan date for days with multiple scans.
Here is some test data for 2 machines:
Unique ID Scanner ID Scan Date
A1JN221169H07 49374 2003-02-21 15:12:53.000
A1JN22116BK08 49374 2003-02-21 15:14:08.000
A1JN22116DN09 49374 2003-02-21 15:15:23.000
A1JN22116FP0A 49374 2003-02-21 15:16:37.000
A1JOA050U900J 80354 2004-10-05 10:53:24.000
A1JOA050UB30K 80354 2004-10-05 10:54:39.000
A1JOA050UD60L 80354 2004-10-05 10:55:54.000
A1JOA050UF80M 80354 2004-10-05 10:57:08.000
A1JOA0600O202 80354 2004-10-06 08:38:26.000
I want to remove any data points that were scanned on the same day except for the last one for a given machine.
So I assume you want to group by both ScanDate and ScannerID. Here is the code:
var result = dataPoints.GroupBy(i => new { i.ScanDate.Date, i.ScannerID })
.OrderByDescending(i => i.Key.Date)
.Select(i => i.First())
.ToList();
If I understand you correctly this is what you want.
var result = dataPoints.GroupBy(i => new { i.ScanDate.Date, i.ScannerID })
.Select(i => i.OrderBy(x => x.ScanDate).Last())
.ToList();
This groups by the scanner id and the day (SacnnerDate.Date will zero out the time portion), then for each grouping it orders by the ScanDate (since the groups are the same day this will order on the time) and takes the last. So for each day you will get one result for each scanner which has the latest ScanDate for that particular day.
Just as an aside, the class could be defined as
public class DataPoint
{
public DataPoint(string uid)
{
UniqueId = uid;
}
public string UniqueId {get; private set; }
public string ScannerID { get; set; }
public DateTime ScanDate { get; set; }
}

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

I need help with using linq

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;

Categories

Resources