user7355975
user7355975

Reputation:

One to many relationship in Code first

I want to create a relationship between Users and Notifications which I think is a one-to-many relationship.

I'm having trouble with this and not sure how I am going wrong. Here is the code for the two classes:

public class ApplicationUser : IdentityUser
{
    public string FirstName { get; set; }

    public virtual ICollection<Notification> Notifications { get; set; }
}

public class Notification
{
    public int NotificationID { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public DateTime Time { get; set; }

    public string ApplicationUserID { get; set; }
    public virtual ApplicationUser ApplicationUser { get; set; }
}

I also have the following mapping on it:

modelBuilder.Entity<Notification>()
    .HasRequired<ApplicationUser>(u => u.ApplicationUser)
    .WithMany(u => u.Notifications)
    .HasForeignKey(u => u.NotificationID);

I have came across different errors while trying to fix this such as:

Edit:

The same exception (Multiplicity constraint violated. The role 'Notification_ApplicationUser_Target' of the relationship Notification_ApplicationUser' has multiplicity 1 or 0..1.) is thrown when I try to add notifications for all users as I have it done in this method:

public void AddNotification(Notification n)
{
    var roleId = context.Roles.Where(m => m.Name == "User").Select(m => m.Id).SingleOrDefault();
    var users = context.Users.Where(u => u.Roles.All(r => r.RoleId == roleId)).ToList();
    try
    {
        foreach(var user in users)
        {
            user.Notifications.Add(n);
        }
        context.SaveChanges();
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
    }
}

Upvotes: 2

Views: 1583

Answers (2)

Chris Pratt
Chris Pratt

Reputation: 239460

I think the issue is coming from the string type of the foreign key. By default strings are nullable at the database level, but a required foreign key cannot be null. Specifying HasRequired only enforces that the relationship is required, not that the foreign key property must be not null. I think if you simply add [Required] to your foreign key property, that will correct the issue:

[Required]
public string ApplicationUserID { get; set; }

Obviously, you will need to do a migration to apply this change to your database.

EDIT

Sorry, I read into the exception wrong. The source of your problem is actually from adding the same notification to each user, as in literally the same notification. EF does object tracking, so after the entity instance referenced by n has been added once, it is being tracked by EF. Then, when you try to add it to another user, it thinks you're trying to do a many-to-many, basically, where that single Notification record will belong to multiple users.

Off the top of my head, I'm not sure what the best method is to fix this confusion in EF, but I have a couple of potential ideas:

  1. You can try to detach the entity after adding it:

    user.Notifications.Add(n);
    context.Entry(n).State = EntityState.Detached;
    

    However, I'm not sure if that will allow it to be added at all. You'll have to test. If it doesn't create it, you can also try saving before detaching. I think that would work, but it's obviously pretty inefficient committing to the database each iteration.

  2. The safest method would be to create a new instance for each, and simply map over the data from n:

    var notification = new Notification
    {
        Title = n.Title,
        Description = n.Description,
        Time = n.Time
    };
    user.Notifications.Add(notification);
    

    That will ensure that every notification you add is a totally separate tracked instance.

Upvotes: 3

ocuenca
ocuenca

Reputation: 39386

Here is the proper configuration of your relationship:

    modelBuilder.Entity<Notification>()
                .HasRequired(u => u.ApplicationUser)
                .WithMany(u => u.Notifications)
                .HasForeignKey(u => u.ApplicationUserID);

In HasForeignKey method you need to specify the FK of that relationship, not the PK of Notification entity.

Update

The problem is you have an one to one relationship in your DB and you are trying to configure an one to many in your model. If this last one is what you really wants, then I suggest you to use Migrations to change your DB schema, otherwise you can configure your relationship this way:

    modelBuilder.Entity<Notification>()
                .HasRequired(u => u.ApplicationUser)
                .WithOptional(u => u.Notification)

And change the navigation property in ApplicationUser entity:

public class ApplicationUser : IdentityUser
{
       public string FirstName { get; set; }

       public virtual Notification Notification { get; set; }
}

Upvotes: 2

Related Questions