简体   繁体   中英

Entity Framework DbContext Confusion When two foreign keys pointing at same table

public partial class MyDbContext : DbContext
{
    public MyDbContext()
        : base("name=Model1")
    {
    }

    public virtual DbSet<User> Users { get; set; }
    public virtual DbSet<UserGroup> UserGroups { get; set; }
    public virtual DbSet<UserGroupMember> UserGroupMembers { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder) { }
}

[Table("gnr.UserGroup")]
public partial class UserGroup
{
    public UserGroup()
    {
        ChildUserGroups = new HashSet<UserGroupMember>();
        UserGroupMembers = new HashSet<UserGroupMember>();
    }

    public long ID { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }

    [InverseProperty("ChildUserGroup")]
    public virtual ICollection<UserGroupMember> ChildUserGroupMembers { get; set; }

    [InverseProperty("UserGroup")]
    public virtual ICollection<UserGroupMember> UserGroupMembers { get; set; }
}

[Table("gnr.UserGroupMember")]
public partial class UserGroupMember
{
    public long ID { get; set; }
    public long? UserID { get; set; }
    public virtual User User { get; set; }

    [ForeignKey("UserGroup")]
    public long UserGroupID { get; set; }
    public virtual UserGroup UserGroup { get; set; }

    [ForeignKey("ChildUserGroup")]
    public long? ChildUserGroupID { get; set; }
    public virtual UserGroup ChildUserGroup { get; set; }

}

[Table("gnr.User")]
public partial class User
{
    public User()
    {
        UserGroupMembers = new HashSet<UserGroupMember>();
    }

    public long ID { get; set; }
    public string Username { get; set; }
    public string MobileNumber { get; set; }
    public virtual ICollection<UserGroupMember> UserGroupMembers { get; set; }
}


        static void Main(string[] args)
    {
         WorkNotCorrectly();

        Console.WriteLine("Done");

        Console.ReadLine();
    }

    private static void WorkNotCorrectly()
    {
        using (var db = new MyDbContext())
        {
            var ug = new UserGroup { Title = "It's a new UserGroup 1" };

            var cugm = new UserGroupMember { ChildUserGroupID = 1 };

            ug.ChildUserGroupMembers.Add(cugm);

            // After This Line
            db.UserGroups.Add(ug);

The MyDbContext sets the 'ChildUserGroup' property of the 'cugm' object to the 'ug'

But the expected behaviour is to set the 'UserGroup' property of 'cugm' object to the 'ug'

'ug' object To Json:

{
"ID": 0,
"Title": "It's a new UserGroup 1",
"Description": null,
"ChildUserGroupMembers": [
    {
        "ID": 0,
        "UserID": null,
        "User": null,
        "UserGroupID": 0,
        "UserGroup": null,
        "ChildUserGroup": HERE IS THE PROBLEM => The MyDbContext sets the 'ChildUserGroup' property instead of 'UserGroup' property,
        "ChildUserGroupID": 1 
    }
],
"UserGroupMembers": []
}

And the SaveChanges:

            db.SaveChanges();
        }
    }

'ug' object To Json After SaveChanges:

    {
    "ID": 2,
    "Title": "It's a new UserGroup 1",
    "Description": null,
    "ChildUserGroupMembers": [
        {
            "ID": 1,
            "UserID": null,
            "User": null,
            "UserGroupID": 2,
            "ChildUserGroupID": 2 WHAT???? IT IS CHANGED TO 2 ( THE NEW UserGroup THAT IS GENERATED),
            "ChildUserGroup": HERE IS THE PROBLEM => The MyDbContext sets the 'ChildUserGroup' property instead of 'UserGroup' property 
        }
    ],
    ????? WHAT IS THIS??? WHY UserGroupMembers PROPERTY IS FILLED
    "UserGroupMembers": [ 
        {
            "ID": 1,
            "UserID": null,
            "User": null,
            "UserGroupID": 2,
            "ChildUserGroupID": 2
        }
    ]
}

