c# - 如果我将虚拟关键字添加到属性,我需要帮助解决为什么实体框架不会保存
问题描述
我创建了一个名为 Random Data Generator 的开源项目: https ://github.com/DataJuggler/RandomDataGenerator 。
我还发布了一个简短的 6 分钟视频,以便您了解这是否值得克隆:https ://youtu.be/7XEXWhEW_Fw
Random Data Generator 是一个示例项目,用于演示我的开源项目 DataTier.Net 与 Entity Framework 相比有多快,因为 DataTier.Net 使用所有存储过程。
这个项目已经完成并且正在运行,但是在我开始吹嘘 DataTier.Net ( https://github.com/DataJuggler/DataTier.Net ) 比 EF 保存速度快 x 倍之前,我想确保我提供了 EF 属性表示。我承认我从我的另一个项目中复制了数据上下文并对其进行了修改,但可能遗漏了一些东西。
如果任何超级英雄(EF Man?)Entity Framework 爱好者有几分钟的时间来看看它可能会节省模糊的答案,因为代码和数据库已发布。
我想这是因为我累了,但是如果我将 virtual 关键字添加到 Address 属性中,Entity Framework 将停止保存 Member 对象,所以我必须先通过一次来保存 Member,然后再通过另一次来保存 Address。
(停止保存任何东西)
public virtual Address Address
{
get; set;
}
我在两个表之间建立了关系(我认为):
(这可能更容易使用 [RandomData] GO
/****** Object: Table [dbo].[Address] Script Date: 8/16/2019 2:57:43 AM
******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Address](
[Id] [int] IDENTITY(1,1) NOT NULL,
[MemberId] [int] NOT NULL,
[StreetAddress] [nvarchar](255) NOT NULL,
[Unit] [nvarchar](10) NULL,
[City] [nvarchar](50) NOT NULL,
[StateId] [int] NOT NULL,
[ZipCode] [nvarchar](10) NOT NULL,
CONSTRAINT [PK_Address] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
/****** Object: Table [dbo].[Member] Script Date: 8/16/2019 2:57:43 AM
******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Member](
[Id] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [nvarchar](25) NOT NULL,
[LastName] [nvarchar](25) NOT NULL,
[Active] [bit] NOT NULL,
CONSTRAINT [PK_Member] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Member] ADD CONSTRAINT [DF_Member_Active] DEFAULT ((1))
FOR
[Active]
GO
ALTER TABLE [dbo].[Address] WITH CHECK ADD CONSTRAINT [FK_Address_Member]
FOREIGN KEY([MemberId])
REFERENCES [dbo].[Member] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Address] CHECK CONSTRAINT [FK_Address_Member]
GO
ALTER TABLE [dbo].[Address] WITH CHECK ADD CONSTRAINT [FK_Address_State]
FOREIGN KEY([StateId])
REFERENCES [dbo].[State] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Address] CHECK CONSTRAINT [FK_Address_State]
GO
谢谢你的帮助。
解决方案
virtual
与问题无关(它控制与此处无关的延迟加载行为)。示例 EF 模型与示例数据库模型根本不匹配。在使用现有数据库时,实体模型的正确映射对于 EF 正常运行至关重要。
因为所有 EF 运行时行为(查询生成、插入/更新/删除操作及其执行顺序等)都是基于根据约定、数据注释和流畅配置构建的实体模型,而不是实际的数据库。
让您当前的实体模型有问题(删除所有不必要的显式字段/属性):
public partial class Member
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public bool Active { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[NotMapped]
public Address Address { get; set; }
}
public partial class Address
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string City { get; set; }
public int MemberId { get; set; }
public int StateId { get; set; }
public string StreetAddress { get; set; }
public string ZipCode { get; set; }
public string Unit { get; set; }
[NotMapped]
public bool IsNew => this.Id < 1;
}
PK 和原始属性都可以。Member
但是和之间的关系Address
不是,这就妨碍了正确保存数据。
在 EF 中,引用另一个实体或另一个实体集合的属性称为导航属性,并表示关系的对应端以及基数(一个或多个)。
在您的情况下,导航属性是Member.Address
. [NotMapped]
实际上,如果没有属性,它本来会是。使用该属性,您将告诉 EF 在所有操作中忽略该属性,就像它不存在一样。
在这种情况下的效果是,从 EF 的角度来看和之间没有关系,因此 EF 无法正确处理插入操作,这需要获取实际插入的标识值并将其用作依赖实体记录插入中的 FK。从上面生成EF迁移可以看出:Address
Member
CreateTable(
"dbo.Address",
c => new
{
Id = c.Int(nullable: false, identity: true),
City = c.String(),
MemberId = c.Int(nullable: false),
StateId = c.Int(nullable: false),
StreetAddress = c.String(),
ZipCode = c.String(),
Unit = c.String(),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.Member",
c => new
{
Id = c.Int(nullable: false, identity: true),
Active = c.Boolean(nullable: false),
FirstName = c.String(),
LastName = c.String(),
})
.PrimaryKey(t => t.Id);
如您所见,只有创建带有列的表,但没有 FK 约束。
所以第一步将是删除该[NotMapped]
属性。现在真正的问题来了。数据库中对MemberId
column没有唯一约束的FK关系意味着一对多的关系,即一个Member
可以有多个Address
es,因此导航属性不能是单个的Address
,而是集合:
public partial class Member
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public bool Active { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Address> Addresses { get; set; }
}
现在 EF 迁移完全匹配数据库(注意.ForeignKey
声明):
CreateTable(
"dbo.Address",
c => new
{
Id = c.Int(nullable: false, identity: true),
City = c.String(),
MemberId = c.Int(nullable: false),
StateId = c.Int(nullable: false),
StreetAddress = c.String(),
ZipCode = c.String(),
Unit = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Member", t => t.MemberId, cascadeDelete: true)
.Index(t => t.MemberId);
CreateTable(
"dbo.Member",
c => new
{
Id = c.Int(nullable: false, identity: true),
Active = c.Boolean(nullable: false),
FirstName = c.String(),
LastName = c.String(),
})
.PrimaryKey(t => t.Id);
现在模型与数据库匹配,您可以创建成员以及一个或多个关联地址。但这需要更改您的代码,该代码似乎假定成员具有零个或一个地址,即一对一的关系。
如果一对一是预期的关系,那么数据库设计是不合适的。对于这种情况(EF 自然支持)最好的是所谓的共享主键关联MemberId
,其中的 PKAddress
也用作FK,而不是单独的 FK。
这将是 EF 的最佳设计。不幸的是,这需要更改数据库设计,因此不能与您用于比较的通用数据库一起使用。
有一种方法可以保持数据库设计不变并创建所需的一对一映射(尽管在技术上并未在数据库中强制执行)。这需要映射 Address
属性Member
作为参考导航属性MemberId
,从类中删除显式 FK 属性(EF6 限制)并使用流式 APIAddress
将 FK 列映射为阴影属性:
public partial class Member
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public bool Active { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
//[NotMapped] <-- remove this
public Address Address { get; set; }
}
public partial class Address
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string City { get; set; }
//public int MemberId { get; set; } <-- remove this
public int StateId { get; set; }
public string StreetAddress { get; set; }
public string ZipCode { get; set; }
public string Unit { get; set; }
[NotMapped]
public bool IsNew => this.Id < 1;
}
和内部OnModelCreation
覆盖:
modelBuilder.Entity<Member>()
.HasOptional(e => e.Address)
.WithRequired()
.Map(m => m.MapKey("MemberId"))
.WillCascadeOnDelete(true);
现在这样的代码将同时创建Member
和关联Address
:
var member = new Member
{
FirstName = "FN1",
LastName = "LN1",
Address = new Address
{
City = "C1",
Unit = "U1",
ZipCode = "ZC1",
StreetAddress = "SA1",
}
};
dbContext.Set<Member>().Add(member);
dbContext.SaveChanges();
回顾一下:
EF 对定义关系及其基数的方式更加严格,因为许多运行时行为都依赖于此。为了得到正确的操作处理,正确的映射是必须的。
另外作为旁注,CUD 性能绝对不是 EF 的优势之一,因此将它与另一个库恕我直言进行比较没有多大意义。它还有许多其他优点,如果需要,可以通过一些用于批量插入/更新/删除的第 3 方扩展来显着提高 CUD 操作的性能。
推荐阅读
- python - 如何使用所有包在 pycharm 中创建 anaconda python 环境?
- javascript - 递归函数没有响应
- asp.net-core - 无法在 Hangfire 作业中使用依赖注入
- java - 将 TreeMap 转换为两个数组,转换问题
- java - 为什么这段代码一直显示错误
- python - 实验功能
- python - “当真”循环不运行
- python - 为什么我得到 TypeError: __init__() missing 1 required positional argument: 'master' in Python
- vue.js - Vue 3 什么是基于类的 API、基于函数的 API、反应性 API 和组合 API
- android - 单个事件的事件侦听器多次调用相同的值