c# - HasRequired 和 WithOptional 不生成一对一关系
问题描述
我有两个非常简单的对象:
public class GenericObject
{
public int Id { get; set; }
public string description { get; set; }
public User UserWhoGeneratedThisObject { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Lastname { get; set; }
}
我重写了 OnModelCreating 函数来声明对象的关系:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<GenericObject>().HasRequired(u => u.UserWhoGeneratedThisObject)
.WithOptional();
}
然后,在我的应用程序中,我执行以下操作:
using (var ctx = new TestContext())
{
GenericObject myObject = new GenericObject();
myObject.description = "testobjectdescription";
User usr = new User() { Name = "Test"};
ctx.Users.Add(usr);
//myObject.UserWhoGeneratedThisObject = usr;
ctx.GenericObjects.Add(myObject);
ctx.SaveChanges();
}
当我将 myObject 添加到数据库时,我不应该遇到异常吗,因为我没有为它分配一个 User 对象?(注释掉的行)我希望在这种情况下看到异常,但代码工作正常。更糟糕的是,如果我在 ctx.SaveChanges() 之后添加以下两行:
var u = ctx.GenericObjects.Find(1);
System.Console.WriteLine(u.ToString());
我看到作为 GenericObjects 的变量 u 实际上指向我在数据库中创建的用户,即使我没有将用户分配给 GenericObject。
解决方案
TL;博士
此行为并非特定于one-to-one
关系,one-to-many
其行为方式完全相同。
如果你想让 EF 用你当前的代码抛出一个异常,然后映射单独的外键GenericObject
modelBuilder
.Entity<GenericObject>()
.HasRequired(u => u.UserWhoGeneratedThisObject)
.WithOptional()
.Map(config =>
{
config.MapKey("UserId");
});
实际答案
实际上one-to-many
,关系遭受完全相同的问题。让我们做一些测试。
一对一
public class GenericObject
{
public int Id { get; set; }
public string Description { get; set; }
public UserObject UserWhoGeneratedThisObject { get; set; }
}
public class UserObject
{
public int Id { get; set; }
public string Name { get; set; }
}
配置
modelBuilder
.Entity<GenericObject>()
.HasRequired(u => u.UserWhoGeneratedThisObject)
.WithOptional();
在这种情况下GenericObject.Id
,主键和外键UserObject
同时引用实体。
测试1.只创建GenericObject
var context = new AppContext();
GenericObject myObject = new GenericObject
{
Description = "some description"
};
context.GenericObjects.Add(myObject);
context.SaveChanges();
执行查询
INSERT [dbo].[GenericObjects]([Id], [Description])
VALUES (@0, @1)
-- @0: '0' (Type = Int32)
-- @1: 'some description' (Type = String, Size = -1)
例外
INSERT 语句与 FOREIGN KEY 约束“FK_dbo.GenericObjects_dbo.UserObjects_Id”冲突。冲突发生在数据库“TestProject”、表“dbo.UserObjects”、列“Id”中。该语句已终止。
因为GenericObject
是唯一实体 EF 执行查询并且它失败,因为数据库中insert
没有等于。UserObject
Id
0
测试 2. 创建 1 个 UserObject 和 一个 GenericObject
var context = new AppContext();
GenericObject myObject = new GenericObject
{
Description = "some description"
};
UserObject user = new UserObject
{
Name = "user"
};
context.UserObjects.Add(user);
context.GenericObjects.Add(myObject);
context.SaveChanges();
执行的查询
INSERT [dbo].[UserObjects]([Name])
VALUES (@0)
SELECT [Id]
FROM [dbo].[UserObjects]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: 'user' (Type = String, Size = -1)
INSERT [dbo].[GenericObjects]([Id], [Description])
VALUES (@0, @1)
-- @0: '10' (Type = Int32)
-- @1: 'some description' (Type = String, Size = -1)
现在上下文包含UserObject
(Id = 0) 和GenericObject
(Id = 0)。EF 认为对GenericObject
的引用是UserObject
因为它的外键等于 0 并且UserObject
主键等于 0。所以首先 EFUserObject
作为主体插入,并且因为它考虑GenericObject
依赖于该用户,所以它需要返回UserObject.Id
并执行第二次插入,一切都很好。
测试 3. 创建 2 个 UserObject 和一个 GenericObject
var context = new AppContext();
GenericObject myObject = new GenericObject
{
Description = "some description"
};
UserObject user = new UserObject
{
Name = "user"
};
UserObject user2 = new UserObject
{
Name = "user"
};
context.UserObjects.Add(user);
context.UserObjects.Add(user2);
context.GenericObjects.Add(myObject);
context.SaveChanges();
例外
EntityFramework.dll 中发生“System.Data.Entity.Infrastructure.DbUpdateException”
附加信息:无法确定“TestConsole.Data.GenericObject_UserWhoGeneratedThisObject”关系的主体端。多个添加的实体可能具有相同的主键。
EF 看到UserObject
上下文中有 2 与Id
equals0
和GenericObject.Id
equals 0,因此框架无法明确连接实体,因为有多种可能的选项。
一对多
public class GenericObject
{
public int Id { get; set; }
public string Description { get; set; }
public int UserId { get; set; } //this property is essential to illistrate the problem
public UserObject UserWhoGeneratedThisObject { get; set; }
}
public class UserObject
{
public int Id { get; set; }
public string Name { get; set; }
}
配置
modelBuilder
.Entity<GenericObject>()
.HasRequired(o => o.UserWhoGeneratedThisObject)
.WithMany()
.HasForeignKey(o => o.UserId);
在这种情况下GenericObject
,具有单独UserId
的外键引用UserObject
实体。
测试1.只创建GenericObject
与中相同的代码one-to-one
。它执行相同的查询并产生相同的异常。原因是一样的。
测试 2. 创建 1 个 UserObject 和 一个 GenericObject
与中相同的代码one-to-one
。
执行的查询
INSERT [dbo].[UserObjects]([Name])
VALUES (@0)
SELECT [Id]
FROM [dbo].[UserObjects]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: 'user' (Type = String, Size = -1)
-- Executing at 14.03.2019 18:52:35 +02:00
INSERT [dbo].[GenericObjects]([Description], [UserId])
VALUES (@0, @1)
SELECT [Id]
FROM [dbo].[GenericObjects]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: 'some description' (Type = String, Size = -1)
-- @1: '3' (Type = Int32)
执行的查询与测试 2 非常相似one-to-one
。唯一的区别是现在GenericObject
有单独的UserId
外键。推理几乎是一样的。上下文包含具有等于和等于现在的UserObject
实体,因此 EF 认为它们已连接并执行插入,然后它需要并执行第二次插入。Id
0
GenericObject
UserId
UserObject
UserObject.Id
测试 3. 创建 2 个 UserObject 和一个 GenericObject
与中相同的代码one-to-one
。它执行相同的查询并产生相同的异常。原因是一样的。
正如您从这些测试中看到的那样,问题并非特定于one-to-one
关系。
但是如果我们不添加怎么UserId
办GenericObject
?在这种情况下,EF 将为UserWhoGeneratedThisObject_Id
我们生成外键,现在数据库中有外键但没有映射到它的属性。在这种情况下,每个测试都会立即抛出以下异常
“AppContext.GenericObjects”中的实体参与“GenericObject_UserWhoGeneratedThisObject”关系。找到 0 个相关的“GenericObject_UserWhoGeneratedThisObject_Target”。1 'GenericObject_UserWhoGeneratedThisObject_Target' 是预期的。
为什么会发生这种情况?现在 EF 无法确定GenericObject
和是否UserObject
已连接,因为 上没有外键属性GenericObject
。在这种情况下,EF 只能依赖导航属性UserWhoGeneratedThisObject
,null
因此会引发异常。
one-to-one
这意味着如果您可以在数据库中有外键但没有属性映射到它的情况下实现这种情况, EF 将为您的问题中的代码抛出相同的异常。通过更新配置很容易完成
modelBuilder
.Entity<GenericObject>()
.HasRequired(u => u.UserWhoGeneratedThisObject)
.WithOptional()
.Map(config =>
{
config.MapKey("UserId");
});
该Map
方法告诉 EFUserId
在GenericObject
. 通过此更改,您问题中的代码将引发异常
using (var ctx = new TestContext())
{
GenericObject myObject = new GenericObject();
myObject.description = "testobjectdescription";
User usr = new User() { Name = "Test"};
ctx.Users.Add(usr);
//myObject.UserWhoGeneratedThisObject = usr;
ctx.GenericObjects.Add(myObject);
ctx.SaveChanges();
}
“AppContext.GenericObjects”中的实体参与“GenericObject_UserWhoGeneratedThisObject”关系。找到 0 个相关的“GenericObject_UserWhoGeneratedThisObject_Target”。1 'GenericObject_UserWhoGeneratedThisObject_Target' 是预期的。
推荐阅读
- php - 在 Shopware 中创建主题期间,我在 Theme.php 中做错了什么配置主题按钮被禁用?
- javascript - .find() 在 DOMNodeInserted 上无法按预期工作
- c - TunTap 设备接收 ping
- php - Docusign 从模板下载文件
- oauth-2.0 - Azure Active Directory 的 OAuth 客户端收到 AADSTS70002:验证凭据时出错
- .net - 使用通过 LINQ 查询的 SQL Server 中的超时异常
- php - Joomla HTML上的MathJax扩展使括号斜体或强调文本
- linux - rmmod时是否需要devm_iounmap,哪个模块使用devm * api?
- angularjs - 量角器角度js
- python - 在 python 3.6 子进程中运行 awk 日期转换