簡體   English   中英

HasRequired和WithOptional不會產生一對一的關系

[英]HasRequired and WithOptional not generating one-to-one relationship

我有兩個非常簡單的對象:

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。

你應該閱讀: .WithMany()和.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推斷出處於相同狀態的兩個實體之間可能的唯一關系。 在其他情況下,他拋出。

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查詢的唯一實體,因此在數據庫中沒有Id等於0 UserObject時失敗。

測試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.因此,首先EF將UserObject作為主體插入,因為它認為GenericObject依賴於該用戶,它返回UserObject.Id並執行第二次插入它和eveything很好。

測試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看到上下文中有2個UserObjectId等於0GenericObject.Id等於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)

執行的查詢非常類似於one-to-one測試2.唯一的區別是現在GenericObject具有單獨的UserId外鍵。 推理是完全一樣的。 上下文包含Id等於0 UserObject實體和UserId等於的GenericObject ,因此EF認為它們已連接並執行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無法確定是否連接了GenericObjectUserObject ,因為GenericObject上沒有外鍵屬性。 在這種情況下,EF只能依賴導航屬性UserWhoGeneratedThisObject ,該屬性為null ,因此引發異常。

這意味着如果在數據庫中存在外鍵但是沒有屬性映射到它時,您可以one-to-one實現這種情況,EF將為您的問題中的代碼拋出相同的異常。 通過更新配置很容易實現

modelBuilder
    .Entity<GenericObject>()
    .HasRequired(u => u.UserWhoGeneratedThisObject)
    .WithOptional()
    .Map(config =>
    {
        config.MapKey("UserId");
    });

Map方法告訴EF在GenericObject創建單獨的UserId外鍵。 通過此更改,您的問題中的代碼將引發異常

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'是預期的。

您需要設置兩個導航屬性:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
   modelBuilder.Entity<GenericObject>()
      .HasRequired(genericObject => genericObject.UserWhoGeneratedThisObject)
      .WithOptional(user => user.GenericObject);
}

我將myObject添加到數據庫時不應該遇到異常,因為我沒有為它分配一個User對象嗎? (注釋掉的行)

我認為不是因為沒有在您的“OnModelCreating”函數實現的數據庫中創建一對一的關系。 應創建將通過“[ForeignKey]”屬性標記為外鍵的GenericObjectId屬性,以在“User”類中創建一對一關系。 應將“GenericObject”屬性添加到“User”類並使用virtual modifier標記。 現在,您的“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; }
    }

關系將被映射后,將出現搜索異常:

應該首先創建通用對象,並且應該將創建的對象id添加到新用戶以避免此異常(如果您將外鍵放入用戶,則會出現這種情況)。

希望我清楚地回答你的問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM