Entity Framework 4.1 Code First Self-Referencing One-to-Many and Many-to-Many Associations - entity-framework

I have a User that can have collection of users he likes...
Another user can have collection of users he likes....
If User A likes User B and if User B likes User A, then they get to hang out. I need to send each other their contact info. How do we represent such a model in Entity Framework Code First?
public class User
{
public int UserId { get; set; }
public int? UserLikeId { get; set; }
public virtual UserLike UserLike { get; set; }
}
public class UserLike
{
public int UserLikeId { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
public virtual ICollection<User> LikeUsers { get; set; }
}
Is this model correct? I can't get this to work.
I've tried another way but that doesn't work too...
I tried to add collection of user to user table.
For ex :
public virtual ICollection<User> userlike { get; set; }
public class User
{
public int UserId { get; set; }
public virtual ICollection<UserLike> UserLikes { get; set; }
}
public class UserLike
{
public int UserLikeId { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
public int LikeUserId { get; set; }
public virtual User LikeUser { get; set; }
}
I get this error when I try to add user and who they like:
Conflicting changes to the role 'UserLike_LikeUser_Target' of the relationship 'UserLike_LikeUser' have been detected.
What's the best way to represent such a model?

You don't really need a separate entity to describe the relationship, the object model below will do the trick:
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
public int? ThisUserLikesId { get; set; }
public virtual User ThisUserLikes { get; set; }
public virtual ICollection<User> LikeThisUser { get; set; }
}
public class Context : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOptional(u => u.ThisUserLikes)
.WithMany(u => u.LikeThisUser)
.HasForeignKey(u => u.ThisUserLikesId);
}
}
Now let's say you have a UserId in your hand and want to find the other User who likes this user which this user also like him:
using (var context = new Context())
{
// For a given user id = 1
var friends = (from u in context.Users
where u.UserId == 1
from v in u.LikeThisUser
where v.UserId == u.ThisUserLikesId
select new
{
OurUser = u,
HerFriend = v
})
.SingleOrDefault();
ExchangeContactInfo(friends.OurUser, friends.HerFriend);
}
Update 1:
A self referencing many-to-many association will be mapped to database using a join table which require a different object model and fluent API altogether:
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<User> ThisUserLikes { get; set; }
public virtual ICollection<User> UsersLikeThisUser { get; set; }
}
public class Context : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(u => u.ThisUserLikes)
.WithMany(u => u.UsersLikeThisUser)
.Map(c =>
{
c.MapLeftKey("UserId");
c.MapRightKey("OtherUserId");
c.ToTable("UserLikes");
});
}
}
Update 2:
As I explained in this post, a many-to-many association cannot have a payload (e.g EventId), and if that’s the case then we have to break it down to two one-to-many associations to an intervening class and I can see you’ve correctly created this class (UserLike) to represent the extra information attached to your self-referencing many-to-many association but the associations from this intermediate class are not correct as we need to define exactly 2 many-to-one association from UserLike to User like I showed in the following object model:
public class User
{
public int UserId { get; set; }
public string Email { get; set; }
public virtual ICollection ThisUserLikes { get; set; }
public virtual ICollection UsersLikeThisUser { get; set; }
}
public class UserLike
{
public int UserLikeId { get; set; }
public int LikerId { get; set; }
public int LikeeId { get; set; }
public int EventId { get; set; }
public User Liker { get; set; }
public User Likee { get; set; }
public virtual Event Event { get; set; }
}
public class Event
{
public int EventId { get; set; }
public string Name { get; set; }
}
public class Context : DbContext
{
public DbSet Users { get; set; }
public DbSet Events { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity()
.HasMany(u => u.ThisUserLikes)
.WithRequired(ul => ul.Liker)
.HasForeignKey(ul => ul.LikerId);
modelBuilder.Entity()
.HasMany(u => u.UsersLikeThisUser)
.WithRequired(ul => ul.Likee)
.HasForeignKey(ul => ul.LikeeId)
.WillCascadeOnDelete(false);
}
}
Now you can use the following LINQ query to retrieve all the users who like each other:
using (var context = new Context())
{
var friends = (from u1 in context.Users
from likers in u1.UsersLikeThisUser
from u2 in u1.ThisUserLikes
where u2.LikeeId == likers.LikerId
select new
{
OurUser = u1.UserId,
HerFriend = u2.LikeeId
})
.ToList();
}
Hope this helps.

Related

Entity Framework Core - 3 tier relationship

