You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 6 MVC数据填充器未生成Category,AppUserId非空约束报错

问题:Category数据填充失败,AppUserId为空约束报错

使用PostgreSQL数据库,AppUser与Contact、Category均为一对多关系。手动创建Category时字段可正常填充,但数据填充器运行时,AppUser和Contact能正常创建,Category却无法生成,终端报错:

null value in column "AppUserId" of relation "Categories" violates not-null constraint

Contact的AppUserId可正确赋值,Category却不行,代码如下:

using ContactPro.Data;
using ContactPro.Models;
using Microsoft.AspNetCore.Identity;

namespace ContactPro.Helpers
{
    public class DataSeeder
    {
        private readonly ApplicationDbContext _dbContext;
        private readonly UserManager<AppUser> _userManager;

        private static string appUserId;

        public DataSeeder(ApplicationDbContext dbContext, UserManager<AppUser> userManager)
        {
            _dbContext = dbContext;
            _userManager = userManager;
        }

        public async Task SeedDataAsync()
        {
            await SeedUsersAsync();
            await SeedContactsAsync();
            await SeedCategoriesAsync();
        }

        private async Task SeedUsersAsync()
        {
            if (_dbContext.Users.Any())
            {
                return;
            }

            var demoUser = new AppUser()
            {
                UserName = "demouser@mail.com",
                Email = "demouser@mail.com",
                FirstName = "Demo",
                LastName = "User",
                EmailConfirmed = true,
            };

            await _userManager.CreateAsync(demoUser, "xxxxxx");
            await _dbContext.SaveChangesAsync();

            appUserId = _userManager.Users.FirstOrDefault(u => u.Email == "demouser@mail.com").Id;
        }

        private async Task SeedContactsAsync()
        {
            if (!_dbContext.Contacts.Any())
            {
                var contact = new List<Contact>()
                {
                    new Contact()
                    {
                        FirstName = "John",
                        LastName = "Dickens",
                        Address1 = "12 Main St.",
                        City = "Concord",
                        States  = ContactPro.Enums.States.NH,
                        ZipCode = 03101,
                        Email = "john@mail.com",
                        AppUserId = appUserId,
                    }
                };

                await _dbContext.Contacts.AddRangeAsync(contact);
                await _dbContext.SaveChanges();
            }
        }

        private async Task SeedCategoriesAsync()
        {
            if (!_dbContext.Categories.Any())
            {
                var category = new List<Category>()
                {
                    new Category()
                    {
                        Name = "_UnCategorized",
                        AppUserId = appUserId,
                    },
                };

                await _dbContext.Categories.AddRangeAsync(category);
                await _dbContext.SaveChanges();
            }
        }
    }
}

原因分析

核心问题出在静态字段appUserId的赋值逻辑漏洞:

  1. 当数据库中已经存在用户数据时,SeedUsersAsync会直接return,跳过appUserId = ...的赋值步骤,导致appUserId保持null。
  2. Contact能正常创建,大概率是因为之前运行填充器时appUserId已经被赋值,静态字段在应用生命周期内会保留值;但应用重启后,或者首次运行时用户已存在,就会触发Category的空值报错。
  3. 另外,_userManager.CreateAsync执行后,demoUser.Id已经自动生成,没必要再通过_userManager.Users.FirstOrDefault查询,这一步不仅多余,还可能因上下文未及时刷新导致查询异常。

修复方案

方案1:移除静态字段,直接传递用户ID(推荐)

把静态appUserId去掉,通过方法参数传递用户ID,避免静态变量的生命周期问题:

// 修改SeedUsersAsync,返回用户ID
private async Task<string> SeedUsersAsync()
{
    var existingUser = await _userManager.FindByEmailAsync("demouser@mail.com");
    if (existingUser != null)
    {
        return existingUser.Id;
    }

    var demoUser = new AppUser()
    {
        UserName = "demouser@mail.com",
        Email = "demouser@mail.com",
        FirstName = "Demo",
        LastName = "User",
        EmailConfirmed = true,
    };

    await _userManager.CreateAsync(demoUser, "xxxxxx");
    // 无需额外调用SaveChanges,CreateAsync内部已处理
    return demoUser.Id;
}

// 修改SeedDataAsync,传递用户ID给后续方法
public async Task SeedDataAsync()
{
    var appUserId = await SeedUsersAsync();
    await SeedContactsAsync(appUserId);
    await SeedCategoriesAsync(appUserId);
}

// 修改SeedContactsAsync和SeedCategoriesAsync,接收用户ID参数
private async Task SeedContactsAsync(string appUserId)
{
    if (!_dbContext.Contacts.Any())
    {
        var contact = new List<Contact>()
        {
            new Contact()
            {
                FirstName = "John",
                LastName = "Dickens",
                Address1 = "12 Main St.",
                City = "Concord",
                States  = ContactPro.Enums.States.NH,
                ZipCode = 03101,
                Email = "john@mail.com",
                AppUserId = appUserId,
            }
        };

        await _dbContext.Contacts.AddRangeAsync(contact);
        await _dbContext.SaveChangesAsync();
    }
}

private async Task SeedCategoriesAsync(string appUserId)
{
    if (!_dbContext.Categories.Any())
    {
        var category = new List<Category>()
        {
            new Category()
            {
                Name = "_UnCategorized",
                AppUserId = appUserId,
            },
        };

        await _dbContext.Categories.AddRangeAsync(category);
        await _dbContext.SaveChangesAsync();
    }
}

方案2:保留静态字段,但补全赋值逻辑

如果一定要用静态字段,修改SeedUsersAsync,确保无论用户是否存在都能赋值:

private async Task SeedUsersAsync()
{
    var existingUser = await _userManager.FindByEmailAsync("demouser@mail.com");
    if (existingUser != null)
    {
        appUserId = existingUser.Id;
        return;
    }

    var demoUser = new AppUser()
    {
        UserName = "demouser@mail.com",
        Email = "demouser@mail.com",
        FirstName = "Demo",
        LastName = "User",
        EmailConfirmed = true,
    };

    await _userManager.CreateAsync(demoUser, "xxxxxx");
    appUserId = demoUser.Id;
}

额外优化点

  • 移除SeedUsersAsync中的await _dbContext.SaveChangesAsync();,因为_userManager.CreateAsync内部已经会调用SaveChanges,重复调用属于冗余操作。
  • 使用_userManager.FindByEmailAsync代替_userManager.Users.FirstOrDefault,这是Identity框架提供的专门查询方法,更高效且符合规范。

内容的提问来源于stack exchange,提问作者David

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 03:47:04