Get results matching a list of Ids using Linq - c#

I am trying to get the list of gameIds that satisfy all the genreIds included in a a List<int>.
The tables (partial):
editorial_list:
game_id
content
game_genres (game can belong to several genres):
id
game_id
genre_id
I need to get the list of the game Ids of games that exists for all the genre_id's in the game_genres table.
For example:
The list of genre_id's includes genre 2 and 3.
If Game Id = 14 exists in game_genres table for both genre_id = 2 and genre_id = 3. So it will be included in the final results.
Here is my code:
// get the list of game_id's that have an editorial
var editorialList = (from ee in db.editorials where ee.is_enabled == true select new {
game_id = ee.game_id
}).ToList();
// Produce a list of the games in the editorials and their genre Ids that the belong to
var gameAndGenres = (from el in editorialList join gg in db.game_genres
on el.game_id equals gg.game_id
select new {
game_id = el.game_id,
genre_id = gg.genre_id
}
);
var res = gameAndGenres.Where(
x => x.genres.Contains(x.genre_id)) == genres.Count; // stuck here
The end results should be a unique list of game_id's, that each game in the list belongs to all the genres that was listed in the genres List<int>.
I created several steps to help me understand the query, but it might be able to be solved in one line, I just wasn't able to solve it.
Update: This is a new code that I'm trying.
var res = gameAndGenres.GroupBy(x => x.game_id)
.Select(g => new {
game_id = g.Key,
genreIds = g.Select( c => c.genre_id)
});
var res2 = res.Where(x => genres.Intersect(x.genreIds).Count()
== genres.Count()).ToList();

The relation between Game and Genre is a many-to-many: a Game can belong to zero or more genres and a Genre can have zero or more Games.
You want (the IDs of) all games that belong to all Genres in your game-genres table.
For example, if your list game-genres contains only records with references to genre 2 and genre 3 then you want all games that belong to genres 2 and 3.
Note that a Genre may exist that is not owned by any 'Game'. In that case there is no record in the game-genres table with a reference to this Genre.
Below an example of your Property Entity Framework classes. The actual names of the classes and properties may vary, but you'll get the Id
public class Game
{
public int GameId {get; set;}
public virtual ICollection<Genre> Genres {get; set;}
... // other properties
}
public class Genre
{
public int GenreId {get; set;
public virtual ICollection<Game> Games {get; set;}
...
}
public MyDbContext : DbContext
{
public DbSet<Game> Games {get; set;}
public DbSet<Genre> Genres {get; set;}
...
}
The entity framework model builder will detect that there is a many-to-many relation between Games and Genres and will automatically add a table like your 'game-genres'.
The nice thing, is that if a certain Genre does not belong to any Game, it won't be in your game_genres table. Also the other way round: if you have an element in your game-genres table, than there is at least one game that belongs to that genre.
So you don't want all genres, you only want genres that are used by at least one Game.
IEnumerable<Genre> usedGenres = dbContext.Genres
.Where(genre => genre.Games.Any());
Now you want only those Games that belong to EVERY genre in usedGenres
= every game where every element of usedGenres is in the collection Game.Genres.
To check if a genre is in the collection of Game.Genres, we only have to compare the GenreId of Game.Genres with the GenreId of usedGenreIds
IEnumerable<int> usedGenreIds = usedGenres
.Select(genre => genre.GenreId);
IEnumerable<Game> gamesWithAllUsedGenres = dbContext.Games
.Where(game => usedGenreIds.All(genreId => game.Genres.Select(genre => genre.GenreIdContains(genreId));

Something like this:
var gamesIds = db.editorials
.Where(e => db.game_genres.Select(gg => gg.genre_id).Distinct().All(genId => db.game_genres.Any(gg => gg.game_id == e.game_id && gg.genre_id == genId)))
.Select(e => e.game_id)
.ToList();
Select game_id from editorials, where the game_id have an entry of all distinct genre_ids in the game_genre table.
If you want all games having all genres in a list instead of all in the table:
List<int> genreIds = new List<int>() {1,2,3};
var gamesIds = db.editorials
.Where(e => genreIds.All(genId => db.game_genres.Any(gg => gg.game_id == e.game_id && gg.genre_id == genId)))
.Select(e => e.game_id)
.ToList();

You can achieve it by groupping rows from your tables.
Using Lambda expressions:
var res = db.game_genres.GroupBy(gg => gg.game_id)
.Where(g => g.Count() == db.genres.Count())
.Select(x => x.Key).ToList();
or using LINQ:
var res = (from gg in db.game_genres
group gg by gg.game_id into g
where g.Count() == db.genres.Count()
select g.Key).ToList();
Such groupping produces one record (called group) per game_id. The Key of each group is the game_id used in the "group by" clause. Each group is a collection of rows having equal game_id, therefore you can treat it like any other collection of database entities, here you filter it using where statement and call Count() on the collection.

Related

How to convert Linq query for joining 3 tables to Linq method and use Include

I have seen a few posts that are similar but I haven't been able to figure out how to solve my problem. I have three tables in an ASP.NET Core project. There is a one-to-many relationship on my Prescriptions and Prescribers tables and one-to-many relationship on my Prescribers and Marketers tables. I want to query the Prescriptions table and fetch the foreign key PrescriberId, then query Prescribers table and fetch the foreign key MarketerId and return the MarketerId. So far I have this linq
'var presciber = from p in _context.Prescriptions
join f in _context.Prescribers on p.PrescriberId equals f.Id
join m in _context.Marketers on f.MarketerId equals m.Id
where p.FolderStatusId == 3
select new { m.Id };
I want to be able to use .Include so I can include other data from Marketers table, so instead of displaying MarketerId, I can display Marketer Name and not get null in the view. So I want to convert this query into a method, I have tried this
var prescriber = _context.Prescriptions
.Join(_context.Prescribers,
p => p.PrescriberId,
f => f.Id,
(p, f) => new { Prescriptions = p, Prescribers = f })
.Join(_Context.Marketers,
f => f.Prescribers.MarketerId,
m => m.Id,
(f, m) => new { Prescribers = f, Marketers = m })
.Where(d => d.FolderStatusId == 3);//This line gets the error
On the Where clause I get the error
<anonmyous type: <anonymous type:Prescription Prescriptions, Prescriber Prescribers> Prescribers, Marketer Marketers> does not contain a definition for FolderStatusId
Here are my classes
public class Prescriptions {
public int Id {get;set;}
public int FolderStatusId {get;set;}
public int PrescriberId { get; set; }
public string Medication { get; set; }
public virtual Prescriber Prescriber { get; set; }
}
Here is the Prescribers class
public class Prescriber {
public int Id { get; set; }
public int MarketerId { get; set; }
public string Name { get; set; }
public virtual Marketer Marketer {get;set;}
public virtual ICollection<Prescription> Prescriptions { get; set; }
}
Here is the Marketer class
public class Marketer {
public int Id { get;set; }
public string Name { get; set; }
public virtual ICollection<Prescriber> Prescribers { get; set; }
}
My question is how can I correct this Linq method and also where do I add the .Include?
My advice would be to use identifiers that would mean anything to the reader. That would have helped you to see the cause of the problem:
var result = _context.Prescriptions
.Join(_context.Prescribers,
prescription => prescription.PrescriberId,
prescriber => prescriber.Id,
(prescription, prescriber) => new
{
Prescription = prescription,
Prescriber = prescriber,
})
// End of first join
.Join(_Context.Marketers,
firstJoinResult => firstJoinResult.Prescriber.MarketerId,
marketer => marketer.Id,
(firstJoinResult, marketer) => new
{
Prescription = prescription,
Prescriber = prescriber,
Marketer = marketer,
})
// end of second join
.Where(secondJoinResult => secondJoinResult.FolderStatusId == 3);
//This line gets the error
Your second join result, does not have a property FolderStatusId. In your original code, you used identifier d to refer to the second join result. Only Prescriptions have a FolderStatusId. So your Where should be like:
.Where(secondJoinResult => secondJoinResult.Prescription.FolderStatusId == 3);
There's room for improvement
Use Where before the joins
If you use the Where before you start joining, a lot of Prescriptions don't have to be joined at all:
var result = _context.Prescriptions
.Where(prescription => prescription.FolderStatusId == 3);
.Join(_context.Prescribers,
prescription => prescription.PrescriberId,
prescriber => prescriber.Id,
... // etc
Select only the properties that you plan to use
So a Marketer has zero or more Prescribers, and every Prescriber has zero or more Prescriptions, like you said: one-to-many relationships.
Every Prescription of Marketer [14] has a foreign key MarketerId with a value 14. If Marketer [14] has 1000 Prescriptions, then you would transfer 1000 times the combination of Marketer [14] with one of the Prescriptions with MarketerId [14].
Apart from the fact that you would transfer 1000 times the same Marketer data, you would also transfer the same value of Prescription.MarketerId, of which you already know the value: it has value [14]. What a waste of processing power!
Solution: select only the properties that you plan to use:
var result = _context.Prescriptions
.Where(prescription => prescription.FolderStatusId == 3);
.Join(_context.Prescribers,
prescription => prescription.PrescriberId,
prescriber => prescriber.Id,
... // etc
(firstJoinResult, marketer) => new
{
Marketer = new
{
// Select only the Marketer properties that you plan to use
Id = marketer.Id,
Name = markter.Name,
...
}
Subscriber = new
{
// Again: only the properties that you plan to use:
Id = firstJoinResult.Subscriber.Id,
Name = markter.Name,
// No need for this, you know the value:
// MarketerId = firstJoinResult.Subscriber.MarketerId,
},
Prescription = new { ... },
});
In Entity framework always use Select to query data and select only the properties that you plan to use. Only use Include if you plan to change the included data.
The reason is, that entity framework can only update items that are fetched completely.
Consider a GroupJoin
I already mentioned in the previous chapter: the same values of Marketer [14] will be transferred many times.
Whenever you want Items with their subitems, like Schools with their Students, Customers with their Orders, and Marketers with their Prescribers, consider a GroupJoin instead of a Join.
var result = _context.Marketers
.GroupJoin(_context.Prescribers,
marketer => marketer.Id,
prescriber => prescriber.MarketerId,
// parameter ResultSelector:
// get all Marketers with their prescribers to make one new
(marketer, prescribersOfThisMarketer) => new
{
Id = marketer.Id,
Name = marketer.Name,
...
Prescribers = prescribersOfThisPrescription.GroupJoin(
_context.Prescriptions.Where(prescription => prescription.FolderStatusId == 3),
prescriber => prescriber.Id,
prescription => prescription.PrescriberId,
// from every prescriber with all its prescriptions, make one new
(prescriber, prescriptionsOfThisPrescriber) => new
{
Id = prescriber.Id,
Name = prescriber.Name,
...
Prescriptions = prescriptionsOfThisPrescriber.Select(presription => new
{
Id = prescription.Id,
...
})
.ToList(),
})
.ToList(),
});
So Instead of
Marketer Subscriber
A 10
A 11
B 20
A 28
C 12
B 13
You get:
Marketer A with his Subscribers 10, 11, 28
Marketer B with his Subscriber 20, 13
Marketer C with his Subscriber 12
Marketer D without any Subscribers
This usually looks more like you would want your result.
Note: GroupJoin will also return items that have no subitems!, like Marketers without any Subscribers. Usually you want this, but if you don't want them, use a Where to filter them out:
.Where(marketer => marketer.Subscribers.Any());
The most revolutional: use the ICollection!
Some people told me that EF-core does not support this. I know that full entity framework certainly does: use the ICollections instead of a group-join.
Requirement Give me (some properties of) all Marketers, each with (some properties of all) his Prescribers, each with his Prescriptions.
var marketers = dbContext.Marketers.Select(marketer => new
{
Id = marketer.Id,
Name = marketer.Name,
...
Prescribers = marketer.Prescribers.Select(prescriber => new
{
Id = prescriber.Id,
Name = prescriber.Name,
...
Prescriptions = prescriber.Prescriptions
.Where(prescription => prescription.FolderStatusId == 3)
.Select(prescription => new
{
Id = prescription.Id,
...
})
.ToList(),
})
.ToList(),
});
This feels way more naturally than a (Group-)Join. Entity framework knows the relations between the tables and translate it into the correct (Group-)Join.

Entity Framework - GroupBy then OrderBy

I have a group of records that includes PatientID and Appointment Date.
I would like to group them by PatientID and order the groups by the oldest appointment in each PatientID group. Like this...
ID ApptDate
----------------------
3 2/5/2005 (oldest compared to all groups , so group "ID = 3" is sorted first)
3 5/10/2006
3 6/2/2010
1 8/5/2007
1 9/1/2015
2 6/15/2009
2 9/19/2009
I'm pretty sure I need to use grouping first to obtain the oldest date value for each ID and then order by that value but I am stuck understanding how the two functions will work together.
var query = from a in db.Appointments
group a by new { a.Id, a.ApptDate} into pa
select new {
ID = pa.Key.Id,
Date = pa.Min(x =>x.ApptDate) ...
... but I crash and burn at this point.
Any help appreciated.
I modified a bit Tanveer Badar's answer to return a row for each entity.
Create a class for your return data
public class ReturnType
{
public int ID { get; set; }
public DateTime AppDate { get; set; }
}
Get a group of IDs and enumerable of ordered dates
var result = db.Appointment
.GroupBy(a => a.Id)
.OrderBy(a => a.Min(n => n.Date))
.Select(a => new { ID = a.Key, AppDates = a.OrderBy(na =>
na.Date).Select(ne => ne.Date) })
.ToList();
Then flatten the returned list. I tried with SelectMany but with no success.
var results = new List<ReturnType>();
foreach (var a in result)
{
foreach (var date in a.AppDates)
{
var returnType = new ReturnType
{
ID = a.ID,
AppDate = date
};
results.Add(returnType);
}
}
You requirements are as follows (though the question does not mention one of them, but it is apparent from how data is laid out):
Group based on patient ID.
Sort groups based on oldest appointment date in each group.
Sort data within a group based on appointment date.
var query = records.GroupBy(r => r.PatientId) // Group patients
.OrderBy(g => g.Min(r => r.AppointmentDate)) // sort groups by oldest record in each
.Select(g =>
new
{
PatientId = g.Key,
Records = g.OrderBy(r => r.AppointmentDate) // sort records within a group by oldest
});
In my case it works fine...
var query = from a in db.Appointments
group a by new { a.Id, a.ApptDate} into pa
select new {
ID = pa.Key.Id,
Date = pa.Max(x =>x.ApptDate)
}).Select(x=>new {
x.ID,x.Date
}).ToList();
db.Appointments.GroupBy(new {ID = item.ID, ApptDate = item.ApptDate}).OrderByDescending(item => item.ApptDate)

c# and LINQ- group objects based on their IDs and then find common values in them

I have a object collection like in the following.
List<Product> productList=new List<Product>();
Structure of the class Product is like in the following.
public class Product
{
public int Id;
public Product(int id)
{
Id=id;
}
public List<SomeOtherList> t;
}
The class Product contains some other list
public class SomeOtherList
{
private int value;
public SomeOtherList(int val)
{
value=val;
}
}
Product prodOne=new Product(1);
List<SomeOtherList> temp_1=new List<SomeOtherList>();
temp_1.Add(new SomeOtherList(10));
temp_1.Add(new SomeOtherList(20));
temp_1.Add(new SomeOtherList(30));
prodOne.t=temp_1;
Product prodTwo=new Product(2);
List<SomeOtherList> temp_2=new List<SomeOtherList>();
temp_2.Add(new SomeOtherList(100));
temp_2.Add(new SomeOtherList(40));
temp_2.Add(new SomeOtherList(30));
prodTwo.t=temp_2;
Product prodThree=new Product(1);
List<SomeOtherList> temp_3=new List<SomeOtherList>();
temp_3.Add(new SomeOtherList(10));
temp_3.Add(new SomeOtherList(20));
prodThree.t=temp_3;
productList.Add(prodOne);
productList.Add(prodTwo);
productList.Add(prodThree);
I want to get "SomeOtherList" objects common to all the Products. For example, for Product Id 1, I should get SomeOtherList object 20 as the common One.
I have written the following LINQ query to achieve this.
List<Product> t=productList
.GroupBy(p => p.Id)
.Where(g => g.Count() == productList.Count)
.Select(x => x.First())
.ToList();
But it does not give me what I want . Can someone point what is wrong with that query ?
What you want is the intersection between your SomeOtherList when the product is some specific value (i.e. 1).
So you need to first select the each Product which has the correct Id and then join their SomeOtherList together and group by the value.
To do this we need to flattern the each SomeOtherList for a Product, which we can do with SelectMany
Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence.
Single Id
If we are only intrested in a single Id then we can do the following
var common1 = productList
.Where(product => product.Id == 1)
.SelectMany(product => product.t)
.GroupBy(groupValue => groupValue.value)
.Where(groupValue => groupValue.Count() > 1)
.Select(values => values.First());
This will:
Filter the products based on Id being equal to 1
Flattern each products SomeOtherList into one IEnumerable
Group each element based on SomeOtherList.value
Filter out any groups which only have 1 entry, as we only want those that are common
All Id's
If, however, we would like to get the list of all duplicates for each key then we can do the same as for a Single Id but have a first step where we group based on the Id.
var common = productList
.GroupBy(groupId => groupId.Id)
.Select(groupId => groupId.SelectMany(product => product.t)
.GroupBy(groupValue => groupValue.value)
.Where(groupValue => groupValue.Count() > 1)
.Select(values => values.First().value));
Difficult to do in single LINQ query, but following code should work.
var min = productList.Min(x=>x.Id);
var max = productList.Max(x=>x.Id);
for(int i = min; i<=max;i++)
{
var products = productList.Where(x=>x.Id = i).ToList();
List<SomeOtherList> cList = new List<SomeOtherList>();
foreach(var product in products)
{
cList.AddRange(product.t);
}
var distinctList = cList.Distinct();
}
Because of the nested lists you need to group unwrap the list of SomeOtherList using SelectMany before grouping again. I think this is what you're after.
var result = productList
.GroupBy(p => p.Id)
.Select(pg =>
new
{
Id = pg.Key,
CommonOtherList = pg
.SelectMany(solg => solg.t)
.GroupBy(solg => solg.value)
.Where(solg => solg.Count() == pg.Count())
.Select(solg => new { OtherId = solg.Key })
});
foreach (var product in result)
{
Console.WriteLine(product.Id);
Console.WriteLine("**Common**");
foreach (var otherProduct in product.CommonOtherList)
{
Console.WriteLine("-->{0}", otherProduct.OtherId);
}
}

seeking elegant linq solution

I have a model like this:
public class Post
{
public int PostId,
public List<Category> Categories
}
Posts have at least 1 category, but can also have many categories.
I have a List, this list contains Posts (some with the same PostId), and each entry in the List contains exactly one unique Category (Categories.Count = 1 for each).
I want to create a new List with only distinct Posts (distinct PostId), with the Categories list populated with each category in the original List having the same PostId.
Basically, find each Post in the original list, and populate the Categories field by adding each of their First (and only) entry in their Categories field together.
Is there a nice solution for this in linq?
Category is just an Enum,
I have tried using varous nested foreach and for loops and it works but it is just gross. I know there is a clean way to do it.
Example:
Categories = { PostId = 1, Category = Shopping }, { PostId = 1, Category = Pizza }, { PostId = 2, Category = Laundry }
after sequence desired output to be:
Categories = { PostId = 1, Categories = Shopping, Pizza }, { PostId = 2, Categories = Laundry }
Order does not matter for the category list
Given that you will have only one category per post (as stated in the second paragraph), you can try
var result = aPosts
.GroupBy(item => item.PostId, item => item.Categories[0])
.Select(group => new Post() { PostId = group.Key, Categories = new List<Category>(group) })
.ToList();
Note that having a Post constructor that accepts both PostId and Categories would allow a more simplified version of any solution.
Post(int postId, IEnumerable<Category> categories)
{
PostId = postId;
Categories = new List<Category>(categories);
}
Would allow the following:
var result = aPosts
.GroupBy(item => item.PostId, item => item.Categories[0])
.Select(group => new Post(group.Key, group))
.ToList();
something like below
var result = yourlist.GroupBy(l=>l.PostId)
.Select(x=>new Post{ PostId =x.Key, Categories =x.SelectMany(y=>y.Categories).ToList()})
.ToList();
With LINQ expressions:
var result = from o in posts
group o by o.PostID into gr
select new Post
{
PostID = gr.Key,
Categories = gr.SelectMany(c=>c.Categories).ToList()
};
All the other given solutions would work. But if you might have more than 1 category in the Category list, and you need only the first of each Post you can use following.
var posts =
postList.GroupBy(p => p.PostId)
.Select(
g =>
new Post
{
PostId = g.Key,
Categories =
g.Select(p => p.Categories.FirstOrDefault())
.Where(c => c != null).ToList()
});
Also, make sure you initialize you Categories property (e.g. in the constructor of Post class) before using Linq given in the answers. Otherwise you might get NUllReferenceException.

Group By Query with Entity Framework

In my application I have Movements associated with a category.
I want a list of the most frequent category.
My objects are:
Category: catId, catName
Movement: Movid, movDate, movMount, catId
I think it would have to raise it with a "Group By" query (grouping by catId and getting those more)
(Im using Entity Framework 6 in c#)
From already thank you very much!
IMPORTANT: Entity Framework 7 (now renamed to Entity Framework Core 1.0) does not yet support GroupBy() for translation to GROUP BY in generated SQL. Any grouping logic will run on the client side, which could cause a lot of data to be loaded.
https://blogs.msdn.microsoft.com/dotnet/2016/05/16/announcing-entity-framework-core-rc2
group the movements by category and select catid and count.
join this result with category to get the name and then descending sort the results on count.
var groupedCategories = context.Movements.GroupBy(m=>m.catId).Select(g=>new {CatId = g.Key, Count = g.Count()});
var frequentCategories = groupedCategories.Join(context.Categories, g => g.CatId, c => c.catId, (g,c) => new { catId = c.catId, catName = c.catName, count = g.Count }).OrderByDescending(r => r.Count);
foreach (var category in frequentCategories)
{
// category.catId, category.catName and category.Count
}
i hope this help:
var query = dbContext.Category.Select(u => new
{
Cat = u,
MovementCount = u.Movement.Count()
})
.ToList()
.OrderByDescending(u => u.MovementCount)
.Select(u => u.Cat)
.ToList();
I resolved the problem!
I used the proposal by "Raja" solution (Thanks a lot!).
This return a collection composed of "Category" and "Count". I Change it a bit to return a list of Categories.
var groupedCategories = model.Movement.GroupBy(m => m.catId).Select(
g => new {catId= g.Key, Count = g.Count() });
var freqCategories= groupedCategories.Join(model.Category,
g => g.catId,
c => c.catId,
(g, c) => new {category = c, count = g.Count}).OrderByDescending(ca => ca.count).Select(fc => fc.category).ToList ();
you just need to use navigation property on category simply, you have a navigation property on category contains all related Movement, i call it Movements in following query. you can write your query like this, with minimum of connection with DB.
class Cat
{
public Guid catId { get; set; }
public string catName { get; set; }
public IEnumerable<Movement> Movements { get; set; }
public int MovementsCount { get { return Movements.Count(); } }
}
var Categories = category.Select(u => new Cat()
{
u.catId,
u.catName,
Movements = u.Movements.AsEnumerable()
}).ToList();
var CategoriesIncludeCount = Categories.OrderBy(u => u.MovementsCount).ToList();

Categories