【问题标题】:One-to-one Entity Framework relationship一对一的实体框架关系
【发布时间】:2016-09-20 22:08:28
【问题描述】:

在我的数据模型(Entity Framework 6.1.3、ASP.NET MVC 5、Code First 和现有数据库)中有两个表,“Person”和“User”,它们共享一对一的关系。 “用户”表的 PK 列具有 PersonID 列,而该列又是“人员”表的 PK。我希望每当创建新的用户记录时,(首先)自动创建一个人记录,然后将 Person 表中的 PersonID 值插入到新的用户记录中。

这是Person表的模型代码:

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

    public virtual User User { get; set; }
}

这是用户表的型号代码:

[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; }
}

UserController.cs 代码包括:

    // 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);
    }

Person 表的 SQL:

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

用户表的 SQL:

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

在此先感谢大家。

【问题讨论】:

    标签: c# sql-server asp.net-mvc entity-framework


    【解决方案1】:

    你的数据库结构应该是这样的

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

    将上面的db结构翻译成实体框架配置

    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); 
    
    }
    

    如果您尝试单独保存Person 而没有User,则上述配置(1-1 关系)将出错,反之亦然。

    注意:我建议建立(1-0..1 关系),因为逻辑上 User 严格取决于人(弱实体),而 Person 可以在没有用户的情况下单独存在。 1:0..1的配置 与上面的配置非常相似,唯一的区别是通过 .HasOptional(p=&gt;p.User) 而不是 .HasRequired(p=&gt;p.User)

    现在当您创建用户实体时,您的代码应该是这样的

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

    每当您创建用户对象时,上述代码都会强制创建人员对象。

    已更新:针对您的情况

    我建议您创建一个DTO 对象来管理两个对象(用户和个人)的 CRUD,如下所示

    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 ... //
    }
    

    您的UserController.cs 代码可以在一个操作中同时处理创建和编辑,如下所示,只要两个视图相同,您就可以减少代码。

    [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);
    }
    

    你的视图CreateOrEdit.cshtml应该是这样的,我不会为了简单而添加html代码和标签

    @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)
           } 
    }
    

    希望对你有帮助

    【讨论】:

    • 正如我在回答中建议的那样,用户有 PersonID 作为 PK 和 FK,所以当您想提交数据(Create 发布操作)时,您需要构建两个实体(@987654338 @),因此在您的视图中,您应该显示两个实体数据
    • 是的 UserDTO 是一个新的类,最好把它放在单独的文件中,这篇文章将帮助你更好地理解 DTO 的用法asp.net/web-api/overview/data/…
    • 在用于发布值的创建操作中,我已经为您添加了代码,如果您查看我添加到答案中的代码,您会发现我在else // Add 语句中添加了它
    • 从 UPDATED 和向下读取,在 UPDATED 之上,是一个通用的解决方案,然后我编辑了答案为你添加代码,我认为它已经足够清楚了。
    • 没关系,如果对你有帮助,请标记我的答案并投票
    【解决方案2】:

    此配置不起作用,因为 EF 无法以这种方式处理循环引用(它只能使用主键列来处理它)。
    请在此处查看我的回答
    Code First migration when entities have cross references with foreign keys

    【讨论】:

    • 谢谢布比。我去看看。
    【解决方案3】:

    我记得不久前做过这个。我认为您需要将 Person 类抽象化。并让用户继承它。我认为 EF 应该自动处理加入这两个表。

    这是一个例子:

    [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; }
    }
    

    在配置中我添加了几个Users,如下所示:

    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"});
        }
    }
    

    以下是数据库中生成的内容

    有关 EF 中的模型继承的更多信息,请参见 here

    【讨论】:

    • 谢谢mrstebo。有什么细节吗?
    • 稍后我会在我的电脑上编辑我的答案
    • 刚刚对我的回答进行了更改,详细说明了我所说的话
    • 干杯 Mrstebo。我会看看如何实现它,我会告诉你的。
    • 您好 Mrstebo。我应用了您的建议,但是当我构建解决方案时,我收到一个错误,与 MyContext.cs 文件一致:ErrorCS1061:'Person' 不包含'User' 的定义并且没有扩展方法'User' 接受'Person 类型的第一个参数' 可以找到(您是否缺少 using 指令或程序集引用?)。接下来我将包含错误代码。非常感谢您的帮助,galaf。
    猜你喜欢
    • 2016-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-24
    • 2015-05-04
    • 2016-01-27
    • 2020-09-26
    • 1970-01-01
    相关资源
    最近更新 更多