EF Core Many-to-Many hide pivot - c#

I'm still learning .NET Core 2.1. I'm working on a Web API where I use EF Core. Currently I'm working on a many to many relationship between Users and Roles. I wanted to hide the pivot, but it ended out being a bit hacky i think, so I wanted to see what I could do to improve it.
I started out with something like this:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
public class Role
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
public class UserRole
{
public int UserId { get; set; }
public int RoleId { get; set; }
}
That works just fine, I then wanted to add an IEnumerable<Role> to the user for easier accessibility and for a prettier JSON output. I found an article online which did that like this:
public class User
{
// All of the previous code
[NotMapped]
public virtual IEnumerable<Role> Roles => UserRoles.Select(x => x.Role);
}
I can then get users and roles:
_context.Users.Include(x => x.UserRoles).ThenInclude(y => y.Role)
The thing is, sometimes, I only wish to get the users without the roles:
_context.Users
That makes the program crash, as UserRoles is null, then .Select(x => x.Role) will fail.
My fix to the User class was the following:
public class User
{
public virtual IEnumerable<Role> Roles
{
get
{
if (UserRoles == null) return null;
return UserRoles.Select(x => x.Role);
}
}
}
But to me, that is a really hacky and ugly solution to the problem. I just don't know how I can simplify it. I tried doing something like
public virtual IEnumerable<Role> Roles => UserRoles.Select(x => x?.Role);
I want to have something just as simple as the above line, but it should actually work as intended.

Try this:
public virtual IEnumerable<Role> Roles => UserRoles?.Select(x => x.Role);

I always do initialize any collection in empty constructor ( for EF ) and always call it from any other. As an example:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
public IEnumerable<Role> Roles => UserRoles.Select(x => x.Role);
public User()
{
this.UserRoles = new List<UserRole>();
}
public User(string name)
:this()
{
}
}
Whenever you will include the roles collection will be fill else you will always have empty collection so any operation wont fail.
Also you do not neet [NotMapped] attribute since the property is readonly and EF will know that

Related

How to Map Twitter follower/following type of relation in EF Core 5

How do you configure something similar to Twitter Following and Follower type of relationship using EF Core 5 with the Fluent API? I tried various different ways of configuring it and the only few ways I was able to get it to work is if I ignored the navigation properties on the User entity. I am currently migrating my code from EF Core 2.1 to 5. The following configuration worked earlier. (Not sure if it is misconfigured)
public class User
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<UserFollower> Followers { get; set; }
public ICollection<UserFollower> Following { get; set; }
}
public class UserFollower
{
public long UserId { get; set; }
public User User { get; set; }
public long FollowedById { get; set; }
public User FollowedBy { get; set; }
}
public class UserFollowerConfiguration : IEntityTypeConfiguration<UserFollower>
{
public void Configure(EntityTypeBuilder<UserFollower> builder)
{
builder.HasKey(p => new { p.UserId, p.FollowedById });
builder.HasOne(p => p.User)
.WithMany(i => i.Followers)
.HasForeignKey(i => i.UserId);
builder.HasOne(p => p.FollowedBy)
.WithMany(i => i.Following)
.HasForeignKey(i => i.FollowedById);
}
}
This configuration throws an error when saving to the database.
SqlException: Violation of PRIMARY KEY constraint 'PK_UserFollower'.
Cannot insert duplicate key in object 'dbo.UserFollower'. The duplicate key value is (111, 111).
Even when trying to directly add to the DbContext and calling SaveChanges() on it.
Context.Add(new UserFollower() {UserId = 222, FollowedById = 111});
What is the recommended way of mapping such a relationship with EF Core 5? Note that I do need to access the UserFollowers table without going through the Navigation properties of the User.
Edit #1
The following is the OnModelCreating() for the DbContext
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfigurations(typeof(DbContext).Assembly);
/*few configurations unrelated to UserFollower entity*/
}
User entity has the following configuration,
builder.HasKey(i => i.Id);
builder.Property(i => i.Id).ValueGeneratedOnAdd();
Try configuring it like this.
builder.Entity<User>().HasMany(s => s.Followers)
.WithOne(f => f.FollowedBy);
builder.Entity<User>().HasMany(s => s.Following)
.WithOne(f => f.);
Also, The PK is missing for the UserFollower table, I don't know if an Id is being generated somehow somewhere. If not, maybe this is why it's trying to wrongly use FollowedById as key, but define an Id for the UserFollower table and see.
public class UserFollower
{
public long Id {get;set;}
public long UserId { get; set; }
public User User { get; set; }
public long FollowedById { get; set; }
public User FollowedBy { get; set; }
}
Even if this works, I would recommend you change the structure of your model, it looks ambigous for the twitter requirements you described. If I query Userfollowers
var userFollowers = _context.UserFollowers.ToList();
For each result in the list, there is no way for me to tell if the user is following or being followed. You could change your models to these ones;
public class User
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<UserFollower> Followers { get; set; }
public ICollection<UserFollowing> Following { get; set; }
}
public class UserFollower
{
public long UserId { get; set; }
public User User { get; set; }
public long UserFollowingMeId { get; set; }
public User UserFollowingMe { get; set; }
}
public class UserFollowing
{
public long UserId { get; set; }
public User User { get; set; }
public long UserIAmFollowingId { get; set; }
public User UserIAmFollowing { get; set; }
}
This way, everybody knows when they check the UserFollowings table, the UserId is the Id of the person that is following and vice versa for the UserFollowers table. If I had an Id of 8 in the system, I can query my followers and people I follow like this;
var myFollowers = _context.UserFollowers.Where(UserId = 8);
var peopleIFollow = _context.UserFollowing.Where(UserId = 8);

