I am trying to get each user with its projects using entity framework core in a web api project in the controller with linq
I tried with this query but it gave me an empty object
var users = _context.Users.Include(x => x.userProjects.Select(up => up.UserId == x.Id)).ToListAsync();
I also tried this one and got the same result
var users = _context.Users.Include(x => x.userProjects.Where(up => up.UserId == x.Id)).ToListAsync();
This is the User class
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<UserProject> userProjects { get; set; }
}
This is the Project class
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<User> Users { get; set; }
public ICollection<UserProject> UserProjects { get; set; }
}
and this is the UserProject class
public class UserProject
{
[ForeignKey("User")]
public int UserId { get; set; }
public User User { get; set; }
[ForeignKey("Project")]
public int ProjectId { get; set; }
public Project Project { get; set; }
}
I want to get a json with each user and an array of its projects
For multiple level of includes, you need ThenInclude.
var users = _context.Users
.Include(x => x.userProjects)
.ThenInclude(y => y.Project)
.ToListAsync();
var users = _context.Users.Include(u => u.userProjects)
.ThenInclude(up => up.Projects))
.Where(u => u.UserId == Id)
.ToListAsync();
What happens here:
You will retrieve all users which have UserId = yourId and also all the UserProjects and Projects of those users.
Example of code which shows you you can access all the projects of the first returned user:
var projectsForFirstUser = users.First().Select(x => x.UserProjects).Select(x => x.Project).ToList();
EDIT: Modified to ThenInclude because of EF Core.
Related
In an ASP.NET core (dotnet 6) web application, we have a small user management functionality in which a user can be assigned to a specific site (physical location) with certain rights. A user can be assigned to one or more sites and a site can also be assigned to one or more users, thus the many to many relationship. We have the following classes:
public class User
{
public int ID { get; set; }
public string SamAccountName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int GroupID { get; set; }
public bool IsEnabled { get; set; }
public ICollection<UserSite> UserSites { get; set; }
}
public class Site
{
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public ICollection<UserSite> UserSites { get; set; }
}
The many to many relationship is described in a join table with fluent API:
public class UserSite
{
public int UserID { get; set; }
public User User { get; set; }
public int SiteID { get; set; }
public Site Site { get; set; }
}
// Fluent API for UserSite table
builder.HasKey(us => new { us.UserID, us.SiteID });
builder.HasOne(us => us.User)
.WithMany(us => us.UserSites)
.HasForeignKey(us => us.UserID)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(us => us.Site)
.WithMany(us => us.UserSites)
.HasForeignKey(us => us.SiteID)
.OnDelete(DeleteBehavior.Cascade);
These classes result in the following DTO's:
public class UserDTO
{
public int ID { get; set; }
public string SamAccountName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int GroupID { get; set; }
public bool IsEnabled { get; set; }
public IList<SiteDTO> Sites { get; set; }
}
public class SiteDTO
{
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
What we want to know is, what is the best way to create a new user or update an existing user? Just to clarify, the data in the Sites table exists already and is static. What I mean by that is that when creating a new user or updating an existing one, a user can be assigned to one or more existing sites, but no new site is created in the process.
We already tried some things that did work but we are not sure if this is really the best way to this or if there is a far better way to do it. Here is what we tried:
The create method
// Create method
public IActionResult CreateUser(UserDTO userDTO)
{
if (ModelState.IsValid)
{
User user = _mapper.Map<User>(userDTO);
user.IsEnabled = true;
AttachSitesToUser(user.ID, user.UserSites);
_context.Users.Add(user);
_context.SaveChanges();
userViewModel = _mapper.Map<UserViewModel>(user);
}
return new JsonResult(new[] { userDTO });
}
The update method:
// Update method
public IActionResult UpdateUser(UserDTO userDTO)
{
if (ModelState.IsValid)
{
User user = _context.Users
.Include(u => u.Sites)
.ThenInclude(s => s.Site)
.Single(u => u.ID == userDTO.ID);
_mapper.Map(userViewModel, user);
AttachSitesToUser(user.ID, user.UserSites);
_context.Entry(user).State = EntityState.Modified;
_context.SaveChanges();
}
return new JsonResult(new[] { userDTO });
}
The helper method AttachSitesToUser:
// Helper method that adds the user ID and attaches the site(s) to the context
private void AttachSitesToUser(int userID, IEnumerable<UserSite> userSites)
{
foreach (UserSite userSite in userSites)
{
userSite.UserID = userID;
if (Context.Entry<Site>(userSite.Site).State == EntityState.Detached)
{
Context.Sites.Attach(userSite.Site);
Context.Entry<Site>(userSite.Site).State = EntityState.Unchanged;
}
}
}
As you can see, for the moment we have to loop through the UserSites list in the User entity and attach the sites the user was assigned to manually to the context. Is there no better way to do this or is this the official best practice?
I have a table called Ad (advertisement):
public class Ad
{
public long AdId { get; set; }
public long UserId { get; set; } // <-- this is advertiser not currentUser
public string Description { get; set; }
}
Users can add zero or more advertisements to their favourites list, so I have created another table called Favourite:
public class Favourite
{
public long FavouriteId { get; set; }
public long AdId { get; set; }
public long UserId { get; set; }
}
And I have a table called User:
public class User
{
public long UserId { get; set; }
public string Name { get; set; }
}
This is how I retrieve an ad, given currentUserId and adId:
public TEntity GetAd(long currentUser, long adId)
{
return Context.Ad
.Where(r.AdId == adId)
// include a flag indicating the pair (adId and currentUserId) exist in Favouries table
.FirstOrDefault();
}
What I want to include in the result is a flag, indicating if the ad is added to the favourites list or not?
In Entity Framework we have navigation properties. You can define navigation property on child table for the parent table row. For you case you can change your child entity like following:
public class Favourite
{
public long FavouriteId { get; set; }
public long AdId { get; set; }
public long UserId { get; set; }
public virtual Ad Ad { get; set; }
}
and now the Entity Framework should take care of populating it for you and you can access the Ad related row of Favourite like :
return Context.Ad
.Where(r => r.UserId == userId && r.AdId == adId)
.Select(x => x.Ad.Description);
But in your case, You can write a query to know if the ad is favourite or not:
return Context.Ad
.Where(r.AdId == adId)
Select(x => new
{
ad = x,
IsFavourite = Context.Favourite.Any(y=> y.AdId = adId
&& y.UserId = currentUserid))
.FirstOrDefault();
you can create a Domain View Model and map it to that :
public UserFavouriteAd
{
public Ad Ad { get; set; }
public IsFavourite { get; set; }
}
and populate it:
return Context.Ad
.Where(r.AdId == adId)
.ToList()
Select(x => new UserFavouriteAd
{
ad = x,
IsFavourite = Context.Favourite.Any(y=> y.AdId = adId
&& y.UserId = currentUserid))
.FirstOrDefault();
public object GetAd(long currentUser, long adId)
{
return from a in Context.Favourite
where a.UserId == currentUser
select new {
flag = a.AdId != null
a.FavouriteId,
//etc
};
}
In continuation of yesterday's post
Two Entities
public class Realtor
{
public Realtor()
{
Guid = Guid.NewGuid();
Registration = DateTime.Now;
}
public int Id { get; set; }
public Guid Guid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Registration { get; set; }
public int SubdivId { get; set; }
public Subdiv Subdiv { get; set; }
}
public class Subdiv
{
public Subdiv()
{
Created = DateTime.Now;
}
public int Id { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public List<Realtor> Realtors { get; set; }
}
I spend test
I added one Subdiv (TOSTER TM) and received his ID
Next, I add a Realtor and I push Subdiv property found on the ID, the newly created TOSTER TM
Realtor.Subdiv is an object of type Subdiv. OK.
Then I try to select from the base the newly added Realtor.
Realtor.Subdiv = null OMG!!
We get Subdiv object, which is lacking in Realtor above and see his List<Realtor> = null
Please help in solving this problem.
Try this:
Relator rl = Context.Relators.Include(r => r.Subdiv).First(s => s.Id == id);
Now you can access to Subdiv property
For more related date you can call Include Methods more times:
Relator rl = Context.Relators
.Include(r => r.Subdiv)
.Include(r => r.AnotherRel)
.First(s => s.Id == id);
For Entities with multiple levels in depth:
If Subdir is a collection
Relator rl = Context.Relators
.Include(r => r.Subdiv)
.ThenInclude(sub => sub.SecondLevelDepth)
.First(s => s.Id == id);
if Subdir is an Entity
Relator rl = Context.Relators
.Include(r => r.Subdiv.Select(s => s.SecondLevelDepth)
.First(s => s.Id == id);
The problem is not with saving the related data (it should be saved correctly, you could check that inside the database), but loading it.
EF Core currently does not support lazy loading, so in order to get the related data you need to explicitly request it (the so called eager loading):
Realtor rl = context.Realtors.Include(r => r.Subdiv).First(r => r.Id == id);
For more info, see EF Core: Loading Related Data.
Entity Framework core allows to save related entities, you need to define Fluent API settings for both Master and detail table
public class Order
{
public int Id { get; set; }
public int AddressId { get; set; }
public string DeliveryNotes { get; set; }
public int PurchaseOrderNo { get; set; }
public virtual ICollection<OrderItem> Items { get; set; }
}
public class OrderItem
{
public int Id { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal UserPrice { get; set; }
public string Comment { get; set; }
[ForeignKey("OrderId ")]
public int OrderId { get; set; }
public virtual Order Order { get; set; }
}
Inside the DBConext OnModelCreating method , define the relation like, it will make sure when you have order object, its related or child objects i.e Enrolments will also be saved
modelBuilder.Entity<Order>()
.HasMany(c => c.Items)
.WithOne(e => e.Order);
modelBuilder.Entity<OrderItem>().Ignore(x => x.Order);
Now your code should look like this
Order _order = new Order{ AddressId = 1, DeliveryNotes ="some notes", PurchaseOrderNo =1};
_order.Items = new List< OrderItem>();
_ordert.Items.add(new OrderItem{ ProductName =”Laptop”, Quantity =1, UserPrice =1500.00, Comment =”some testing comments”});
repository.order.insert(_order);
repository.save();
public class Module
{
public int id { get; set; }
public string moduleName { get; set; }
//navigation property
public virtual HashSet<Policy> policies { get; set; }
}
public class Policy
{
public int id { get; set; }
//foreign keys
public int subscriberId { get; set; }
//navigation properties
public virtual Subscriber subscriber { get; set; }
}
public class Subscriber
{
public int id { get; set; }
public string name { get; set; }
public int subscriptionId { get; set; }
// Navigation property
public virtual HashSet<Policy> policies { get; set; }
}
I have 3 related objects.
Module - Policy - Subscriber
A module has multiple policies
A policy has one subscriber
I need to list all the policies and subscribers under a certain module in JSON format. Due to the posts that I found on web I created this query:
return db.modules
.Where(m => m.id == id)
.Include (m => m.policies.Select(p => p.subscriber))
.Select(m => new {
m.id,
m.moduleName,
m.policies
}) ;
This only gives the result below. As you can see the details of Subscriber entity under policies are not present (NULL) :( What is wrong?
[{"id":1,"moduleName":"module1",
"policies":[{"id":1,"subscriberId":1,"subscriber":null}]}]
Since you are using dynamics in your Select method, you have to build it all out like this:
return db.modules
.Where(m => m.id == id)
.Include (m => m.policies.Select(p => p.subscriber))
.Select(m => new {
m.id,
m.moduleName,
policies = m.policies.Select(p => new
{
p.id,
p.subscriberId,
subscriber = new
{
p.subscriber.id,
p.subscriber.name,
p.subscriber.subscriptionId,
}
}
});
I typically use real Dto classes so if the Dto ever needs to be updated, refactoring will work properly. I would also consider using a DtoFactory to handle the construction, but you could do it with linq like this:
public class ModuleDto
{
public int id { get; set; }
public string moduleName { get; set; }
public IEnumerable<PolicyDto> policies { get; set; }
}
public class PolicyDto
{
public int id { get; set; }
public int subscriberId { get; set; }
public SubscriberDto subscriber { get; set; }
}
public class SubscriberDto
{
public int id { get; set; }
public string name { get; set; }
public int subscriptionId { get; set; }
}
...other code here...
return db.modules
.Where(m => m.id == id)
.Include (m => m.policies.Select(p => p.subscriber))
.Select(m => new ModuleDto {
m.id,
m.moduleName,
policies = m.policies.Select(p => new PolicyDto
{
p.id,
p.subscriberId,
subscriber = new SubsciberDto
{
p.subscriber.id,
p.subscriber.name,
p.subscriber.subscriptionId,
}
}
});
It gets a little messy to read the linq statement. This is why I typically use a DtoFactory to generate the Dtos from the models.
I have the following model:
public class User
{
public virtual int Id { get; set; }
public virtual string Username { get; set; }
public virtual string Password { get; set; }
public virtual string Email { get; set; }
public IList<SubmissionNote> Notes { get;set; }
}
public class SubmissionNote
{
public virtual Int64 Id { get; set; }
public virtual string Text { get; set; }
public virtual DateTime CreatedOn { get; set; }
public virtual User Creator { get; set; }
public virtual Submission BelongsTo { get; set; }
}
public class Submission
{
public virtual Int64 Id { get; set; }
public virtual DateTime HappenedOn { get; set; }
public virtual int StatusCode { get; set; }
}
I would like to write a query that brings all the Submissions whose last note was entered by some user or a list of users (identified by their names). I would appreciate your help for building this query using NHibernate QueryOver API. I have been able to create a query to bring all the last notes as follows:
SubmissionNote alias = null;
var lastNote = session.QueryOver<SubmissionNote>()
.SelectList(list => list.
SelectGroup(x => x.BelongsTo).WithAlias(() => alias.BelongsTo).
SelectMax(x => x.CreatedOn).WithAlias(() => alias.CreatedOn).
Select(x => x.Creator).WithAlias(() => alias.Creator).
Select(x => x.Id).WithAlias(() => alias.Id).
Select(x => x.Text).WithAlias(() => alias.Text))
.TransformUsing(Transformers.AliasToBean<SubmissionNote>())
.List();
Can I use the specified query as a subquery to produce the required result? Or do you suggest another solution?
I would try something like this with a subquery (not tested though)
SubmissionNote subNoteAlias = null, subNoteAliasMax =null;
User userAlias = null;
String[] userNames = new[]{"John","Joe"};
var subquery =
QueryOver.Of(() => subNoteAliasMax)
.Where(subNote => subNote.BelongsTo.Id == subNoteAlias.BelongsTo.Id)
.Select(Projections.Max<SubmissionNote>(subNote => subNote.CreatedOn));
var result = session
.QueryOver<SubmissionNote>(() => subNoteAlias)
.WithSubquery.WhereProperty(subNote=>subNote.CreatedOn).Eq(subquery)
.JoinQueryOver(
subNote=>subNote.Creator
,()=>userAlias
,JoinType.InnerJoin
,Restrictions.In(Projections.Property(() => userAlias.Username ), userNames))
.List();
The generated SQL query would (very) roughly look like :
SELECT SubmissionNote.*
FROM SubmissionNote INNER JOIN User
ON SubmissionNote.creatorId = User.userId
AND User.userName in ('john','joe')
WHERE SubmissionNote.CreatedOn = (SELECT MAX(CreatedOn) FROM SubmissionNote snAlias
WHERE snAlias.SubmissionId = SubmissionNote.SubmissionId)
Hope this will help. There might be issues with the subquery returning a date instead of an id. LMK and I will give it a look.