【问题标题】:Creatng a new child with a relation to an existing parent creates a new parent at dbContext.save()创建一个与现有父级有关系的新子级会在 dbContext.save() 处创建一个新父级
【发布时间】:2015-05-17 17:58:09
【问题描述】:

我有一个使用代码优先实体框架模型创建的一对多父子关系。

下面的代码创建一个引用现有父项的新子项的结果是我最终得到了一个新的父项。

我已经看到一个建议的解决方案是将父级附加到上下文或更改它的 EntityState。但是我认为这不起作用,因为这两种模型存在于不同的环境中。

在我看来,不同的上下文总是很奇怪,但我在某处读到,这样做是很常见的,以使每个模型都保持模块化。 (即使他们使用相同的连接字符串等)

所以问题是: 我如何确保 Child 与现有的父级关联,而不是与新创建的父级关联?

即使不同的 dbContexts 没有导致问题,这样做是否明智?

我的控制器代码:

[HttpPost]
        [ValidateAntiForgeryToken]
        [ValidateInput(false)]
        public ActionResult Create([Bind(Include = "Information_Id,Information_Title,Information_LinkText,Information_URLBody")] Information information, int InformationContainer_Id)
        {
            InformationContainerDBContext dbContainer = new InformationContainerDBContext();

            //information.Information_Container = (from p in dbContainer.InformationContainers where p.InformationContainer_Id == InformationContainer_Id select p).ToList()[0];
            information.Information_Container = dbContainer.InformationContainers.Single(o => o.InformationContainer_Id == InformationContainer_Id);


            if (ModelState.IsValid)

            {
                dbContainer.Entry(information.Information_Container).State = EntityState.Unchanged;

                db.Informations.Add(information);
                db.SaveChanges();


                return RedirectToAction("Index");
            }

            return View(information);
        }

我的模特:

家长:

public class InformationContainer
    {
        [Key]
        public int InformationContainer_Id { get; set; }

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

       [InverseProperty("Information_Container")]
        public List<Information> Informations{ get; set; }

    }

    public class InformationContainerDBContext : DbContext
    {
        public InformationContainerDBContext()
            : base(ConfigurationManager.ConnectionStrings["DataDBString"].ConnectionString)
        {
        }
        public DbSet<InformationContainer> InformationContainers { get; set; }
    }

孩子:

public class Information
    {
    [Key]
    public int Information_Id { get; set; }

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

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

    [Required]
    [AllowHtml]
    public string Information_URLBody { get; set; }

    public InformationContainer Information_Container { get; set; }
}

public class InformationDBContext : DbContext
{
    public InformationDBContext()
        : base(ConfigurationManager.ConnectionStrings["DataDBString"].ConnectionString)
    {
    }
    public DbSet<Information> Informations { get; set; }
}

我的观点:

@model eCommSite.Areas.Admin.Models.Information

@{
    ViewBag.Title = "Information Pages - Create";
}

<script type="text/javascript" src="~/Scripts/tinymce/tinymce.min.js"></script>
<script>tinymce.init({ selector: 'textarea' });</script>