Object is null when configuring one to one relationship with fluent api

I have three classes: Role, Permission and RolePermission(role permission is the third table in a many to many relationship)
public class Role : Entity
{
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<RolePermission> RolePermissions { get; set; }
}
public class Permission : Entity
{
public string Name { get; set; }
public virtual ICollection<RolePermission> RolePermissions { get; set; }
}
public class RolePermission : Entity
{
public int RoleId { get; set; }
public int PermissionId { get; set; }
public Permission Permission { get; set; }
public Role Role { get; set; }
}
Then I used fluentAPI in order to configure relationship:
For Role:
HasMany(role => role.RolePermissions)
.WithRequired(rolePermission => rolePermission.Role)
.HasForeignKey(rolePermission => rolePermission.RoleId);
For Permission:
HasMany(permission => permission.RolePermissions)
.WithRequired(rolePermission => rolePermission.Permission)
.HasForeignKey(rolePermission => rolePermission.PermissionId);
For RolePermission:
HasRequired(rolePermission => rolePermission.Permission)
.WithMany(permission => permission.RolePermissions)
.HasForeignKey(rolePermission => rolePermission.PermissionId);
HasRequired(rolePermission => rolePermission.Role)
.WithMany(role => role.RolePermissions)
.HasForeignKey(rolePermission => rolePermission.RoleId);
The problem is that only Role object is populated.
The code in this question pertains to setting up a relationship. The reported issue in this question pertains to related data not being loaded automatically. These are two different things that have little to do with one another.
Did you miss an Include somewhere? Have you accessed (and therefore lazily loaded) the Role nav prop, but not the Permission nav prop? I would like to see the code starting from where you launch the query up to where you inspect this object (as per your screenshot)
You responded with the requested code:
var user = _userRepository
.FirstOrDefaultAsync(us => us.Email == email);
var userPermissions =
user.UserRoles
.First()
.Role
.RolePermissions
.Select(rp => rp.Permission)
.ToList();
If you insert an Include() statement in your query, you will see that the Permission will actually be fetched correctly.
I am not quite sure which object you're inspecting. The screenshot tells me you're looking at a RolePermission, but the posted code suggests that you fetch a list of Permission objects.
Regardless, you seem to already have fixed it using an Include:
Mihai Alexandru-Ionut #Flater, yes, I have to use include and the problem is solved. This is the solution, so please post it as an answer in order to accept it.
Id Property is missing for both Role and Permission tables. When you say RoleId property in RolePermission table, EF looks for Id Property in Role table.
Update your Role and Permission tables like this and give a try:
public class Role : Entity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<RolePermission> RolePermissions { get; set; }
}
public class Permission : Entity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<RolePermission> RolePermissions { get; set; }
}

Entity Framework Many-Too-Many Not Populating Results

I'm trying to map a many-too-many relationship in C# EF with code first migrations.
The issue that I'm having is that no results are being populated from the related table. Take this piece of code for example, _role contains no permissions whereas it should contain 4 results:
foreach (Role _role in _dbContext.Roles) //_dbContext.Roles.Count = 2
{
foreach(Permission _permission in _role.Permissions) //_role.Permissions.Count = 0
{
//Do something with each _permission
}
}
The classes look like this:
[Table("Roles")]
public partial class Role
{
public Role()
{
Permissions = new HashSet<Permission>();
}
[Key]
public int RoleId { get; set; }
public string RoleName { get; set; }
public ICollection<Permission> Permissions { get; set; }
}
[Table("Permissions")]
public partial class Permission
{
public Permission()
{
Roles = new HashSet<Role>();
}
[Key]
public int PermissionId { get; set; }
public string PermissionName { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
And finally the Fluent API:
modelBuilder.Entity<Permission>()
.HasMany(e => e.Roles)
.WithMany(e => e.Permissions)
.Map(m => m
.ToTable("_Lnk_Role_Permission")
.MapLeftKey("PermissionId")
.MapRightKey("RoleId"));
After inspecting the database, the tables, keys and data are all in order and manually adding and querying data produces the correct results. When trying to access the data, roles are present but no permissions.
Can anybody see where it could be going wrong?
I think you are looking for the Include statement.
foreach (var role in _dbContext.Roles.Include(x => x.Permissions))
{
...
}
The virtual keyword is good practice and plays a role in how the data is loaded. If you aren't careful, you can end up doing 1+N queries (1 for the Role object + N more for each Permission). The Include statement tells EF to effectively join the tables in the DB query so that the data is available in memory for handling your nested for loops.
Apologies for linking elsewhere, but MSDN links should be here for a while. This is a pretty good read to understand the implications of virtual/non-virtual and with/without the Include statement:
https://msdn.microsoft.com/en-us/data/jj574232.aspx
Well after hours of head scratching, I missed out Virtual from the ICollection. The modified class looks like so:
public partial class Role
{
public Role()
{
Permissions = new HashSet<Permission>();
}
[Key]
public int RoleId { get; set; }
public string RoleName { get; set; }
public Virtual ICollection<Permission> Permissions { get; set; } //Notice the virtual?!
}

The number of elements in ICollection is zero in many-to-many relationship

I have a database made with Entity Framework. I have two tables Users and Advertisments and the relationship between them is Many-to-Many. Everything is working fine except when I want to return the number of ICollection in class Users.
[Table("Advertisments")]
public class Advertisment
{
public Advertisment()
{
Users = new HashSet<User>();
}
[Key]
public int AdvertismentID { get; set; }
public string Address { get; set; }
public string City { get; set; }
public double Rating { get; set; }
public int NumberOfRates { get; set; }
public string Title { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public ICollection<User> Users { get; set; }
}
[Table("Users")]
public class User
{
public User()
{
FavouriteAdvertisments = new HashSet<Advertisment>();
}
[Key]
public int UserID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public ICollection<Advertisment> FavouriteAdvertisments { get; set; }
}
public class GetHiredDBContext : DbContext
{
public GetHiredDBContext()
: base("GetHiredDBContext")
{ }
public DbSet<User> Users { get; set; }
public DbSet<Advertisment> Advertisments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().HasMany(a => a.FavouriteAdvertisments).WithMany(u => u.Users).Map(m =>
{
m.MapLeftKey("UserID");
m.MapRightKey("AdvertismentID");
m.ToTable("UserAdvertisment");
});
}
}
And this is what I want to do:
public ICollection<Advertisment> favouriteAdvertismentsByUser(int UserID)
{
GetHiredDBContext db = new GetHiredDBContext();
foreach (User user in db.Users)
{
if (user.UserID == UserID)
{
return user.FavouriteAdvertisments;
}
}
return null;
}
Everytime I call this method, the number of elements in ICollection for every user is 0!
public ICollection<Advertisment> favouriteAdvertismentsByUser(int UserID)
{
GetHiredDBContext db = new GetHiredDBContext();
// First of all, you probably forgot to "include" FavouriteAdvertisments
var users = db.Users.Include(u => u.FavouriteAdvertisments);
// Second of all, use linq!
return users.SingleOrDefault(u => u.UserID == UserID).FavouriteAdvertisments;
}
If your using Entity Framework you need to write your queries in Linq so the query provider can translate that into a SQL statement. As you have it now it is doing a table scan. Instead try this:
public ICollection<Advertisment> favouriteAdvertismentsByUser(int UserID)
{
return new GetHiredDbContext()
.Users
.Single(u => u.UserID = UserID)
.FavouriteAdvertisements;
}
One thing to note, this method now expects there to be exactly 1 record in your table with that UserID. It will throw an exception if it does not exist. Personally I prefer this because if I'm calling a method I expect it to work, and exception would mean I coded something wrong allowing me to find bugs earlier. You also do not have to check if your collection is null before getting the count.
The way your entites are currently set up, you will have to either use Eager, or Explicit loading, your related entites will not be loaded automatically.
To Explicitly load, I believe you can use your original query (provided you're passing an entity that can be found in the DBSet, and make an explicit call to load the related information (using Load):
E.g. (provided your entity can be found).
public ICollection<Advertisment> favouriteAdvertismentsByUser(User userEntity)
{
// Load the blog related to a given post
GetHiredDBContext db = new GetHiredDBContext();
db.Entry(userEntity).Reference(p => p.FavouriteAdvertisments).Load();
return user.FavouriteAdvertisments;
}
Although it's probably cleaner to obtain your entity from your context, and call load on that, rather than interating through your entire set.
To Eagerly load, you make your load request at the time of query, using Include:
public ICollection<Advertisment> favouriteAdvertismentsByUser(int userID)
{
GetHiredDBContext db = new GetHiredDBContext();
User myUser = db.Users
.Where(x => x.UserID = userID)
.Include(x => x.FavouriteAdvertisments)
.FirstOrDefault();
return myUser.FavouriteAdvertisments;
}
To obtain the data the third way, using Lazy-Loading, you would have to make some alterations to your classes, namely marking your ICollection navigation properties as virtual, so entity framework is able to create valid proxy types for your classes. Your data would be available as required, and loaded on demand.
Hopefully I haven't got the problem completely wrong, just about to shut down/sleep.
Good luck.
More info: http://msdn.microsoft.com/en-us/data/jj574232.aspx

How to write a an EF query involving a many to many relationship

I'm quite new to using the entity framework and I'm having trouble getting my head around how to write a query that uses a many to many relationship. I have 3 entities. Role, User and Securable. A Role can have multiple Securables, and a Securable can be assigned to many Roles. A Role can have multiple Users, and a User can have multiple Roles.
My Question is: How would I go about writing a query that gave me a distinct list of Securables for a given user ID?
Here is my model, where EF automatically creates the link tables for me.
public class SecurityContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Securable> Securables { get; set; }
}
public class User
{
public Guid UserId { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class Securable
{
public Guid SecurableId { get; set; }
public string Name { get; set; }
public virtual ICollection<Role> Roles { get;set;}
}
public class Role
{
public Guid RoleId { get; set; }
public string Name { get; set; }
public virtual ICollection<Securable> Securables { get; set; }
public virtual ICollection<User> Users { get; set; }
}
Untested, but off the top of my head it would be something like this:
var context = new DbContext();
var result = context.Securables
.Where(s => s.Roles
.Any(r => r.Users
.Any(u => u.UserId = userId)))
.Distinct();
Like this?
User user = ...;
IEnumerable<Securable> securablesForUser =
user.Roles.SelectMany(x => x.Securables).Distinct();
Update:-
After working on a project where this was genuinely a performance bottleneck, I investigated more deeply and found that the following LINQ query generates the best SQL (for our data):-
IEnumerable<Securable> securablesForUser =
context.Users.Where(x => x.UserId == userId)
.SelectMany(x => x.Roles)
.SelectMany(x => x.Securables)
.Distinct();
This will use an INNER JOIN in the translated SQL wheras:-
IEnumerable<Securable> securablesForUser = context.Securables.Where(
x => x.Roles.Any(y => y.Users.Any(z => z.UserId == userId))).Distinct();
uses WHERE EXISTS which in our benchmark was slower than querying twice.
As always, if you have performance concerns I recommend profiling. Results with your data may differ. If you don't care enough to profile, you don't care enough to optimise!

Categories