为什么Microsoft的实体框架因基本简单的单个外键而失败,一个表单中的两个表名称和电子邮件失败...以及如何操作我修理它?
我们都听说过飞行常客计划。加入我的爱立信启发的频繁失败计划,了解如何不使用EF。看看我试过的代码和下面的相应错误。
Morteza Manavi描述了此One-to-One Primary Key Association or Shared Primary Key问题。 Telerik的 OpenAccess ORM称之为" Vertical Inheritanc e"。 EF没有像Ruby On Rails这样的一致术语或属性。 ActiveRecord belongs_to。
如果您想快速原型化和播放,那么构建此SQL Server图中显示的两个表,外键和唯一约束的T-SQL脚本将包含在下面...
在这种情况下没有多少。如果您正在寻找1对多或多对多的答案,抱歉,您错误的地方。多重性是1人到0..1电子邮件。
如果用户在Razor表单中留下电子邮件空白,则Person和ContactInfoes之间的关系应为1:0(即,一对一[12n1])且没有ContactInfoes记录。
否则ContactInfoes与People有1:1的关系(即一对一[121])。或者,People记录与ContactInfoes记录具有1:1的关系。
我已经做了多年的尖叫[SQL]并喜欢图片,但上面的EDMX屏幕截图并没有提供背景中发生的事情的完整画面。为了使一对一或一个EF和 Code Firsterers 通过为每个引用其他实体的实体创建虚拟 ICollection 来增加复杂性下面的DbContext Code First模型。
public class MVC3EF4Context : System.Data.Entity.DbContext
{
public System.Data.Entity.DbSet<Person> People { get; set; }
public System.Data.Entity.DbSet<ContactInfo> ContactInfoes { get; set; }
}
public class Person
{
[Key]
public int PersonId { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<ContactInfoes> ContactInfo { get; set; }
}
public class ContactInfo
{
[Key]
[ForeignKey("PersonId")]
public int PersonId { get; set; }
[Required]
public string Email { get; set; }
public virtual ICollection<People> Person { get; set; }
}
这种额外复杂性的优势在于强类型控制器中的IntelliSense以及代码生成器从People模型创建的视图 - 它可以来自EDMX图,T4 .tt文件或Code First类。
使用EF为Noob做直观的事情就是简单地将@Html.EditorFor(model => model.**ContactInfoes.Email**)
添加到Create.cshtml和Edit.cshtml自动生成的Razor表单中。
虽然在有电子邮件时插入到ContactInfoes表中会自动运行,因为EF构建了实体的完整图表:),不幸的是,
_Email = StructuralObject.SetValidValue(value, false);
IsNullable=false
,无法插入电子邮件。自动代码生成足够智能,可以检测和处理后代实体,但不是作为开发人员阅读我的想法,只是在没有电子邮件时跳过创建实体。如果有一个&#34;如果有错误,那就行了,建立图中的所有其他实体,但跳过这一个&#34;命令我不知道它是什么。作为开发人员,我必须在没有输入电子邮件时拦截和插入。
不幸的是,自动生成的db.People.Attach(people);
也无法进行更新(即使谓词ModelState.IsValid为true)。在提交表单时,EF显然没有检查其后代关系 - 如果有,我不知道如何提供它。
A referential integrity constraint violation occurred:
The property values that define the referential constraints are not consistent
between principal and dependent objects in the relationship.
MVC 3 with Entity Framework and Editing Multiple Objects leads to "referential integrity constraint violation"建议使用AutoMapper来解决这个问题,在整个解决方案中对于3个属性总数来说有点过头了。 Bengt Berge的Intro to AutoMapper快速而简单。使用FormsCollection的建议似乎不合适,因为People和ContactInfoes实体中已经没有任何其他信息。
因此,这里显示的自动生成的代码(我尝试使用try和catch的一小部分错误报告/异常处理程序)需要决定是否创建或删除InfoContact实体。
[HttpPost]
public ActionResult Edit(People people)
{
if (ModelState.IsValid)
{
try
{
db.People.Attach(people);
}
catch (Exception ex)
{
return Content("<h1>Error!</h1>"
+ ex.Source + "<br />\n\r"
+ ex.Message + "<br />\n\r"
+ ex.InnerException + "<br />\n\r"
+ ex.StackTrace+ "<br />\n\r"
+ ex.TargetSite + "<br />\n\r"
+ ex.HelpLink + "<br />\n\r"
+ ex.Data + "<br />\n\r"
+ "If this is an emergency call 911.\n\r"
);
}
db.ObjectStateManager.ChangeObjectState(people, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.PersonId = new SelectList(db.ContactInfoes, "PersonId", "Email", people.PersonId);
return View(people);
}
一些侧注切线
虽然这是一个垂直继承关系,但Microsoft的代码生成器会生成ViewBag.PersonId = new SelectList(db.ContactInfoes, "PersonId", "Email", people.PersonId);
为什么有人会在此模型中明确定义的自引用场景中使用下拉列表?当然,程序员可以删除自动代码,或忽略它,但为什么在没有需要时首先生成它?
有没有比我的粗略异常处理程序实现更好的调试方法?输出提供深蹲的平方根,即无用。我假设Log4net和ELMAH不会提供更多信息,只需跟踪可用内容。
最终完成所有ViewModel映射后,使用EF真的有优势吗?
事实证明,在强类型表单处理中使用IntelliSense的秘诀是与AutoMapper相关,因为开发人员需要在它与People和ContactInfoes实体之间添加一个ViewModel和map,因此侧面注意切线#3。
using System.ComponentModel.DataAnnotations;
public class PCI //PersonContactInfo
{
[Key]
public int PersonId { get; set; }
[Required]
public string Name { get; set; }
// Add Annotation for valid Email or Null
public string Email { get; set; }
}
使用ViewModel只需要在编辑和创建cshtml Razor表单文件中进行两处更改。
第一行从@model MVC3EF4.Models.People
到@model MVC3EF4.Models.PCI
和我原来的修改
@Html.EditorFor(model => model.ContactInfoes.Email)
变为
@Html.EditorFor(model => model.Email)
注意:我没有使用PCI ViewModel开始构建新的Controller和Razor Forms。 我也没有使用DbContext,但EDMX代码生成=默认。如果要使用DbContext,请设置EDMX代码生成属性=无。
[HttpPost]
public ActionResult Create(PCI pci) //use the ViewModel PCI instead of People
{
if (ModelState.IsValid)
{
// THIS IS WHERE AutoMapper WOULD BE HANDY FOR MAPPING ViewModel
// to the People and ContactInfoes Models
// Map PCI to People
People people = new People();
people.PersonId = pci.PersonId;
people.Name = pci.Name;
// Map PCI to ContactInfoes --if there is an Email
if (pci.Email != null && pci.Email.Length > 3)
{
// KNOWN AND THROWN ????
// Why is there no concurrency error thrown?
// How is this added when PersonId isn't known?
// This isn't standard functional programming. This is how EF builds a graph.
db.AddToContactInfoes(
new ContactInfoes {
PersonId = people.PersonId,
Email = pci.Email
}
);
}
// else don't add ContactInfoes entity/table.
db.People.AddObject(people);
db.SaveChanges();
return RedirectToAction("Index");
}
// Where does a dropdownlist get used? It doesn't for a 1:0..1 relationship.
//ViewBag.PersonId = new SelectList(db.ContactInfoes
//, "PersonId", "Email", people.PersonId);
return View(pci);
}
// GET: /PersonContactInfo/Edit/5
public ActionResult Edit(int id)
{
People people = db.People.Single(p => p.PersonId == id);
// Map People to ViewModel PCI...
PCI pci = new PCI()
{
PersonId = people.PersonId,
Name = people.Name
};
if (people.ContactInfoes != null) { pci.Email = people.ContactInfoes.Email; }
/* why a SelectList in a one-to-one or one-to-none?
* what is to select?
* ViewBag.PersonId = new SelectList(db.ContactInfoes
, "PersonId"
, "Email"
, people.PersonId);
*/
return View(pci);
}
//
// POST: /PersonContactInfo/Edit/5
[HttpPost]
// THIS DOESN'T WORK
//public ActionResult Edit(People people) //use the ViewModel instead
//public ActionResult Edit(FormCollection col) //No need, everything is available from strongly typed ViewModel
public ActionResult Edit(PCI pci)
{
if (ModelState.IsValid)
{
// THIS DOESN'T WORK
// var people = new People(); --reload what the Person was from the database
People people = db.People.Single(p => p.PersonId == pci.PersonId);
try
{
people.Name = pci.Name;
if (pci.Email != null && pci.Email.Length > 3)
{
if (people.ContactInfoes == null) {
var ci = new ContactInfoes { PersonId = pci.PersonId, Email = pci.Email };
db.AddToContactInfoes(ci);
// THIS DOESN'T WORK
//db.ContactInfoes.Attach(ci) // this causes an error on the next line
// A referential integrity constraint violation occurred: A primary key property that is a part of referential integrity constraint cannot be changed when the dependent object is Unchanged unless it is being set to the association's principal object. The principal object must be tracked and not marked for deletion.
// people.ContactInfoes = ci;
// THIS DOESN'T WORK
//people.ContactInfoesReference.Attach(new ContactInfoes { PersonId = pci.PersonId, Email = pci.Email });
//Attach is not a valid operation when the source object associated with this related end is in an added, deleted, or detached state. Objects loaded using the NoTracking merge option are always detached.
}
else
people.ContactInfoes.Email = pci.Email;
}
else // no user input for Email from form so there should be no entity
{
// THIS DOESN'T WORK
// people.ContactInfoes = null;
// people.ContactInfoesReference = null;
ContactInfoes ci = people.ContactInfoes;
if (ci != null) //if there isn't an ContactInfo record, trying to delete one will cause an error.
db.ContactInfoes.DeleteObject(ci);
/*
* THIS DOESN'T WORK
// this causes a concurrency error
var ci = new ContactInfoes();
ci.PersonId = pci.PersonId;
db.AttachTo("ContactInfoes", ci);
db.ContactInfoes.DeleteObject(ci);
*/
}
// THIS DOESN'T WORK
// db.People.Attach(people);
// doing People people = db.People.Single(p => p.PersonId == pci.PersonId); makes people automatically attached
// The object cannot be attached because it is already in the object context. An object can only be reattached when it is in an unchanged state.
}
catch (Exception ex)
{
return Content("<h1>Error!</h1>"
+ ex.Source + "<br />\n\r"
+ ex.Message + "<br />\n\r"
+ ex.InnerException + "<br />\n\r"
+ ex.StackTrace+ "<br />\n\r"
+ ex.TargetSite + "<br />\n\r"
+ ex.HelpLink + "<br />\n\r"
+ ex.Data + "<br />\n\r"
);
}
db.ObjectStateManager.ChangeObjectState(people, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
//ViewBag.PersonId = new SelectList(db.ContactInfoes, "PersonId", "Email", people.PersonId);
return View(pci);}
-- --------------------------------------------------
-- Entity Designer DDL Script for SQL Server 2005, 2008, and Azure
-- --------------------------------------------------
-- Date Created: 05/31/2012 18:03:38
-- Generated from EDMX file: C:\Users\Jb\Documents\Visual Studio 11\Projects\MVC3EF4\MVC3EF4\Models\PersonContactInfoModel.edmx
-- --------------------------------------------------
SET QUOTED_IDENTIFIER OFF;
GO
USE [MVC3EF4];
GO
IF SCHEMA_ID(N'dbo') IS NULL EXECUTE(N'CREATE SCHEMA [dbo]');
GO
-- --------------------------------------------------
-- Dropping existing FOREIGN KEY constraints
-- --------------------------------------------------
IF OBJECT_ID(N'[dbo].[FK_PersonContactInfo]', 'F') IS NOT NULL
ALTER TABLE [dbo].[ContactInfoes] DROP CONSTRAINT [FK_PersonContactInfo];
GO
-- --------------------------------------------------
-- Dropping existing tables
-- --------------------------------------------------
IF OBJECT_ID(N'[dbo].[ContactInfoes]', 'U') IS NOT NULL
DROP TABLE [dbo].[ContactInfoes];
GO
IF OBJECT_ID(N'[dbo].[People]', 'U') IS NOT NULL
DROP TABLE [dbo].[People];
GO
-- --------------------------------------------------
-- Creating all tables
-- --------------------------------------------------
-- Creating table 'ContactInfoes'
CREATE TABLE [dbo].[ContactInfoes] (
[PersonId] int NOT NULL,
[Email] nvarchar(120) NOT NULL
);
GO
-- Creating table 'People'
CREATE TABLE [dbo].[People] (
[PersonId] int IDENTITY(1,1) NOT NULL,
[Name] nvarchar(50) NOT NULL
);
GO
-- --------------------------------------------------
-- Creating all PRIMARY KEY constraints
-- --------------------------------------------------
-- Creating primary key on [PersonId] in table 'ContactInfoes'
ALTER TABLE [dbo].[ContactInfoes]
ADD CONSTRAINT [PK_ContactInfoes]
PRIMARY KEY CLUSTERED ([PersonId] ASC);
GO
-- Creating primary key on [PersonId] in table 'People'
ALTER TABLE [dbo].[People]
ADD CONSTRAINT [PK_People]
PRIMARY KEY CLUSTERED ([PersonId] ASC);
GO
-- --------------------------------------------------
-- Creating all FOREIGN KEY constraints
-- --------------------------------------------------
-- Creating foreign key on [PersonId] in table 'ContactInfoes'
ALTER TABLE [dbo].[ContactInfoes]
ADD CONSTRAINT [FK_PersonContactInfo]
FOREIGN KEY ([PersonId])
REFERENCES [dbo].[People]
([PersonId])
ON DELETE CASCADE ON UPDATE NO ACTION;
GO
-- --------------------------------------------------
-- Creating FOREIGN KEY Relationship Documentation
-- --------------------------------------------------
EXEC sys.sp_addextendedproperty @name=N'MS_Description',
@value=N'One-to-One or One-to-None' ,
@level0type=N'SCHEMA',@level0name=N'dbo',
@level1type=N'TABLE',@level1name=N'ContactInfoes',
@level2type=N'CONSTRAINT',@level2name=N'FK_PersonContactInfo'
GO
-- --------------------------------------------------
-- Script has ended
-- --------------------------------------------------