Coming from the world of mighty nHibernate trying to get bidirectional relation to work in Entity Framework Core.
Here are my simplified entities:
public class User
{
public int id { get; set; }
public string username { get; set; }
public ICollection<Login> Logins { get; set; }
}
public class Login
{
public int id { get; set; }
public string email { get; set; }
public User User { get; set; }
}
Here is my mapping:
public DbSet<User> Users { get; set; }
public DbSet<Login> Logins { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.ToTable("users")
.HasMany(x=>x.Logins)
.WithOne(x=>x.User)
.HasForeignKey(x=>x.user_id);
modelBuilder.Entity<Login>()
.ToTable("logins")
.HasOne(d=>d.User)
.WithMany(d=>d.Logins)
.HasForeignKey(e => e.user_id);
}
When I query User, Logins collection is fetched correctly. However when I query Login the User comes null.
var login = DBContext.Logins.Where(x => x.email == email).SingleOrDefault();
// login.User is null
Related
I am trying to return all items in UserDTO from a User given a particular Team. The below code only returns the UserId (since that is what contained in TeamUser). Can this be done in a straightforward way? Or do I have to perform a second lookup on Users once the UserId's are given?
public class User
{
public long UserId { get; set; }
public string? Name { get; set; }
[Required]
[EmailAddress]
public string? Email { get; set; } = default!;
[Required]
public string? CreatedDate { get; set; }
/* relationships */
public virtual ICollection<TeamUser>? Teams { get; set; } //many Teams to many Users
}
public class Team
{
public long TeamId { get; set; }
[Required]
public string? Name { get; set; }
[Required]
public string? CreatedDate { get; set; }
/* relationships to User */
public virtual ICollection<TeamUser>? Users { get; set; } //many Users to many Teams
}
public class UserDTO
{
public long UserId { get; set; }
public string? Name { get; set; }
[Required]
[EmailAddress]
public string? Email { get; set; } = default!;
}
public class TeamUser
{
public long TeamId { get; set; }
public Team? Team { get; set; }
public long UserId { get; set; }
public User? User { get; set; }
}
[HttpGet]
public async Task<ActionResult<IEnumerable<UserDTO>>> GetUsers(long? userId, long? teamId)
{
if(teamId == null)
{
return await _context.Users.Select(x => UserToDTO(x)).ToListAsync();
}
else
{
// get users given teamId
return await _context.TeamUsers
.Include(u => u.User)
.Where(t => t.TeamId == teamId)
.Select(pt => new UserDTO
{
UserId = pt.UserId
})
.ToListAsync();
}
EF Core introduced the new ThenInclude() extension method to load multiple levels of related entities.
As EF Core won't load related properties automatically, so you'll need to explicitly do this, but something like the following should do the trick:
var result = context.Begrip
.Include(x => x.Categories)
.ThenInclude(x => x.category);
Note, intellisense doesn't always work on .ThenInclude at the moment, but the code should still compile even if it gets a red underline.
If you're returning this to the view or an API, you'll likely want to map it to a DTO so you don't have to deal with .Categories[0].category.Name etc.
I have a many-to-many relationship in EF core. I would like to retrieve all the users and roles assigned to them where UserRoles is active (IsActive)
I have three tables User, Role, UserRoles (see below).
According to business requirements, the user can exist without a role. Moreover, the role can exist without a user.
IsActive field in the RoleUsers table determines role is assigned to the user or not. IsActive is also used for soft delete.
Below is my linq query
**return await _context.User
.Include( c=> c.Role)
.ThenInclude(e=> e.UserRoles.Where(u=> u.IsActive)))
.ToListAsync<User>();**
SQL Server query would be something like below
public class User
{
public string UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Role> Roles { get; set; }
public List<UserRoles > UserRoles { get; set; }
}
public class Role
{
public Guid RoleId{ get; set; }
public string Description{ get; set; }
public ICollection<User> Users { get; set; }
public List<UserRoles > UserRoles { get; set; }
}
public class UserRoles
{
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public int Id { get; set; }
public string UserId { get; set; }
public Guid RoleId{ get; set; }
public User User { get; set; }
public Role Role{ get; set; }
public bool IsActive{ get; set; }
}
public DbSet<Role> Role{ get; set; }
public DbSet<User> User { get; set; }
public DbSet<UserRole> UserRole { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(m => m.Roles)
.WithMany(g => g.Users)
.UsingEntity<UserRoles>(
mg => mg.HasOne(prop => prop.Role)
.WithMany(p => p.UserRoles)
.HasForeignKey(prop => prop.RoleId),
mg => mg.HasOne(prop => prop.User)
.WithMany(p => p.UserRoles)
.HasForeignKey(prop => prop.UserId),
mg =>
{
mg.HasKey(prop => new { prop.RoleId, prop.UserId });
mg.Property(prop => prop.CreatedOn).HasDefaultValueSql("GETUTCDATE()");
}
);
base.OnModelCreating(modelBuilder);
}
I am creating API in ASP .NET Core that will retrieve posts with user Id. Post should contain text and Id of a user who posted it.
I have two models Users and Posts and I need help on how to configure this relationship
I want one User to have many posts
Currently my user model contains
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public List<Post> Posts { get; set; }
}
And my Post model
public class Post
{
public int Id { get; set; }
public string Text { get; set; }
}
What is the best way to do this ?
One to many relationships ( User to have many posts).
public class User{
public int Id { get; set; }
public string Username { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Text { get; set; }
//Navigation
public int UserId { get; set; }
public User User{ get; set; }
}
this is your Model Class:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public Virtual List<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Text { get; set; }
public int UserId { get; set; }
public Virtual User User { get; set; }
}
and in your DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// configures one-to-many relationship
modelBuilder.Entity<User>().HasMany(x=>x.Posts).WithRequired(x=>x.User)
.HasForeignKey<int>(s => s.UserId);
}
I'm trying to build a simple helpdesk application. In this app, when a ticket is created and displayed, I want to show the first name of the creating user. I am trying to solve how to do this in the best possible way.
I've extended the ApplicationUser class and added FirstName and LastName columns. I also created two foreign keys in my Tickets table, one for the user who created the ticket and one for the agent gets assigned to that ticket. So when the ticket is displayed, I need to show both creators and agents first name + last name's, instead of their UserId's.
This is my ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Ticket> Users { get; set; }
public ICollection<Ticket> Agents { get; set; }
}
This is my model:
public class Ticket
{
public int Id { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public int Status { get; set; }
public string UserId { get; set; }
public string AgentId { get; set; }
public DateTime Created { get; set; }
public DateTime LastUpdated { get; set; }
public DateTime? Completed { get; set; }
public bool Muted { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser TicketUser { get; set; }
[ForeignKey("AgentId")]
public virtual ApplicationUser TicketAgent { get; set; }
}
This is my DbContext:
public DbSet Tickets { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity()
.HasOne(m => m.TicketUser)
.WithMany(t => t.Users)
.HasForeignKey(m => m.UserId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity()
.HasOne(m => m.TicketAgent)
.WithMany(t => t.Agents)
.HasForeignKey(m => m.AgentId)
.OnDelete(DeleteBehavior.Restrict);
}
This is the controller action to display a specific ticket:
[HttpGet]
public ViewResult Tickets(int id)
{
TicketDetailsViewModel ticketDetailsViewModel = new TicketDetailsViewModel()
{
Ticket = _ticketRepo.GetTicket(id)
};
return View(ticketDetailsViewModel);
}
This is my viewmodel:
public class TicketDetailsViewModel
{
public Ticket Ticket { get; set; }
}
Now, I can display the full name in my view if I do this:
#inject UserManager userManager;
#{
var ticketUser = (await userManager.FindByIdAsync(Model.Ticket.UserId)).FirstName + " " + (await userManager.FindByIdAsync(Model.Ticket.UserId)).LastName;
}
But I am not sure if this is a good way to do it. I'd like to learn what is the best way to achive this.
Thank you very much.
You can define a _fullname in your ApplicationUser , and then if firstname and lastname both exist, you can directly call Fullname, like:
public class ApplicationUser : IdentityUser
{
private string _fullName; //new property
public string FirstName { get; set; }
public string LastName { get; set; }
[NotMapped]
public string FullName
{
get
{
return _fullName = this.FirstName + "." + this.LastName;
}
set
{
_fullName = value;
}
}
public ICollection<Ticket> Users { get; set; }
public ICollection<Ticket> Agents { get; set; }
}
In view, just call FullName:
#{
var ticketUser = (await userManager.FindByIdAsync(Model.Ticket.UserId)).FullName;
}
In these scenarios I usually prefer to go with an extension method instead of an additional property like proposed by user Jerry Cai, the model remains lighter and cleaner imho:
public static class ApplicationUsersExtensions
{
public static string GetFullname(this ApplicationUser user)
{
return $"{user.FirstName}.{user.LastName}";
}
}
I'v got a backend on Asp.Net Core. Structure of the database looks that:
User - the basics information about user: login, password etc.
Profile - this entity is connected to the"User" one to one relation
Profile photos- each of the users has a own collection of photos.
This entity is connected to the "Profile"
Here is the "User" entity:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public Profile Profile { get; set; }
}
Then Profile:
public class Profile
{
[ForeignKey("User")]
public int Id { get; set; }
public string BannerImageUrl { get; set; }
public string ProfileImageUrl { get; set; }
public string ShortDescription { get; set; }
public string Description { get; set; }
public User User { get; set; }
public ICollection<ProfilePhotos> ProfilePhotos { get; set; }
}
And "ProfilePhotos":
public class ProfilePhotos
{
public int Id { get; set; }
public string ImageUrl { get; set; }
public int ProfileId { get; set; }
public Profile Profile { get; set; }
}
I want to get all profile photos so I created a endpoint to to that:
[HttpGet("{username}/photos")]
public IActionResult GetPhotos(string username)
{
var profilePhotos = _profileService.GetAllPhotos(username);
var model = _mapper.Map<IList<ProfilePhotosModel>>(profilePhotos);
return Ok(model);
}
To get all photos I use a method from "profileService":
public IEnumerable<ProfilePhotos> GetAllPhotos(string username)
{
return _context.ProfilePhotos.Include(a=>a.Profile).ThenInclude(b=>b.User).Where(x => x.Profile.User.Username == username);
}
On response I want to get a id of photo, photoUrl and username so I mapped my profile photos to "ProfilePhotosModel"
public class ProfilePhotosModel
{
public int Id { get; set; }
public string ImageUrl { get; set; }
public string Username { get; set; }
}
but unfortunately on response I only get Id and photoUrl. The username is null :(
What am I doing wrong?
You could add custom mapping for the Username property.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ProfilePhotos, ProfilePhotosModel>()
.ForMember(m => m.Username, exp => exp.MapFrom(p => p.Profile.User.Username));
});