<h2>Information Pages</h2>

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Create</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.Information_Title, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Information_Title, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Information_Title, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Information_LinkText, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Information_LinkText, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Information_LinkText, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Information_Container, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <select name="InformationContainer_Id">
                    <option value="1">Help</option>
                    <option value="2">Company</option>
                    <option value="3">Information For Parents</option>

                </select>
              </div>
        </div>


        <div class="form-group">
            @Html.LabelFor(model => model.Information_URLBody, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10" style=" height:500px!important; width: 80%;">
                <textarea id=" wysiwyg"
                          style=" height:350px; width: 500px;"
                          name="Information_URLBody"></textarea>

            </div>

        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

【问题讨论】:

  • 尝试删除dbContainer.Entry(information.Information_Container).State = EntityState.Unchanged; information.Information_Container 如果您向其中添加新信息条目,则应该处于已更改状态。
  • 我认为这些多重背景造成的弊大于利。事实上,我看不到让多个上下文指向同一个数据库有什么帮助。如果有人有任何线索,请赐教。我真的很想知道。
  • @rraszewski 这是一个解决方案的尝试,可以忽略它,结果是一样的
  • MCVE,拜托...这里发生了很多事情。

标签: c# entity-framework model-view-controller one-to-many dbcontext


【解决方案1】:

这里发生了一些事情:

首先,拥有多个 DbContext 太疯狂了,我通过一些工作将它们全部合并。

(如果您正在阅读这篇文章.... 只需要维护一个 DbContext 是巨大的,无论您多么担心可能涉及的工作,我都强烈建议合并,特别是如果您像我一样只有由于缺乏经验,需要多个上下文。)

其次,我添加了对来自另一个上下文的实体的引用以及对实体的引用而不是它们的 id,这在调用保存更改时创建了新实体

【讨论】:

    【解决方案2】:

    您应该将代码更改为:

            [HttpPost]
            [ValidateAntiForgeryToken]
            [ValidateInput(false)]
            public ActionResult Create([Bind(Include = "Information_Id,Information_Title,Information_LinkText,Information_URLBody")] Information information, int InformationContainer_Id)
            {
                    if (ModelState.IsValid)        
                    {
                        InformationContainerDBContext dbContainer = new InformationContainerDBContext();
                        var infContainer = dbContainer.InformationContainers.Single(o => o.InformationContainer_Id == InformationContainer_Id);
    
                        infContainer.Informations.Add(information);
                        dbContainer.SaveChanges();
    
    
                        return RedirectToAction("Index");
                    }
                    return View(information);
            }
    

    如果您将导航属性添加到已存在属性的集合中,它也会添加到数据库中。

    【讨论】:

    • information.Information_Container 是使用 dbContainer 上下文检索的。因此,它已经附加了。
    • 又是一个类似的问题。当您添加到 infContainer.Informations 时,它会添加到 dbContainer 上下文中。我们没有在该上下文中调用 SaveChanges()。所以不会有任何区别。
    • 您可以将其添加为版本来回答(也可以提问)。似乎有问题(并在答案中复制粘贴)
    【解决方案3】:

    去掉多余的 DbContext,只有一个。将此属性添加到您的 Information 类中,使其成为外键:

    public int InformationContainer_Id { get; set; }
    

    您的上下文应如下所示:

    public class InformationDBContext : DbContext
    {
        public InformationDBContext()
            : base(ConfigurationManager.ConnectionStrings["DataDBString"].ConnectionString)
        {
        }
        public DbSet<Information> Informations { get; set; }
        public DbSet<InformationContainer> InformationContainers { get; set; }
    }
    

    由于InformationContainer_Id 是一个外键,你的控制器代码应该是这样的并且它应该可以工作。

    [HttpPost]
    [ValidateAntiForgeryToken]
    [ValidateInput(false)]
    public ActionResult Create([Bind(Include = "Information_Id,Information_Title,Information_LinkText,Information_URLBody")] Information information, int InformationContainer_Id)
    {
        if (ModelState.IsValid)
        {
            information.InformationContainer_Id = InformationContainer_Id;
            db.Informations.Add(information);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(information);
    }
    

    【讨论】:

    • 我可以这样做吗? [ForeignKey("InformationContainer_Id")] public InformationContainer Information_Container { get;放; }
    • 是的,你必须这样做。抱歉,我以为你会处理好它。
    • 所以看起来这可能会导致解决方案,但我决定同时合并数据库上下文,现在无论我做什么(包括完全删除两个表,两个控制器,两者视图并重新构建它们)我得到一个数据库迁移错误,即使出于所有目的,上下文都是全新的。我现在必须解决这个问题,一旦排序,我就可以检查这个解决方案
    • 我也为这种情况写了一个答案,但后来认为它在这种情况下可能没有用,所以将其还原。由于您已决定合并上下文,因此我将取回该版本。对于数据库迁移问题,如果您擅长重建整个数据库,则可以使用这些命令。 update-database -TargetMigration $InitialDatabase。从 Migrations 文件夹中删除所有迁移类文件并运行Add-Migration "NewModel",然后运行update-database -verbose
    • 我收到以下错误:参数 objname 不明确或声明的 objtype (COLUMN) 错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多