-2

I'm trying to seed some data into database when I startup the server. But I get a NullReferenceException when I'm trying to insert user data.

The exception happens on the line

userManager.CreateAsync(user, "P@ssw0rd");

But I've checked that none of the objects in that line are null.

Please give me some advice, thanks in advance.

Here is the code:

public static async Task SeedUsers(UserManager<AppUser> userManager,
    RoleManager<AppRole> roleManager, DataContext context)
{
    // only seed user when no data in database
    if (await userManager.Users.AnyAsync()) return;

    var userData = await File.ReadAllTextAsync("Data/Users.json");

    var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };

    var users = JsonSerializer.Deserialize<List<AppUser>>(userData);

    var roles = new List<AppRole>
    {
        new AppRole { Name = "Member" },
        new AppRole { Name = "Admin" },
        new AppRole { Name = "Moderator" },
    };

    foreach (var role in roles)
    {
        await roleManager.CreateAsync(role);
    }

    int langCount = context.Languages.Count(); // Get the total number of rows in the table
    int countryCount = context.Countries.Count();

    foreach (var user in users)
    {
        Random random = new Random();
        int randIndex1 = random.Next(0, langCount);
        int randIndex2 = random.Next(0, langCount);
        int randCountryIndex = random.Next(0, countryCount);
        int desiredRowCount = random.Next(1, 5);

        var langRows1 = context.Languages
            .Skip(randIndex1)
            .Take(desiredRowCount)
            .ToList();

        var langRows2 = context.Languages
            .Skip(randIndex2)
            .Take(desiredRowCount)
            .ToList();

        var country = context.Countries
            .Skip(randCountryIndex)
            .Take(1)
            .FirstOrDefault();

        user.UserName = user.UserName.ToLower();
        // specify utc datetime or it will throw error on postgresSQL
        user.Created = DateTime.SpecifyKind(user.Created, DateTimeKind.Utc);
        user.LastActive = DateTime.SpecifyKind(user.LastActive, DateTimeKind.Utc);
        user.Languages = langRows1;
        user.InterestedLanguages = langRows2;
        user.CountryId = country.Id;

        context.Entry(user).State = EntityState.Added;

        await userManager.CreateAsync(user, "P@ssw0rd");
        await userManager.AddToRoleAsync(user, "Member");
    }

    var admin = new AppUser
    {
        UserName = "admin"
    };

    await userManager.CreateAsync(admin, "P@ssw0rd");
    await userManager.AddToRolesAsync(admin, new[] { "Admin", "Moderator" });
}

Log:

fail: Program[0]
      An error occurred during migration
      System.NullReferenceException: Object reference not set to an instance of an object.
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.InitialFixup(InternalEntityEntry entry, InternalEntityEntry duplicateEntry, Boolean fromQuery)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.FireStateChanged(EntityState oldState)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey, Nullable`1 fallbackState)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
         at Microsoft.EntityFrameworkCore.DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)
         at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState)
         at Microsoft.EntityFrameworkCore.DbContext.Add[TEntity](TEntity entity)
         at Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9.CreateAsync(TUser user, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Identity.UserManager`1.CreateAsync(TUser user)
         at Microsoft.AspNetCore.Identity.UserManager`1.CreateAsync(TUser user, String password)
         at API.Data.Seed.SeedUsers(UserManager`1 userManager, RoleManager`1 roleManager, DataContext context) in /Users/shaoyulin/SoftwareDev/CsharpProjects/LangEx/API/Data/Seed.cs:line 74

my json looks like this:

[
  {
    "UserName": "rvasyutichev4",
    "Gender": "female",
    "DateOfBirth": "1988-09-26",
    "KnownAs": "rvasyutichev4",
    "Created": "2022-07-17",
    "LastActive": "2023-01-02",
    "Introduction": "Duis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus.",
    "CountryId": 79,
    "Photo": [
      {
        "Url": "https://randomuser.me/api/portraits/women/321.jpg",
        "IsMain": true
      }
    ]
  }
]

and here is the AppUser:

public class AppUser : IdentityUser<int>
{
    public DateOnly DateOfBirth { get; set; }
    public string? KnownAs { get; set; }
    public DateTime Created { get; set; } = DateTime.UtcNow;
    public DateTime LastActive { get; set; } = DateTime.UtcNow;
    public string? Gender { get; set; }
    public string? Introduction { get; set; }

    public int CountryId { get; set; }
    public Country Country { get; set; }
    public List<Language> Languages { get; set; } = new();

    public List<Language> InterestedLanguages { get; set; } = new();
    public List<Photo> Photos { get; set; } = new();

    public List<UserLike> LikedByUsers { get; set; }
    public List<UserLike> LikedUsers { get; set; }

    public List<Message> MessagesSent { get; set; }
    public List<Message> MessagesReceived { get; set; }

    public ICollection<AppUserRole> UserRoles { get; set; }
}
Ian
  • 342
  • 1
  • 3
  • 14
  • 1
    What does your user JSON look like? – CodeCaster Jul 01 '23 at 08:36
  • 1
    -> `context.Entry(user).State = EntityState.Added;` <- why did you add this line? You're telling EF the user has already been created before you call create... what made you decide to do that? In any case, we need a [mcve]. – JHBonarius Jul 01 '23 at 08:40
  • @CodeCaster I've edit the question, please check it. – Ian Jul 01 '23 at 16:14
  • 2
    People, don't use [What is a NullReferenceException, and how do I fix it?](https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) as a duplicate target for exceptions thrown from library code. – CodeCaster Jul 01 '23 at 16:35
  • Still, what is `context.Entry(user).State = EntityState.Added;` doing there? – JHBonarius Jul 01 '23 at 16:36
  • @JHBonarius yea I should remove that. – Ian Jul 01 '23 at 16:37
  • And does that change the behavior? – JHBonarius Jul 01 '23 at 16:39
  • no it doesn't, still got the exception – Ian Jul 01 '23 at 16:49
  • 1
    Since you've verified these objects aren't null and we can see from the stack trace that it's coming from deep within the EF library, there's a few possibilities. Perhaps you have set up something wrong and then the problem manifests itself later (generally the library should guard against that), or you've got mismatched DLL versions, perhaps with some binding redirects. Or there's just a bug in EF that only manifests under certain conditions. Creating a true [mcve] will help narrow this down. – mason Jul 01 '23 at 16:57
  • And start with a fresh database... the error says "an error occurred during migration" which could indicate incompatible data in your existing database. – JHBonarius Jul 01 '23 at 19:46
  • yea I already done that, still same. I've create a project to replicate this error, please check it here: https://github.com/SYLin117/LangEx/tree/main/ErrorProject – Ian Jul 01 '23 at 22:36
  • @JHBonarius uhh, the "an error occurred during migration" is printing by me. Forgot to mention that. – Ian Jul 02 '23 at 05:21
  • checked it out an can reproduce the issue. I'll have a look. First this I notice: `options` is never passed to the deserializer. But that doesn't fix the issue – JHBonarius Jul 03 '23 at 07:25

1 Answers1

1

After loading the symbols from the microsoft resource, it can be seen that the exception is thrown by Entity Framework when trying to get the inverse of the Skip Navigation of the Language entity collection. enter image description here

A Skip Navigation is part of the many-to-many relationship. Apparently Entity Framework is not able to determine the inverse skip navigation.

You've enabled skip navigation by defining the many to many relationship between User and Language with the join tables like

builder.Entity<AppUser>()
    .HasMany(s => s.Languages)
    .WithMany(c => c.Users)
    .UsingEntity<AppUserLanguage>(
        j => j.HasOne(e => e.Language).WithMany(),
        j => j.HasOne(e => e.User).WithMany()
    );
builder.Entity<AppUser>()
    .HasMany(s => s.InterestedLanguages)
    .WithMany(c => c.Users)
    .UsingEntity<AppUserInterestedLanguage>(
        j => j.HasOne(e => e.Language).WithMany(),
        j => j.HasOne(e => e.User).WithMany()
    );

I'm not sure what you are trying to do there, but just removing the parameters of the .UsingEntity part ( the j=> ...) will resolve the null reference exception.

JHBonarius
  • 10,824
  • 3
  • 22
  • 41