Group Join and Orderby while maintaining previous query - c#

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);

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(),
});
})

How to query IQueryable with Include - ThenInclude?

In .NET Core 2.2 I'm stuck with filtering IQueryable built as:
_context.Ports.Include(p => p.VesselsPorts)
.ThenInclude(p => p.Arrival)
.Include(p => p.VesselsPorts)
.ThenInclude(p => p.Departure)
.OrderBy(p => p.PortLocode);
in many-to-many relation. And the entity models are such as:
public class PortModel
{
[Key]
public string PortLocode { get; set; }
public double? MaxKnownLOA { get; set; }
public double? MaxKnownBreadth { get; set; }
public double? MaxKnownDraught { get; set; }
public virtual ICollection<VesselPort> VesselsPorts { get; set; }
}
public class VesselPort
{
public int IMO { get; set; }
public string PortLocode { get; set; }
public DateTime? Departure { get; set; }
public DateTime? Arrival { get; set; }
public VesselModel VesselModel { get; set; }
public PortModel PortModel { get; set; }
}
Based on this this SO answer I managed to create LINQ like that:
_context.Ports.Include(p => p.VesselsPorts).ThenInclude(p => p.Arrival).OrderBy(p => p.PortLocode)
.Select(
p => new PortModel
{
PortLocode = p.PortLocode,
MaxKnownBreadth = p.MaxKnownBreadth,
MaxKnownDraught = p.MaxKnownDraught,
MaxKnownLOA = p.MaxKnownLOA,
VesselsPorts = p.VesselsPorts.Select(vp => vp.Arrival > DateTime.UtcNow.AddDays(-1)) as ICollection<VesselPort>
}).AsQueryable();
BUT what I need is to find all port records, where:
VesselsPorts.Arrival > DateTime.UtcNow.AddDays(-1) quantity is greater than int x = 5 value (for the example). And I have no clue how to do it :/
Thanks to #GertArnold comment, I ended up with query:
ports = ports.Where(p => p.VesselsPorts.Where(vp => vp.Arrival > DateTime.UtcNow.AddDays(-1)).Count() > x);
When using entity framework people tend to use Include instead of Select to save them some typing. It is seldom wise to do so.
The DbContext holds a ChangeTracker. Every complete row from any table that you fetch during the lifetime of the DbContext is stored in the ChangeTracker, as well as a clone. You get a reference to the copy. (or maybe a reference to the original). If you change properties of the data you got, they are changed in the copy that is in the ChangeTracker. During SaveChanges, the original is compared to the copy, to see if the data must be saved.
So if you are fetching quite a lot of data, and use include, then every fetched items is cloned. This might slow down your queries considerably.
Apart from this cloning, you will probably fetch more properties than you actually plan to use. Database management systems are extremely optimized in combining tables, and searching rows within tables. One of the slower parts is the transfer of the selected data to your local process.
For example, if you have a database with Schools and Students, with the obvious one to many-relation, then every Student will have a foreign key to the School he attends.
So if you ask for School [10] with his 2000 Students, then every Student will have a foreign key value of [10]. If you use Include, then you will be transferring this same value 10 over 2000 times. What a waste of processing power!
In entity framework, when querying data, always use Select to select the properties, and Select only the properties that you actually plan to use. Only use Include if you plan to change the fetched items.
Certainly don't use Include to save you some typing!
Requirement: Give me the Ports with their Vessels
var portsWithTheirVessels = dbContext.Ports
.Where(port => ...) // if you don't want all Ports
.Select(port => new
{
// only select the properties that you want:
PortLocode = port.PortLoCode,
MaxKnownLOA = port.MaxKnownLOA,
MaxKnownBreadth = prot.MaxKnownBreadth,
MaxKnownDraught = ports.MaxKnownDraught,
// The Vessels in this port:
Vessels = port.VesselsPort.Select(vessel => new
{
// again: only the properties that you plan to use
IMO = vessel.IMO,
...
// do not select the foreign key, you already know the value!
// PortLocode = vessle.PortLocode,
})
.ToList(),
});
Entity framework knows your one-to-many relation, and knows that if you use the virtual ICollection that it should do a (Group-)Join.
Some people prefer to do the Group-Join themselves, or they use a version of entity framework that does not support using the ICollection.
var portsWithTheirVessels = dbContext.Ports.GroupJoin(dbContext.VesselPorts,
port => port.PortLocode, // from every Port take the primary key
vessel => vessel.PortLocode, // from every Vessel take the foreign key to Port
// parameter resultSelector: take every Port with its zero or more Vessels to make one new
(port, vesselsInThisPort) => new
{
PortLocode = port.PortLoCode,
...
Vessels = vesselsInThisPort.Select(vessel => new
{
...
})
.ToList(),
});
Alternative:
var portsWithTheirVessels = dbContext.Ports.Select(port => new
{
PortLocode = port.PortLoCode,
...
Vessels = dbContext.VesselPorts.Where(vessel => vessel.PortLocode == port.PortLocode)
.Select(vessel => new
{
...
}
.ToList(),
});
Entity framework will translate this also to a GroupJoin.

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.

Implement a "join" with a variety of tables in Entity Framework

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 ...

Categories