The Expected behavior and result is :

    {
    "ID": 2,
    "Title": "It's a new UserGroup 1",
    "Description": null,
    "ChildUserGroupMembers": [
        {
            "ID": 1,
            "UserID": null,
            "User": null,
            "UserGroupID": 2,
            "UserGroup": 'The ug object with ID of 2 ',
            "ChildUserGroupID": 1
        }
    ]
}

Result in SSMS and the relationship diagram

It seems that the EF confused to identify the correct FK,The one which has to fill by itself after inserting the object and the one that is filled by me??!!

Update: This is the relation between entities:

  1. UserGroupMember is a bridge table between UserGroup and User

  2. Each User can be member of 1...* UserGroup

  3. Each UserGroup can have 1...* User as member

(until here its like a normal bridge table to represent a many to many relationship)

4. Each UserGroup can have 1...* ChildUserGroup as a member

(this means that a UserGroup can have a User or a ChildUserGroup as member, something like 'Users and Groups' of Windows)

Sincerely.

The main issue here, your code is setting incorrect navigation properties than what you are expecting.

First you are using InverseProperty attributes to define your relationships.

  • UserGroup.ChildUserGroupMembers & UserGroupMember.ChildUserGroup constitute one relationship with foreign key property as ChildUserGroupID
  • UserGroup.UserGroupMembers & UserGroupMember.UserGroup constitute another relationship with foreign key property as UserGroupID

In EF when you define a relationship, there are 3 components, principal to dependent navigation, dependent to principal navigation & foreign key property. Setting value of any one of them would determine others too. If you set any navigation then foreign key property will be set to have same value as principal key property. Navigations also get populated if they are already loaded in memory.

You already have data in your UserGroup table with ID = 1 as SSMS output shows. Now in your code,

  • You are creating a new UserGroup ug .
  • You are creating new UserGroupMember cugm .
  • You are setting value of cugm.ChildUserGroupID = 1. Given relationship the entity has, cugm.ChildUserGroup navigation (which is associated with ChildUserGroupID fk), should have UserGroup with ID = 1 assigned to it. Since it is not loaded in memory, that navigation will remain null. Same way if the UserGroup with ID = 1 was loaded in memory than UserGroup.ChildUserGroupMembers collection would add cugm to it.
  • Then you are adding cugm to ug.ChildUserGroupMembers collection. Meaning the FK property cugm.ChildUserGroupID should take value of ug.ID . And cugm.ChildUserGroup which is inverse navigation should point to ug . Since till this point EF not in picture, none of above changes will happen till entity is added.
  • Then you are adding ug to UserGroups db set so EF will start tracking the entity and its children. Since ug.ChildUserGroupMembers collection have cugm added to it, EF will set cugm.ChildUserGroup to ug . ug object still have temporary key hence EF will not copy ug.ID to cugm.ChildUserGroupID

That is how you got your first JSON. It has ChildUserGroup set because you set the inverse navigation of it.

Once you call SaveChanges, EF will save the entity ug to database. Since your database already have Entity with ID = 1, it gets next ID which is 2. Since ug.ID is not temporary value anymore, cugm.ChildUserGroupID will take its value and change to 2 .

Now based on your SSMS data, you want to create new UserGroup object - ug which would get ID = 2 when saved. You want to create new UserGroupMember object - cugm which would get ID = 1 (first row with identity column.) cugm.UserGroupId = 2 so cugm.UserGroup navigation should be set to ug & cugm.ChildUserGroupID = 1.

This is what code should look like

var ug = new UserGroup { Title = "It's a new UserGroup 1" };
var cugm = new UserGroupMember { ChildUserGroupID = 1 };

ug.UserGroupMembers.Add(cugm); // You want to set UserGroupID = 2 so cugm should be added to its corresponding inverse navigation

db.UserGroups.Add(ug);

db.SaveChanges();

Above code will generate exact record as you want in your database.

The JSON output of ug object will be like below (which is also incorrect in your question)

{
    "ID": 2,
    "Title": "It's a new UserGroup 1",
    "Description": null,
    "UserGroupMembers": [{
        "ID": 1,
        "UserID": null,
        "User": null,
        "UserGroupID": 2,
        "UserGroup": ... // Pointing to UserGroup with ID = 2
        "ChildUserGroupID": 1,
        "ChildUserGroup": null
    }]
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM