Implement a "join" with a variety of tables in Entity Framework - c#

I have three tables:
Materials:
ID
Title
Content
Likes:
ID
MaterialID
UserID
IsLiked
Visitors:
ID
UserID
MaterialID
Date
ReadNow
I would like to get an object like:
Title
Content
CountLikes
CountVisitors
I tried to do the following:
from mat in ctx.materials
let visitors = mat.VisitorsCollection.Where(x=>x.ReadNow).Count()
let likes = mat.LikesCollection.Where(x=>x.IsLiked).Count()
let iliked = mat.LikesCollection.Where(x=>x.UserID == myID && x.IsLiked).Any()
select new {
Material = mat,
Visitors = visitors,
Likes = likes,
Liked = iliked
}
I get a selection of materials and separately the Entity Framework receives data on the number of visitors and so on.
I also tried the following:
from mat in ctx.materials
join lik in ctx.Likes.Where(x=>x.UserID == myID && x.IsLiked) on map.ID equals lik.MaterialID
select new {
Material = mat,
Liked = lik.Any()
}
but now an error occurs:
Microsoft.EntityFrameworkCore.Query:Warning: The LINQ expression 'Any()' could not be translated and will be evaluated locally.

If you are using entity framework, consider to use the ICollections, instead of performing the joins yourself.
You have a sequence of Materials where every Material has zero or more Likes and zero or more Visitors, both one-to-many relations, using a foreign key to Material.
If you've followed the entity framework code first conventions, you'll have classes similar to the following
class Material
{
public int Id {get; set;}
public string Title {get; set;}
public string Content {get; set;}
// every Material has zero or more Likes (one-to-many)
public virtual ICollection<Like> Likes {get; set;}
// every Material has zero or more Visitors (one-to-many)
public virtual ICollection<Visitor> Visitors {get; set;}
}
Likes and Visitors:
class Like
{
public int Id {get; set;}
public bool IsLiked {get; set;}
...
// every Like belongs to exactly one Material, using foreign key
public int MaterialId {get; set;}
public virtual Material Material {get; set;}
}
class Visitor
{
public int Id {get; set;}
...
// every Visitor belongs to exactly one Material, using foreign key
public int MaterialId {get; set;}
public virtual Material Material {get; set;}
}
This is all that entity framework needs to detect the one-to-many relationships. It might be that you want different table names, or different identifiers for your columns. In that case attributes or fluent API is needed
In entity framework the columns of the tables are represented by non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many, etc)
Once you've got your class definitions correctly, your query is simple and very intuitive:
Requirement:
From my collection of Materials, give me from every Material, the Title, the Content, the number of Likes it has and the number of Visitors it has:
var result = myDbContext.Materials
.Where(material => ...) // only if you don't want all Materials
.Select(material => new // from every Material make one new object
{ // containing the following properties
Title = material.Title,
Content = material.Content,
// if you want any information of the likes of this material, use property Likes
LikeCount = material.Likes
.Where(like => like.IsLiked) // optional, only if you don't want all likes
.Count(),
NrOfVisitors = material.Visitors
.Where(visitor => ...) // only if you don't want all visitors
.Count(),
});
In words: from my complete collection of Materials, keep only those Materials that ... From every remaining Material, make one new object:
Title is the title of the Material
Content is the content of the Material
LikeCount is the number of Likes of this material (that have a true IsLiked)
NrOfVisitors is the number of Visitors of this material (that are ...)
Entity framework knows your relations, and knows that a GroupJoin is needed.

Well if you have foreign keys in the database then the EF would generate links between the objects so all you need to do is:
var result = ctx.materials.Select(x =>
new SomeClass{
Material = x,
Visitors = x.Visitors.Where(v => v.ReadNow).Count(),
Likes = x.Likes.Where(y => y.IsLiked).Count(),
Liked = x.Likes.Where(z => z.IsLiked && z.UserID == myID).Count()
}).ToList();
The syntax maybe is not totally correct, but you get the point ...

Related

Select all columns from main table and only 1 column from JOINed table

I have two tables:
Table1
Id ArticleName ArticleTypeId
1 Blah Blah 3
2 Helo Blah 5
and
Table2
ArticleTypeId TypeName
3 Business
5 Construction
I'm trying to Join TableA and TableB on ArticleTypeId and basically return everything from Table1 and TypeName from Table2
Here's what I'm trying to do, but I'm not sure to to edit the SELECT in the statement to include the TypeName
var articles = (from s in _context.Articles
join b in _context.ArticleTypes on s.ArticleTypeId equals b.ArticleTypeId
select s).ToList();
Or is there an easier way to do this?
Goal:
Id ArticleName TypeName
1 Blah Blah Business
2 Helo Blah Construction
So you have two tables, a table with Articles (Table1), and a table with ArticleTypes (Table2). I decided to give your tables names that are meaningful, this makes discussion easier.
There is a one to many relationship between Articles and ArticleTypes: Every Article has exactly one ArticleType, namely the Article type that the foreign key ArticleTypeId refers to. Every ArticleType has zero or more Articles that refer to it.
You are using entity framework. If you've followed Entity Framework Coding Conventions, you'll have classes similar to the following.
class Article
{
public int Id {get; set;}
public string Name {get; set;}
// every Article has one ArticleType, namely the one that the foreign key refers to
public int ArticleTypeId {get; set;}
public virtual ArticleType ArticleType {get; set;}
}
class ArticleType
{
public int Id {get; set;}
public string TypeName {get; set;}
// every ArticleType has zero or more Articles referring to it (one-to-many)
public virtual ICollection<Article> Articles {get; set;}
}
In entity framework the non-virtual properties refer to columns of the tables; the virtual properties refer to the relations between the tables (one-to-many, many-to-many, ...)
The foreign key ArticleTypeId is a real column, so the property is non-virtual. Property ArticleType is virtual, because it represents the one-to-many relation.
For completeness your DbContext:
class MyWarehouse : DbContext
{
public DbSet<Article> Articles {get; set;}
public DbSet<ArticleType> ArticleTypes {get; set;}
}
I'm trying to Join TableA and TableB on ArticleTypeId and basically return everything from Table1 and TypeName from Table2
After you've defined your classes, your query is easy. The easiest method is using the virtual properties.
Use the virtual properties
Requirement Give me the Id and Name of all Articles, each Article with its TypeName.
using (var wareHouse = new MyWareHouse(...))
{
var requestedArticles = wareHouse.Articles.Select(article => new
{
// Select only the Article Properties that you plan to use
Id = article.Id,
Name = article.Name,
TypeName = article.ArticleType.TypeName,
});
// Process the requested Articles before disposing the wareHouse
}
In words: from every Article in the table of Articles fetch the Id, the Name and the one and only TypeName it has.
Entity Framework knows the relation between Articles and ArticleTypes. Because you use the virtual Property Article.ArticleType it knows which join to perform.
Using the virtual properties you can also get each ArticleType together with all Articles that have this ArticleTypes
var constructionArticles = wareHouse.ArticleTypes
.Where(articleType => articleType.TypeName == "construction")
.Select(articleType => new
{
Id = articleType.Id,
TypeName = articleType.TypeName,
// fetch all articles that have this TypeName
Articles = articleType.Articles.Select(article => new
{
Id = article.Id,
Name = article.Name,
// no need to fetch the foreign key, you already got this value
// ArticleTypeId = article.ArticleTypeId,
})
.ToList(),
})
.ToList();
Entity framework knows the relation and will do the proper (Group-)join for you.
Did you notice how natural using the virtual properties feel?
Do the Join yourself
Some people don't want to use the virtual properties, they prefer to do the (Group-)joins themselves.
Use the overload of method Join that has a parameter resultSelector, so you can specify the desired result.
// Join Articles with ArticleTypes
var requestedArticles = wareHouse.Articles.Join(wareHouse.ArticleTypes,
// from every Article take the foreign key
article => articleTypeId,
// from every ArticleType take the primary key
articleType => articleType.Id,
// parameter resultSelector:
// take each article and its one and only matching ArticleType to make one new
(article, articleType) => new
{
Id = article.Id,
Name = article.Name
TypeName = articleType.TypeName,
});
If you have a one-to-many relation, like Schools with their Students, Customers with their Orders, or ArticleTypes with their Articles, use GroupJoin and start at the 'one' side. If you want Students, each Student with the School he attends, use Join, and start at the 'many' side.
var schoolsWithTheirStudents = dbContext.Schools
.Where(school => school.City == ...) // if you don't want all Schools
.GroupJoin(dbContext.Students,
// from every School take the primary key
school => school.Id,
// from every Student take the foreign key to the School he attends
student => student.SchoolId,
// resultSelector: take each Schools with its matching Students to make one ned
(school, studentsWhoAttendThisSchool) => new
{
// Select only the School properties that you plan to use:
Id = school.Id,
Name = school.Name,
Address = school.Address,
...
// studentsWhoAttendThisSchool is a queryable sequence,
// so you can use LINQ on it:
Students = studentsWhoAttendThisSchool.Select(student => new
{
Id = student.Id,
Name = student.Name,
...
})
.ToList(),
});
})

EF Core: Correct way to query data multiple levels deep in related one-to-many entities

I'm trying learn to write efficient Entity Framework queries when data has to be fetched based on multiple joins, including a many-to-many via a junction table. In the following example, I'd like to fetch all States that contain a particular Book.
Let's use a model with the following tables/entities, all linked by navigation properties:
State, City, Library, Book, LibraryBook (junction table for many-to-many relationship between library and book.)
Each State has 1 or more Cities
Each City has 1 or more Libraries
Each Library has many Books & Each Book may exist at more than 1 library.
How can I best return all of the States that contain a particular Book? I'm inclined to think separate queries may work better than 1 large one, but I'm not certain what the best implementation is. I think that getting the LibraryId from the many-to-many relation first in a separate query is probably a good way to start.
So for that:
var bookId = 12;
var libraryIds = _context.LibraryBook.Where(l => l.BookId == bookId).Select(s => s.LibraryId);
If that comes first, I'm uncertain how to best query the next data in order to get the cities which contain each of those LibraryIds. I could use a foreach:
var cities = new List<City>;
foreach(var libraryId in libraryIds)
{
var city = _context.City.Where(c => c.Library = libraryId)
cities.Add(city);
}
But then I'd have to do yet another foreach for the states that contain the city, and this all adds up to a lot of separate SQL queries!
Is this really the only way to go about this? If not, what is a better alternative?
Thanks in advance!
Database management systems are extremely optimized in combining tables and selecting columns from the result. The transport of the selected data is the slower part.
Hence it is usually better to limit the data that needs to be transported: let the DBMS do all the joining and selecting.
For this, you don't need to put everything in one big LINQ statement that is hard to understand (and thus hard to test, reuse, maintain). As long as your LINQ statements remain IQuerayble<...>, the query is not executed. Concatenating several of these LINQ statements is not costly.
Back to your question
If you followed the entity framework conventions, your one-to-many relations and your many-to-many will have resulted in classes similar to the following:
class State
{
public int Id {get; set;}
public string Name {get; set;}
...
// every State has zero or more Cities (one-to-many)
public virtual ICollection<City> Cities {get; set;}
}
class City
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every City is a City in exactly one State, using foreign key:
public int StateId {get; set;}
public virtual State State {get; set;}
// every City has zero or more Libraries (one-to-many)
public virtual ICollection<Library> Libraries {get; set;}
}
Library and Books: many-to-many:
class Library
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every Library is a Library in exactly one City, using foreign key:
public int CityId {get; set;}
public virtual City City {get; set;}
// every Library has zero or more Books (many-to-many)
public virtual ICollection<Book> Books {get; set;}
}
class Book
{
public int Id {get; set;}
public string Title {get; set;}
...
// Every Book is a Book in zero or more Libraries (many-to-many)
public virtual ICollection<Book> Books {get; set;}
}
This is all that entity framework needs to know to recognize your tables, the columns in the tables and the relations between the tables.
You will only need attributes or fluent API if you want to deviate from the conventions: different identifiers for columns or tables, non-default types for decimals, non default behaviour for cascade on delete, etc.
In entity framework, the columns in the tables are represented by the non-virtual properties; the virtual properties represent the relations between the tables.
The foreign key is an actual column in the table, hence it is non-virtual. The one-to-many has virtual ICollection<Type> on the "one" side and virtual Type on the "many" side. The many-to-many has virtual ICollection<...> on both sides.
There is no need to specify the junction table. Entity framework recognizes the many-to-many and creates the junction table for you. If you use database first, you might need to use fluent API to specify the junction table.
But how am I supposed to do the joins without a junction table?
Answer: don't do the (group-)joins yourself, use the virtual ICollections!
How can I best return all of the States that contain a particular Book?
int bookId = ...
var statesWithThis = dbContext.States
.Where(state => state.Cities.SelectMany(city => city.Libraries)
.SelectMany(library => library.Books)
.Select(book => book.Id)
.Contains(bookId);
In words: you have a lot of States. From every State, get all Books that are in all Libraries that are in all Cities in this State. Use SelectMany to make this one big sequence of Books. From every Book Select the Id. The result is one big sequence of Ids (of Books that are in Libraries that are in Cities that are in the State). Keep only those States that have at least one Book.
Room for Optimization
If you regularly need to do similar questions, like: "Give me all States that have a Book from a certain Author", or "Give me all Libraries that have a Book with a certain title", consider to create extension methods for this. This way you can concatenate them as any LINQ method. The extension method creates the query, it will not execute them, so this won't be a performance penalty.
Advantages of the extension method: simpler to understand, reusable, easier to test and easier to change.
If you are not familiar with extension methods, read Extension Methods Demystified
// you need to convert them to IQueryable with the AsQueryable() method, if not
// you get an error since the receiver asks for an IQueryable
// and a ICollection was given
public static IQueryable<Book> GetBooks(this IQueryable<Library> libraries)
{
return libraries.SelectMany(library => library.AsQueryable().Books);
}
public static IQueryable<Book> GetBooks(this IQueryable<City> cities)
{
return cities.SelectMany(city => city.Libraries.AsQueryable().GetBooks());
}
Usage:
Get all states that have a book by Karl Marx:
string author = "Karl Marx";
var statesWithCommunistBooks = dbContext.States.
.Where(state => state.GetBooks()
.Select(book => book.Author)
.Contains(author));
Get all Cities without a bible:
string title = "Bible";
var citiesWithoutBibles = dbContext.Cities
.Where(city => !city.GetBooks()
.Select(book => book.Title)
.Contains(title));
Because you extended your classes with method GetBooks(), it is as if States and Cities have Books. You've seen the reusability above. Changes can be easy, if for instance you extend your database such, that Cities have BookStores. GetBooks can check the libraries and the BookStores. Your change will be in one place. Users of GetBooks(), won't have to change.

How can I get value from .Include() using .ThenInclude()c

I have some users filter in my project and I want to show each user's friends here. UserFrom - who send friendship request, UserTo - who accept it. So I need To know the Id in the code below to choose the opposite, beacuse it will be his friend.
var users = await _context.User
.Where(u => userFilter.Gender != null ?
u.Gender == userFilter.Gender : true)
.Where(u => (userFilter.Languages != null &&
userFilter.Languages.Count() != 0) ?
userFilter.Languages.Any(fl => u.Languages.Any(
ul => ul.LanguageCode == fl &&
LevelInRange(ul, userFilter.MinLevel))) : true)
.Where(u => (userFilter.MaxDistance != null) ?
LocationHelper.GetDistanceBetween((double)u.Longitude, (double)u.Latitude,
longtitude, latitude) <= userFilter.MaxDistance : true)
.Where(u => (userFilter.MaxAge != null) ?
GetAge(u.Birthdate) <= userFilter.MaxAge : true)
.Where(u => (userFilter.MinAge != null) ?
GetAge(u.Birthdate) >= userFilter.MinAge : true)
.Include(u => u.Languages)
.ThenInclude(ul => ul.Language)
.Include(u => u.CreatedEvents)
.Include(u => u.Friends)
.ThenInclude(f => f.UserTo) //The problem is here. How can I get u.Id there
.Include(u => u.Credentials)
.Include(u => u.Hobbies)
.ThenInclude(h => h.Hobby)
.ToListAsync();
Database management systems are optimized for selecting data. One of the slower parts is the transport of the selected data to your process. Hence it is wise to transport only the data that you actually plan to use.
If you have a one-to-many relation, like Schools with their Students, and School 10 has 1000 Students, then every Student of this School will have a foreign key SchoolId with a value 10.
So if you fetch "School [10] with its Students", you already know that every Student of school [10] will have a property SchoolId with a value 10. This value (that you already know) will be transported 1000 times (1001 if you also count the school's primary key). What a waste of processing power!
If you query data using entity framework, always use Select. Only use Include if you want to update the fetched data (change, delete)
Using Select enables you to select only the properties that you want, in the format that you want.
Back to your problem
Alas you forgot to give us your classes. So we'll have to guess it. It seems that a User has zero or more Languages, CreatedEvents, Friends, Hobbies, etc. Some of them will be a one-to-many relation, probably most of them will be a many-to-many relation: a user knows zero or more languages. Every language is spoken by zero or more Users.
If you've followed the entity framework code first conventions, you probably have classes similar to:
class User
{
public int Id {get; set;}
public string Name {get; set;}
// every User has zero or more Hobbies (many-to-many)
public virtual ICollection<Hobby> Hobbies {get; set;}
// every Student has created zero or more events (one-to-many)
public virtual ICollection<CreatedEvent> CreatedEvents {get; set;}
...
}
class Hobby
{
public int Id {get; set;}
public string Name {get; set;}
...
// every Hobby is practised by zero or more Users (many-to-many)
public virtual ICollection<User> Users {get; set;}
}
class CreatedEvent
{
public int Id {get; set;}
public string Name {get; set;}
public DateTime Date {get; set;}
// every event is created by exactly one User (one-to-many, using foreign key)
public int UserId {get; set;}
public virtual User User {get; set;}
}
etc.
In entity framework, the columns of your tables are represented by non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
Hence, a foreign key is non-virtual. The item that the foreign key points to is virtual. If two classes have a virtual ICollection<...> pointing towards each other, entity framework knows that there is a many-to-many relation; if one of the two classes has virtual ICollection<...> while the other has virtual ... then entity framework knows that you intended to design a one-to-many relation.
If you've created your classes properly, especially the virtual ICollections, a query using Select is fairly easy. You seldom have to do a (group-)join anymore. Because you use the virtual properties, entity framework knows that a (group-)join is needed.
var queryUsers = dbContext.User.Where(...).Where(...) ...
.Select(user => new
{
// select only the user properties you really plan to use
Id = user.Id,
BirthDay = user.BirthDay,
// Select the data in the format that you want, for example:
FullName = user.FirstName + user.MiddleName + user.LastName,
// SubCollections:
Languages = user.Hobbies
.Where(hobby => ...) // only if you don't want all this user's hobbies
.Select(hobby => new
{
// again, select only the hobby properties that you plan to use
Id = hobby.Id,
...
// not needed, you already know the value:
// I know, it is probably a many-to-many, but let's suppose it is one-to-many
// UserId = hobby.UserId,
})
.ToList(),
...
});
Now your problem is in property Friends, you can add it to your Select, just like you selected the Hobbies
Friends = user.Friends
.Where(friend => ...) // only if you don't want all Friends
.Select(friend => new
{
// select the Friend properties you actually plan to use:
Id = friend.Id,
Name = friend.Name,
...
})
.ToList(),
// continue the select
IIRC, You can Select() over children with Linq expressions like so for children using .Include().
return _context.User
.Include(a => a.Friends.Select(c => c.UserTo));

Entity Framework Related Data Foreign Keys

I have an entity that consists only of foreign keys of other Entities.
My simplified class looks like this:
DeliverNote
Adress add1 {get; set;}
Adress add2 {get; set;}
I can load adresses by themselves just fine, but I can't load a DeliveryNote, because EF doesn't load the related data by default, I think.
So I saw solutions, mainly with context.notes.Include(dn => dn.Adresses), but I just can't figure out how I tell the note or the adress class how they're related to each other. Basically when I type "dn." nothing shows up.
The simplest, probably working, solution I saw was from microsoft. In the github from this page https://learn.microsoft.com/de-de/ef/core/querying/related-data you can see the Blog and the Post classes. To me the Post class looks flawed though, why would a Post have to know about the Blog it is in? This will mess up the database too in code first solutions. What if the same post is gonna be posted in several blogs?
Most solutions also seem to be lists of some kind, I don't have a list, just simple single objects. 1-1 relationship, I think.
So you have a database with a table of Addresses and a table of DeliveryNotes. Every DeliveryNote has two foreign keys to the Addresses: one From and one To (you call it addr1 and addr2)
If you follow the entity framework code first conventions, you'll have something like this:
class Address
{
public int Id {get; set;}
... // other properties
// every Address has sent zero or more delivery Notes (one-to-many)
public virtual ICollection<DeliveryNote> SentNotes {get; set};
// every Address has received zero or more delivery Notes (one-to-many)
public virtual ICollection<DeliveryNote> ReceivedNotes {get; set};
}
class DeliveryNote
{
public int Id {get; set;}
... // other properties
// every DeliveryNote comes from an Address, using foreign key
public int FromId {get; set;}
public virtual Address FromAddress {get; set;}
// every DeliverNote is sent to an Address, using foreign key:
public int ToId {get; set;}
public virtual Address ToAddress {get; set;}
}
In entity framework the columns of the tables are represented by non-virtual properties. The virtual properties represent the relations between the tables.
Note that the ICollection and FromAddress / ToAddress are virtual and thus not columns into your columns. If desired you can leave them out of your classes. However, if you have these virtual properties, you don't have to do the (Group)Joins yourself.
I can load adresses by themselves just fine, but I can't load a DeliveryNote, because EF doesn't load the related data by default ... I
From this it is not easy to detect what kind of queries you want.
One of the slower parts of database queries is the transport of the selected data from your DBMS to your local process. Hence it is wise to minimize the data being transported.
If you use Include, then the complete object is transported, inclusive the foreign keys and all properties you don't need. If you have a database with Schools and Students, then every Student will have a foreign key to the School he attends. If you ask for a 'School with his 1000 Students' of school with Id 4, using Include, you don't want to transport the foreign key SchoolId a 1000 times, because you already know it will have value 4
In entity framework only use Include if you want to change / update the fetched item, otherwise use Select
Given a bunch of DeliveryNotes, give me some AddressDetails of it:
IQueryable<DeliveryNote> deliveryNotes = dbContext.DeliveryNotes
.Where (deliveryNote => ...) // probably something with Id, or Date, or subject
.Select(deliveryNote => new
{
// select only the delivery note properties you actually plan to use
Subject = deliveryNote.Subject,
DeliveryDate = deliveryNote.DeliveryDate,
...
From = new
{
// select only the From properties you plan to use
Id = deliveryNote.FromAddress.Id,
Name = deliveryNote.FromAddress.Name,
Address = deliveryNote.FromAddress.Address,
...
}
To = new
{
// again: only properties you'll use
Name = deliveryNote.ToAddress.Name,
...
},
});
Entity framework knows the one-to-many relationship and will perform the proper join for you.
Given a bunch of Addresses give me some of the DeliveryNotes they received
var query = dbContext.Addresses
.Where(address => address.City == "New York" && ...)
.Select(address => new
{
// only properties you plan to use
Id = address.Id,
Name = address.Name,
ReceivedNotes = address.ReceivedNotes
.Where(note => note.DeliveryDate.Year == 2018)
.Select(note => new
{
// only properties you plan to use:
Title = note.Title,
...
// no need for this, you know it equals Id
// AddressId = note.FromId,
}),
});
Entity framework knows the one-to-many relationship and will do the proper groupjoin for you.
If you have a one-to-many relationship and you want the "item with its many sub-items", start on the one-side and use the virtual ICollection. If you want the sub-item with the item that it belongs to, start with the many-side and use the virtual property to the one-side
If you define your model as:
public class DeliverNote {
public int Id { get; set; }
public Adress addr1 { get; set; }
public Adress addr2 { get; set; }
}
public class Adress {
public int Id { get; set; }
}
You can then call:
context.notes.Include(dn => dn.addr1).Include(dn => dn.addr2);
Which will include the related data.
Your model doesn't define foreign keys for addr1 or addr2 so EF Core will create shadow properties for you, i.e. columns that exist in the table but not as properties in the c# model.

Group Join and Orderby while maintaining previous query

While working on a problem I realized I need to get query where the data from Table "questions" is reliant on count of Primary key "questions.Id" in another table "Upvotes" where "questionId" is a foreign key to "questions.Id" and can have multiple entries.
So how my system works is I add entries of upvotes to upvote table and I can simply count(particular question id) and total number of questions.
I am stuck with figuring out how to get list of all questions and their respective upvotes.
Example Question Table:
id(pk) question
1 "first quues"
2 "second ques"
3 "third ques"
Example Upvote table:
id(pk) questionid userid(user who upvoted)
1 2 "alpha"
2 2 "charlie"
3 1 "bravo"
4 2 "alpha"
Expected output:
id(question.id) question upvotecount
2 second ques 3
1 first ques 1
3 third ques 0
(Notice the order & count)
Queries I tried so far:
Closest to my output but require storage in separate variable:
var t = query
.Select(x => new
{
question = x.Id,
count = (from upvotes2 in model.Upvotes
where upvotes2.QuestionId == x.Id
select upvotes2).Count()
})
.OrderByDescending(c => c.count);
foreach (var a in t)
Console.WriteLine("" + a);
What I am trying to make it as
query = query
.Select(x => new Question
{
UpvoteCount = (from upvotes2 in model.Upvotes
where upvotes2.QuestionId == x.Id
select upvotes2).Count()
})
.OrderByDescending(c => c.UpvoteCount);
foreach (var a in query)
Console.WriteLine("" + a);
The latter gives me:
System.NotSupportedException: 'The entity or complex type mYpROEJT.DataAccess.CodeFirstModel.Question cannot be constructed in a LINQ to Entities query.
whereas former is close to output which is:
"query" is of type IQueryable<Question>
"Question" is a class generated from Entity and I added a [NotMapped] int UpvoteCount there
{ question = 5, upcount = 2 }
{ question = 3, upcount = 1 }
{ question = 2, upcount = 0 }
{ question = 1, count = 0 }
{ question = 4, count = 0 }
EDIT 1: To add to original post. I want to return list of Questions and an Upvote count along.
So you have a collection of Questions. Every Question has zero or more Upvotes. Every Upvote belongs to exactly one Question, using the foreign key QuestionId. This is a standard one-to-many relationship.
There is also a one-to-many relationship between Users and Upvotes: every User has zero or more Upvotes, every Upvote belongs to exactly one User using a foreign key.
If you use the entity-framework code-first conventions, you will have designed these class similar to the following:
public class Question
{
public int Id {get; set;}
// every Question has zero or more Upvotes:
public virtual ICollection<Upvote> Upvotes {get; set;}
public string QuestionText {get; set;}
... // other properties and relations
}
public class Upvote
{
public int Id {get; set;}
// every Upvote belongs to exactly one Question using foreign key:
public int QuestionId {get; set;}
public virtual Question Question {get; set;}
// every Upvote was done by exactly one User using foreign key:
public int UserId {get; set;}
public virtual User User {get; set;}
... // other properties and relations
}
public class User
{
public int Id {get; set;}
// every User has zero or more Upvotes:
public virtual ICollection<Upvote> Upvotes {get; set;}
... // other properties and relations
}
For completeness tthe DbContext:
public class MyDbContext : DbContext
{
public DbSet<Question> Questions {get; set;}
public DbSet<UpVote> Upvotes {get; set;}
public DbSet<User> Users {get; set;}
}
Because I stuck to the entity framework code first conventions, this is all that entity framework needs to know to detect the one-to-many relationships. It might be that you want different identifiers for your tables or columns. In that case you need to use fluent API or attributes.
Having designed your classes in the proper entity framework way makes your query really easy and intuitive:
using (var dbContext = new MyDbContext())
{
var result = dbContext.Questions // take your table Questions
.Select(question => new // for every question in this table, make one new object
{
Id = question.Id,
Text = question.QuestionText,
UpvoteCount = question.Upvotes.Count,
});
}
Because I use the virtual ICollection<Upvote> Upvotes, entity framework knows that a GroupJoin between the Questions table and the Upvotes table is needed. In Sql this will become an inner join followed by a group by.
If you chose to deviate from the standard entity-framework one-to-many designs, you can't use ICollection.Count, you'll have to do the GroupJoin yourself:
var result = dbContext.Questions.GroupJoin( // GroupJoin Questions with Upvotes
dbContext.Upvotes,
question => question.Id, // from every question take the Id,
upVote => upvote.QuestionId, // from every upvote take the QuestionId,
(question, upvotes) => new // for every question with its matching Upvotes
{ // make one new object
Id = question.Id,
Text = question.QuestionTxt,
UpvoteCount = upvotes.Count(),
});
Finally you want to order by decreasing UpvoteCount:
var finalResult = result.OrderByDescending(joinedItem => joinedItem.UpvoteCount);

Categories