Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assigning UserId to Foreign Key = null

I'm using Entity Framework 5 and MVC 4, with .NET 4.5 using Code First migrations. I have foreign keys between two entities in a 1 to zero-or-1 relationship. When I try to create the record for Jogger everything blows up. I get error messages that are either:

  • Foreign Key is a null reference
  • dbo.Jogger does not exist
  • Unhandled exception ... no metadata.

When I run the EF Viewer the navigation relationships are perfect. No Fluent API code is used at the moment.

User Class

     [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string UserName { get; set; }

    // Other properties

    public int? JoggerId { get; set; }
    public virtual Jogger Jogger { get; set; }

Jogger Class

    [ForeignKey("UserId")]
    public int JoggerId { get; set; }

    public virtual UserProfile UserId { get; set; }

When the JoggerController is generated it produces this code for the Get and Post Methods:

// GET: /Jogger/Create

    public ActionResult Create()
    {
        ViewBag.JoggerId = new SelectList(db.UserProfiles, "UserId", "UserName");
        return View();
    }

    //
    // POST: /Jogger/Create

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(Jogger jogger)
    {



        if (ModelState.IsValid)
        {
            db.Jogger.Add(jogger);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        ViewBag.JoggerId = new SelectList(db.UserProfiles, "UserId", "UserName", jogger.JoggerId);
        return View(jogger);
    }

In the Jogger/Create view there are no fields for JoggerId or UserId according to the generated code.

Typically it fails on the (ModelState.IsValid) portion of code where the Output shows JoggerId = 0 and UserId = null.

I have not seen this behaviour on the Contoso University tutorial on asp.net's website and I have looked at the MSDN Learn EF site as well. I can't seem to solve this problem. Your advice is appreciated.

like image 783
Springbokkie Avatar asked Aug 02 '13 08:08

Springbokkie


3 Answers

After much trial and error I found a way that works to assign the Foreign Key a value.

Firstly, my relationships were setup incorrectly: UserProfile Class did not need the public int? GolferId to represent a navigation property. Final classes looked as follows:

UserProfile

  [Key]
  [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
  public int UserId { get; set; }
  public string UserName { get; set; }

  // Navigation properties
  public virtual Jogger Jogger { get; set; }

Jogger Class

[Key] 
[ForeignKey("UserId")]
public int JoggerId { get; set; }

public string Pronation {get; set;}

public virtual UserProfile UserId { get; set; }

This setup the correct relationship without needing any Fluent API code.

To assign the ForeignKey value was a little trickier and I'm sure there are MUCH better ways of doing it than this.

  1. Instead of the other scaffolding method of defining (id = 0), I used a FormCollection. You can also use the [Bind(Include = "field1, field2, field3")] to ensure that the correct fields are being posted.
  2. There's a long way of getting to the logged-in User's UserId which takes advantage of User.Identity.Name
  3. The FormCollection allows me to reference and save the other fields e.g. Pronation

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(FormCollection values)
    {
    
        var jogger = new Jogger();
        TryUpdateModel(jogger);
    
        var context = new TestContext();
        var userqq = User.Identity.Name;
        var user = context.UserProfiles.SingleOrDefault(u => u.UserName == userqq);
    
        if (ModelState.IsValid)
        {
    
                jogger.JoggerId = user.UserId;
                jogger.Pronation = values["Pronation"];
    
    
                db.Joggers.Add(jogger);
                db.SaveChanges();
               return View();
    
    
        }
            ViewBag.JoggerId = new SelectList(db.UserProfiles, "UserId", "UserName", jogger.JoggerId);
            return View(jogger);
    
    }
    

This wasn't pretty at all but it does work. Thanks to @Moeri for pointing me in the right direction.

like image 140
Springbokkie Avatar answered Oct 13 '22 04:10

Springbokkie


The error is in the data base model. Entity Framework Code First will always require configuration for one-to-one relationships. Code First can’t determine which class is the dependent in these situations.

1- In your Jogger class add the property:

public virtual User User { get; set; }

2 - In your OnModelCreating method add:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
      // One to One relationship
      modelBuilder.Entity<User>()
                        .HasOptional(t => t.Jogger)
                        .WithOptionalDependent(m => m.User);
}
like image 34
Aguedo Valdes Perez Avatar answered Oct 13 '22 03:10

Aguedo Valdes Perez


Here is your error:

public virtual UserProfile UserId { get; set; }

Entity Framework Code First works with conventions, one of them being name-based conventions. That means that if you have a property "UserId", it will look for a table "User" and configure it as a foreign key to that table. I think it's blowing up because you gave your navigation property a name that is conventionally used for a foreign key property.

The correct way of working would be:

public virtual int? UserId { get; set; }
public virtual User User { get; set; }

If you want your property to be called "UserProfileId", you will have to use the "ForeignKey" attribute or the fluent configuration syntax to let Entity Framework know this is a foreign key:

public virtual int? UserProfileId{ get; set; }

[ForeignKey("UserProfileId")
public virtual User User { get; set; }

or with fluent syntax (you write this in the OnModelCreating method of your Context, or in a separate class that inherits from EntityTypeConfiguration):

HasRequired(j => j.User).WithMany().HasForeignKey(i => i.UserProfileId)

Edit: I've just noticed your User table is actually called UserProfile, which sort of renders the latter half of my response moot. Nevertheless I'll leave it as it is as it can be educational.

like image 45
Moeri Avatar answered Oct 13 '22 02:10

Moeri