2

In my data model (Entity Framework 6.1.3, ASP.NET MVC 5, Code First with an existing database) there a two tables, “Person” and “User” that share a one-to-one relationship. “User” table has as PK the PersonID column which is in turn the “Person” table's PK. I’d like whenever a new User record is created, a Person record be (first) automatically created and then the value of PersonID in Person table get inserted into the new User record.

This is the model code for Person table:

[Table("Person")]
public partial class Person
{
    public int PersonID { get; set; }

    public virtual User User { get; set; }
}

This is the model code for User table:

[Table("User")]
public partial class User
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int PersonID { get; set; }

    [Required]
    [StringLength(20)]
    public string Name { get; set; }

    public virtual Person Person { get; set; }
}

The UserController.cs code includes:

    // POST: User/Create
    // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
    // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "PersonID,Name")] User user)
    {
        if (ModelState.IsValid)
        {
            db.Users.Add(user);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        ViewBag.PersonID = new SelectList(db.People, "PersonID", "PersonID", user.PersonID);
        return View(user);
    }

The SQL for the Person table:

CREATE TABLE [dbo].[Person](
[PersonID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL
CONSTRAINT [PK_Person_PersonID] PRIMARY KEY CLUSTERED)

The SQL for the User table:

CREATE TABLE [dbo].[User](
[PersonID] [int] NOT NULL,
[Name] [nvarchar](20) NOT NULL
CONSTRAINT [PK_User_PersonID] PRIMARY KEY CLUSTERED)

ALTER TABLE [dbo].[User]  WITH CHECK ADD  CONSTRAINT [FK_User_Person_PersonID] FOREIGN KEY([PersonID])
REFERENCES [dbo].[Person] ([PersonID])
GO

ALTER TABLE [dbo].[User] CHECK CONSTRAINT [FK_User_Person_PersonID]
GO

Thanks in advance, galaf.

galaf
  • 37
  • 8

3 Answers3

2

Your database structure should look like this

  1. Table User: ( PersonID int (PK, FK), Name nvarchar(20))
  2. Table Person (PersonID int (PK , identity), ...)

To translate the above db structure to entity framework configuration

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // Configure PersonID as PK for User 
    modelBuilder.Entity<User>()
        .HasKey(e => e.PersonID);

    // Configure PersonID as FK for User
    modelBuilder.Entity<Person>()
                .HasRequired(p => p.User) 
                .WithRequired(u => u.Person); 

}

the above configuration (1-1 relationship) will through an error if you try to save Person alone without User and vice versa.

Note: I recommend to build a (1-0..1 relationship) since logically the User is strictly depending on the person ( weak entity) while Person can exists alone without having a user. the configuration of 1:0..1 is very similar to the above configuration, the only difference is by making .HasOptional(p=>p.User) instead of .HasRequired(p=>p.User)

Now when you create the User entity, your code should be like this

var user = new User(){
   Name = "UserName",
   Person  = new Person(){
       FirstName="FirstName",
       LastName ="LastName",
       ...
   }
};

db.Users.Add(user);
db.SaveChanges();

The above code will enforce the creation of the person object whenever you create the user object.

UPDATED : for your case:

I recommend that you create a DTO object to manage the CRUD for both objects ( User and Person) as follows

public class UserDTO 
{
    public int Id { get; set;}
    public string UserName { get;set;}
    public string Password { get;set;}
    public string FirstName { get; set;}
    public string LastName { get; set;}
    // ... any other required properties goes here ... //
}

your UserController.cs code can handle both the create and edit in one action as follows, which will allow you to code less as long as both view are the same.

[HttpGet]
public ActionResult CreateOrEdit(int? id)
{
     UserDTO user = null;
     if( id!=null)
         user = db.Users.Select(t=> new UserDTO {
             UserName = t.UserName,
             FirstName = t.Person.FirstName,
             LastName = t.Person.LastName,
             Id = t.Id
             // ... //
         }).FirstOrDefault();
     else
         user = new UserDTO();
     return View(user);
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateOrEdit(UserDTO model)
{
      if(model!=null && ModelState.IsValid)
      {
           if(model.Id>0) // Edit
           {
               User  user = db.Users.Include(p=>p.Person).Single(t=>t.Id == model.Id);
               user.UserName = model.UserName;
               user.Person.FirstName = model.FirstName;
               user.Person.LastName = model.LastName;
           }
           else // Add
           {
               User user = new User
               {
                   UserName = model.UserName,
                   Password = model.Password, // should be encrypted first
                   Person = new Person{
                      FirstName = model.FirstName,
                      LastName = model.LastName
                   }
               };
               db.Users.Add(user);
           }
           db.SaveChanges();
           return RedirectoToAction("TargetActionGoesHere");
      }
      return View(model);
}

your View CreateOrEdit.cshtml should look like , I will not add html code and labels just for making it simple

@model UserDTO

@using(Html.BeginForm())
{
       @Html.AntiForgeryToken()
       @Html.ValidationSummary(true)
       @* To store the value of Id in hidden field*@
       @Html.HiddenFor(m=>m.Id) 
       @Html.EditorFor(m=>m.FirstName)
       @Html.EditorFor(m=>m.LastName)
       @Html.EditorFor(m=>m.UserName)
       @if(Model.Id ==0 ) 
       { 
           // only in the add case you need to get the password value
           @Html.EditorFor(m=>m.Password)
       } 
}

Hope this will help you

Monah
  • 6,714
  • 6
  • 22
  • 52
  • As I recommended in my answer, User has `PersonID` as PK and FK , so when you want to submit your data ( `Create` post action), you need to build both entities (`User and Person`), so in your View, you should display both entities data – Monah Sep 27 '16 at 06:07
  • yes the UserDTO is a new class, better to make it in separate file, this article will help you to understand better the usage of DTO http://www.asp.net/web-api/overview/data/using-web-api-with-entity-framework/part-5 – Monah Sep 27 '16 at 07:33
  • in the Create action for posting the values, I already added the code for you, if you look to the code I added to the answer, you will find that I added it in the `else // Add` statement – Monah Sep 27 '16 at 07:49
  • read from UPDATED and down, above UPDATED, was a general solution, Then I edited the answer to add the code for you and I think it is clear enough to follow it. – Monah Sep 27 '16 at 07:53
  • It is ok, please mark my answer and vote up if it was helpful for you – Monah Sep 27 '16 at 12:18
  • in the `UserController.cs` , put `Models.UserDTO` and not `UserDTO` since you added it inside the folder Models, if you open the file UserDTO and read the namespace `YourProject.Models.UserDTO`, you will find it as Mod, and you can pres `Ctrl + dot` button it will give you option to add the reference or to complete the path – Monah Sep 27 '16 at 13:17
  • The answer included how the createoredit.cshtml should look like – Monah Sep 28 '16 at 07:09
  • The idea behind combining the two views ( create and edit) is because they have the same role, to allow the user to edit the user name, first name and last name of the person at the same time and not to separate them into different views, this way will allow you to code less. – Monah Sep 28 '16 at 07:11
  • Hadi I'd like to thank you for your help. You really helped me all the way! – galaf Sep 28 '16 at 16:42
0

This configuration won't work because EF can't handle circular references in this way (it can handle it only using primary key columns).
Please have a look to my answer here
Code First migration when entities have cross references with foreign keys

Community
  • 1
  • 1
bubi
  • 6,414
  • 3
  • 28
  • 45
-2

I remember doing this a while ago. I think you need to make the Person class abstract. And get User to inherit from it. I think EF should handle joining the two tables automatically.

Here is an example:

[Table("Person")]
public abstract class Person
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
}

[Table("User")]
public class User : Person
{
    public string Name { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Person> People { get; set; }
    public DbSet<User> Users { get; set; }
}

And in the configuration I added a few Users like so:

internal sealed class Configuration : DbMigrationsConfiguration<MyContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
    }

    protected override void Seed(MyContext context)
    {
        context.Users.AddOrUpdate(
            p => p.Name,
            new User {Name = "Test User #1"},
            new User {Name = "Test User #2"},
            new User {Name = "Test User #3"});
    }
}

And below are what is produced in the database

The results

More information is available here in regards to model inheritance in EF

mrstebo
  • 911
  • 9
  • 12
  • Thanks mrstebo. Any details? – galaf Sep 21 '16 at 02:28
  • I'll edit my answer when I get on to my computer later – mrstebo Sep 21 '16 at 05:01
  • Just made changes to my answer elaborating on what I was saying – mrstebo Sep 21 '16 at 08:06
  • Cheers mrstebo. I'll see how to implement it and I'll let you know. – galaf Sep 21 '16 at 22:55
  • Hi mrstebo. I applied your suggestion but when I build the solution I get an error concerting the MyContext.cs file: ErrorCS1061: 'Person' does not contain a definition for 'User' and no extension method 'User' accepting a first argument of type 'Person' could be found (are you missing a using directive or an assembly reference?). I will include the faulty code next. Thank you very much for your assistance, galaf. – galaf Sep 27 '16 at 03:05
  • The code in MyContext.cs file where the fault is indicated is the following and is pointed to e.User: protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity() .HasOptional(e => e.User) .WithRequired(e => e.Person); Thanks once more, galaf. – galaf Sep 27 '16 at 03:06