I have to apply a set of relationships with a system that incorporates a messaging system.
I have the two of my domain object with one mapping object (for the many-to-many relationship):
public class User
{
public User()
{
UserMails = new List<UserMail>();
}
public int Id { get; set; }
public ICollection<UserMail> UserMails { get; set; }
}
public class Mail
{
public Mail()
{
UserMails = new List<UserMail>();
}
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public ICollection<UserMail> UserMails { get; set; }
}
public class UserMail
{
public int Id { get; set; }
public int FromUserId { get; set; }
public User FromUser { get; set; }
public int ToUserId { get; set; }
public User ToUser { get; set; }
public int MailId { get; set; }
public Mail Mail { get; set; }
}
How would I configure this relationship using Fluent API such that there's a many to many relationship between User and Mail and Mail can have 2 foreign keys back to the UserFrom and UserTo?
Any help on this would be greatly appreciated.
If you are trying to model the relationship between a mail and its sender/recipient, then you don't need a many-to-many relation, or 2 foreign keys in your joining entity. Instead, you need 2 one-to-many relations like below -
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Mail> ReceivedMails { get; set; }
public ICollection<Mail> SentMails { get; set; }
}
public class Mail
{
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public int SenderId { get; set; }
public User Sender { get; set; }
public int RecipientId { get; set; }
public User Recipient { get; set; }
}
and you can configure them as -
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Mail>()
.HasOne(p => p.Sender)
.WithMany(p => p.SentMails)
.HasForeignKey(p => p.SenderId)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<Mail>()
.HasOne(p => p.Recipient)
.WithMany(p => p.ReceivedMails)
.HasForeignKey(p => p.RecipientId)
.OnDelete(DeleteBehavior.NoAction);
}

"The referential relationship will result in a cyclical reference that is not allowed."

This is scenario one, which works fine:
public class Domain
{
public int DomainId { get; set; }
[InverseProperty("Domain")]
public virtual ICollection<Person> Persons { get; set; }
[InverseProperty("Domain")]
public virtual ICollection<Group> Groups { get; set; }
}
public class Person
{
public int PersonId { get; set; }
public virtual Domain Domain { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public class Group
{
public int GroupId { get; set; }
public virtual Domain Domain { get; set; }
public virtual ICollection<Person> Members { get; set; }
}
And this is scenario two, which fails.
public class Domain
{
// Same as scenario 1...
}
public class Person
{
public int PersonId { get; set; }
public int DomainId { get; set; } // <--- new
[ForeignKey("DomainId")] // <--- new
public virtual Domain Domain { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public class Group
{
public int GroupId { get; set; }
public int DomainId { get; set; } // <--- new
[ForeignKey("DomainId")] // <--- new
public virtual Domain Domain { get; set; }
public virtual ICollection<Person> Members { get; set; }
}
The error message in scenario 2 is the following:
The referential relationship will result in a cyclical reference that is not allowed. [ Constraint name = FK_dbo.GroupMembers_dbo.Persons_MemberId ]
Both scenarios have this mapping (many to many), inside OnModelCreating's method.
modelBuilder.Entity<Group>()
.HasMany(group => group.Members)
.WithMany(member => member.Groups)
.Map(m =>
{
m.ToTable("GroupMembers");
m.MapLeftKey("GroupId");
m.MapRightKey("MemberId");
});
What am I doing wrong?! What I want to achieve is perfectly reasonable
Either you're using a different version of EF to me (I'm using 5), or you're not including some code which is causing your problem. I created a context containing the code you've provided, and the only error I got was an error about multiple cascade paths (Domains being deleted via Persons or via Groups). I removed Cascade delete on one of the relationships, and it works fine.
Here's my entire context class which works with no errors:
public class TestContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<Domain> Domains { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Group>()
.HasMany(group => group.Members)
.WithMany(member => member.Groups)
.Map(m =>
{
m.ToTable("GroupMembers");
m.MapLeftKey("GroupId");
m.MapRightKey("MemberId");
});
modelBuilder.Entity<Domain>()
.HasMany(d => d.Groups).WithRequired(g => g.Domain)
.WillCascadeOnDelete(false);
}
}

Entity Framework 5 using multiple relationships between two POCOs

I'm having issues applying multiple relationships (or possibly foreignkey) on two POCO objects. I've got the first relationship many-to-many working and when the database is created it creates the three tables (Projects, Users and ProjectsUsers) needed for the relationship.
Code so far:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? CompletionDate { get; set; }
public bool Deleted { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
public User()
{
Name = new Name();
}
public int UserId { get; set; }
public string LoginId { get; set; }
public string Password { get; set; }
public Name Name { get; set; }
public ICollection<Project> ManagedProjects { get; set; }
}
public class ProjectConfiguration : EntityTypeConfiguration<Project>
{
public ProjectConfiguration()
{
HasMany(x => x.Users)
.WithMany(x => x.ManagedProjects);
}
}
public UserConfiguration()
{
HasMany(x => x.ManagedProjects)
.WithMany(x => x.Users);
}
Now I want to add an optional one-to-one relationship of Project.ManagingUser -> User. However, I can't seem to figure out how to indicate this in the configuration.
Code for what I think is needed:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? CompletionDate { get; set; }
public bool Deleted { get; set; }
public int? ManagingUserId { get; set; }
public User ManagingUser { get; set; }
public ICollection<User> Users { get; set; }
}
I don't think the User object needs to change.
This shows my last attempt on mapping the new relationship:
public ProjectConfiguration()
{
HasMany(p => p.Users)
.WithMany(u => u.Projects);
this.HasOptional(p => p.ManagingUser)
.WithOptionalDependent()
.Map(m=>m.MapKey("ManagingUserId"))
.WillCascadeOnDelete(false);
}
What is happening when the database is created, I now end up with only two tables (Projects and Users). And it looks like it is only trying to setup the one-to-one relationship.
Can someone tell me what I'm missing?
Richard I've not changed the UserConfiguration and below is the DbContext:
public class MyDbContext : DbContext
{
public MyDbContext() : base(Properties.Settings.Default.ConnectionString)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Project> Projects { get; set; }
}
You probably want WithMany instead of WithOptionalDependent - it's a one:many relationship, not a one:one.
HasOptional(p => p.ManagingUser)
.WithMany()
.HasForeignKey(m => m.ManagingUserId)
.WillCascadeOnDelete(false);
EDIT
I think you're missing the OnModelCreating override from the DbContext class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ProjectConfiguration());
modelBuilder.Configurations.Add(new UserConfiguration());
}

How to properly map entities using Fluent API?

I have two entities, a User and a UserProfile. The PK of User is UserId, the PK of UserProfile is UserProfileId. Every time a new user is created in my app, I create a new UserProfile whose PK is the same as the PK in User. When I then try to go update properties on the UserProfile I end up getting multiplicity errors or schema invalid errors. Here are my two entities:
public class User
{
public Guid UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? PhoneExtension { get; set; }
public string Comment { get; set; }
public Boolean IsApproved { get; set; }
public int PasswordFailuresSinceLastSuccess { get; set; }
public DateTime? LastPasswordFailureDate { get; set; }
public DateTime? LastActivityDate { get; set; }
public DateTime? LastLockoutDate { get; set; }
public DateTime? LastLoginDate { get; set; }
public string ConfirmationToken { get; set; }
public DateTime? CreateDate { get; set; }
public Boolean IsLockedOut { get; set; }
public DateTime? LastPasswordChangedDate { get; set; }
public string PasswordVerificationToken { get; set; }
public DateTime? PasswordVerificationTokenExpirationDate { get; set; }
public virtual ICollection<Role> Roles { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
public class UserProfile
{
public Guid UserProfileId { get; set; }
public virtual User ProfileOwner { get; set; }
public Int64? HomePhone { get; set; }
public Int64? MobilePhone { get; set; }
public virtual User Manager { get; set; }
}
..and here are my only defined relationships using Fluent API.
modelBuilder.Entity<UserProfile>()
.HasKey(e => e.UserProfileId);
modelBuilder.Entity<UserProfile>()
.Property(e => e.UserProfileId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<UserProfile>()
.HasRequired(e => e.ProfileOwner)
.WithRequiredDependent(r => r.UserProfile);
Finally, my UserService creates a new user and at the same time creates a new UserProfile whose Guid UserProfileId is the same as the User's Guid UserId. Right after the user and profile are created, I try to update the manager in the UserProfile with my UserProfileService using this:
public void UpdateUserProfile(UserProfile updatedUserProfile)
{
UserProfile oldUserProfile = GetUserProfileByID(updatedUserProfile.UserProfileId);
oldUserProfile.Manager = updatedUserProfile.Manager;
oldUserProfile.HomePhone = updatedUserProfile.HomePhone;
oldUserProfile.MobilePhone = updatedUserProfile.MobilePhone;
this.SetEntityState(oldUserProfile, EntityState.Modified);
this.UnitOfWork.SaveChanges();
}
The this.SetEntityState line throws this error:
Multiplicity constraint violated. The role 'UserProfile_ProfileOwner_Source' of the relationship 'WhelenPortal.Data.Context.UserProfile_ProfileOwner' has multiplicity 1 or 0..1.
I've been trying to get this working for TWO DAYS now, PLEASE HELP!!! Thanks in advance.
As requested, here is some additional information. I'm using the repository pattern and unit of work here. My GetUserProfileById code is below. The service uses the repository so I show both.
public UserProfile GetUserProfileByID(Guid id)
{
if (id == null)
throw new BusinessServicesException(Resources.UnableToRetrieveUserProfileExceptionMessage, new ArgumentNullException("id"));
try
{
Model.UserProfile userProfile = _userProfileRepository.GetUserProfileByID(id);
if (userProfile != null)
return ToServicesUserProfile(userProfile);
return null;
}
catch (InvalidOperationException ex)
{
throw new BusinessServicesException(Resources.UnableToRetrieveUserProfileExceptionMessage, ex);
}
}
..and the repository:
public UserProfile GetUserProfileByID(Guid id)
{
return this.GetDbSet<UserProfile>().Find(id);
}
So after much playing around this is what ended up working for me, hopefully it can help someone else in some fashion. My User class stayed exactly the same but my UserProfile class changed to this:
public class UserProfile
{
public Guid UserProfileId { get; set; }
public virtual User ProfileOwner { get; set; }
public Guid? ManagerId { get; set; }
public virtual User Manager { get; set; }
public Int64? HomePhone { get; set; }
public Int64? MobilePhone { get; set; }
}
And here is the fluent mapping:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasOptional(u => u.UserProfile)
.WithRequired(u => u.ProfileOwner);
modelBuilder.Entity<UserProfile>()
.HasOptional(u => u.Manager)
.WithMany()
.HasForeignKey(u => u.ManagerId);
}

Entity Framework query returns one record, but its supposed to return more

As title say, i got problem with a query in Asp.net mvc 3 EF.
I got 2 tables with 1 to many relationship.
table1 Users int user_ID string username
table2 Friends int friendshipID int user_ID int friend_ID
The controller:
// // GET: /User/Details/5
public ViewResult Details(int id)
{
User user = db.Users.Include("Friends").FirstOrDefault(u => u.user_ID == id);
//Also for each friend get the User:
foreach (var friend in user.Friends.ToList())
{
friend.User = db.Users.Find(friend.friend_ID);
}
return View(user);
}
The view "details":
#model Social2.Models.User
#{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<div class="display-field">
#foreach (var friend in #Model.Friends)
{
#friend.User.username;
}
</div>
Context:
public partial class ASPNETDBEntities : DbContext
{
public ASPNETDBEntities()
: base("name=ASPNETDBEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<aspnet_Users> aspnet_Users { get; set; }
public DbSet<Friend> Friends { get; set; }
public DbSet<User> Users { get; set; }
}
user model:
public partial class User
{
public User()
{
this.Friends = new HashSet<Friend>();
}
[Key]
public int user_ID { get; set; }
public System.Guid user_UniqueID { get; set; }
public string username { get; set; }
public virtual aspnet_Users aspnet_Users { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
}
friend model
public partial class Friend
{
public int friendship_ID { get; set; }
public int user_fr_ID { get; set; }
public int friend_ID { get; set; }
public virtual User User { get; set; }
}
The problem is, when i go to ~/user/details/1, the view show only one(the last one) friend.For every user it shows their last friend. How to show them all ?
Your database must have two relationships defined like so:
If you create an Entity Model from this schema you also get two one-to-many relationships. And when you apply the DBContext T4 template to this model you should get POCO classes like so:
public partial class Users
{
public Users()
{
this.Friends = new HashSet<Friends>();
this.Friends1 = new HashSet<Friends>();
}
public int user_ID { get; set; }
public string username { get; set; }
public virtual ICollection<Friends> Friends { get; set; }
public virtual ICollection<Friends> Friends1 { get; set; }
}
public partial class Friends
{
public int friendship_ID { get; set; }
public int user_fr_ID { get; set; }
public int friend_ID { get; set; }
public virtual Users Users { get; set; }
public virtual Users Users1 { get; set; }
}
Users.Friends and Friends.Users form a pair for the first relationship and Users.Friends1 and Friends.Users1 are a pair for the second relationship. (You can rename the navigation properties in the model designer to make the names more meaningful.) Your query would look like this then (you can include a "second level" and don't need the loop as you did in your example):
public ViewResult Details(int id)
{
// important to use User1 in Include, not User
User user = db.Users.Include("Friends.User1")
.FirstOrDefault(u => u.user_ID == id);
return View(user);
}
With DbContext you can also use the strongly typed version of Include:
Include(u => u.Friends.Select(f => f.User1))
I think the problem is mapping of the class Friend.
try change to:
public partial class Friend
{
[Key]
public int friendship_ID { get; set; }
public int user_fr_ID { get; set; }
public int friend_ID { get; set; }
[ForeignKey("friend_ID")]
public virtual User User { get; set; }
}
I think the problem is here
User user = db.Users.Include("Friends").FirstOrDefault(u => u.user_ID == id);
FirstOrDefault gives only one record.