我有两个非常简单的对象:
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实际上指向了我在数据库中创建的用户,即使我没有将用户分配给GenericObject。
答案 0 :(得分:1)
您应该阅读:Difference between .WithMany() and .WithOptional()?
代码:
using (var ctx = new Tests6Context()) {
GenericObject myObject = new GenericObject();
myObject.description = "testobjectdescription";
User usr = new User() { Name = "Test" };
ctx.Users.Add(usr);
//myObject.UserWhoGeneratedThisObject = usr;
ctx.GenericObjects.Add(myObject);
myObject = new GenericObject();
myObject.description = "testobjectdescription 2";
usr = new User() { Name = "Test 2" };
ctx.Users.Add(usr);
//myObject.UserWhoGeneratedThisObject = usr;
ctx.GenericObjects.Add(myObject);
ctx.SaveChanges();
}
抛出:
System.Data.Entity.Infrastructure.DbUpdateException:无法确定“ ef6tests.GenericObject_UserWhoGeneratedThisObject”关系的主要结尾。添加的多个实体可能具有相同的主键。
在简单情况下,一个新用户和一个新GenericObject EF可以推断处于相同状态的两个实体之间可能的唯一关系。在其他情况下,他会抛出。
答案 1 :(得分:1)
TL; DR
此行为并非专门针对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
的{{1}}。
测试2.创建1个UserObject和一个GenericObject
0
已执行查询
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)
(Id = 0)和UserObject
(Id = 0)。 EF认为GenericObject
引用了GenericObject
,因为它的外键等于0,而UserObject
主键等于0。因此,EF首先插入UserObject
作为主体,并且它认为UserObject
依赖于该用户,因此它返回了GenericObject
并执行了第二次插入操作,并且一切都很好。
测试3.创建2个UserObject和一个GenericObject
UserObject.Id
例外
在EntityFramework.dll中发生了“ System.Data.Entity.Infrastructure.DbUpdateException”
其他信息:无法确定“ TestConsole.Data.GenericObject_UserWhoGeneratedThisObject”关系的主要结尾。添加的多个实体可能具有相同的主键。
EF看到上下文中有2个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();
,其中UserObject
等于Id
,而0
也等于0,所以框架无法肯定地连接实体,因为那里有多个可能的选择。
一对多
GenericObject.Id
配置
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
实体的外键。
测试1.仅创建GenericObject
与UserObject
中的代码相同。它执行相同的查询并产生相同的异常。原因是相同的。
测试2.创建1个UserObject和一个GenericObject
与one-to-one
中的代码相同。
已执行查询
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
的{{1}}实体和Id
等于0
的{{1}},因此EF认为它们已连接并执行GenericObject
的插入取UserId
并执行第二次插入。
测试3.创建2个UserObject和一个GenericObject
与UserObject
中的代码相同。它执行相同的查询并产生相同的异常。原因是相同的。
从这些测试中您可以看到,问题并非特定于UserObject.Id
关系。
但是如果我们不将one-to-one
添加到one-to-one
怎么办?在这种情况下,EF将为我们生成UserId
外键,现在数据库中有外键,但没有映射到它的属性。在这种情况下,每个测试都会立即引发以下异常
“ AppContext.GenericObjects”中的实体参与“ GenericObject_UserWhoGeneratedThisObject”关系。找到0个相关的'GenericObject_UserWhoGeneratedThisObject_Target'。 1'GenericObject_UserWhoGeneratedThisObject_Target是预期的。
为什么会这样?现在,EF无法确定GenericObject
和UserWhoGeneratedThisObject_Id
是否已连接,因为GenericObject
上没有外键属性。在这种情况下,EF只能依赖导航属性UserObject
GenericObject
,因此会引发异常。
这意味着如果数据库中有一个外键但没有属性映射到UserWhoGeneratedThisObject
,如果您可以实现这种情况,则EF将对您问题中的代码抛出相同的异常。通过更新配置很容易完成
null
one-to-one
方法告诉EF在modelBuilder
.Entity<GenericObject>()
.HasRequired(u => u.UserWhoGeneratedThisObject)
.WithOptional()
.Map(config =>
{
config.MapKey("UserId");
});
中创建单独的Map
外键。进行此更改后,您问题中的代码将引发异常
UserId
“ AppContext.GenericObjects”中的实体参与“ GenericObject_UserWhoGeneratedThisObject”关系。找到0个相关的'GenericObject_UserWhoGeneratedThisObject_Target'。 1'GenericObject_UserWhoGeneratedThisObject_Target是预期的。
答案 2 :(得分:0)
您需要设置两个导航属性:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<GenericObject>()
.HasRequired(genericObject => genericObject.UserWhoGeneratedThisObject)
.WithOptional(user => user.GenericObject);
}
答案 3 :(得分:0)
在将myObject添加到数据库中时,我没有遇到异常,因为我没有为其分配User对象? (注释行)
我认为没有使用“ OnModelCreating”函数实现在数据库中创建一对一关系的原因。 应该创建将被“ [ForeignKey]”属性标记为外键的GenericObjectId属性,以在“ User”类中创建一对一关系。应将“ GenericObject”属性添加到“ User”类,并使用虚拟修饰符进行标记。现在,您的“ OnModelCreating”实现将配置这些类之间的关系。 有示例的例子:
public class User
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Lastname { get; set; }
[ForeignKey("GenericObject")]
public int GenericObjectId { get; set; }
public virtual GenericObject GenericObject { get; set; }
}
public class GenericObject
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
public virtual User UserWhoGeneratedThisObject { get; set; }
}
After relations will be mapped, searched exception will be appeared:
首先应创建通用对象,并应将创建的对象ID添加到新用户中,以避免发生此异常(如果您向用户放置外键,则可以避免这种情况。)
希望我能清楚地回答你的问题。