EF Core - DbContext.SaveChangesAsync()不保存数据库

时间:2018-06-16 17:23:17

标签: c# entity-framework-core

我在一项非常简单的任务中遇到了奇怪的行为。我在SQL Server 2016上使用EF Core 2.1,我有以下代码,基本上创建了三个对象,并使用DbContext.Add()将它们插入到数据库中。

使用正确创建的实体,所有三个Add()操作都会成功返回,但是,在调用SaveChangesAsync()时, JobSchedule 永远不会插入到数据库中,我也不知道。

private async Task CreateXPTOJob(XPTOJobModel model)
{
    var jobData = new XPTOJobData
    {
        Id = Guid.NewGuid(),
        Foo = model.Foo,
        Bar= model.Bar
    };

    Context.XPTOJobData.Add(jobData);

    var jobType = await Context.JobTypes.FindByCode(EJobType.XPTO);
    var jobPriority = await Context.JobPriorities.FindByCode(EJobPriority.Normal);
    var jobStatus = await Context.JobStatuses.FindByCode(EJobStatus.Created);

    var job = new Job
    {
        Id = Guid.NewGuid(),
        OwnerId = UserId,
        PriorityId = jobPriority.Id,
        TypeId = jobType.Id,
        StatusId = jobStatus.Id,
        MaxRetries = 3,
        XPTOJobDataId = jobData.Id
    };

    Context.Jobs.Add(job);

    var scheduleFrequency = await Context.ScheduleFrequencies.FindByCode(EScheduleFrequency.Once);

    var schedule = new JobSchedule
    {
        Id = Guid.NewGuid(),
        Enabled = true,
        FrequencyId = scheduleFrequency.Id,
        JobId = jobId,
        NotifyCompletion = true,
        PreferredStartTime = DateTime.Now
    };

    Context.JobSchedules.Add(schedule);

    await Context.SaveChangesAsync();
}

如果查看调试输出,我可以看到四个选择和两个插入,如下所示:

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (3ms) [Parameters=[@__type_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [jobType].[Id], [jobType].[Code], [jobType].[Description], [jobType].[DisplayName], [jobType].[Name]
FROM [JobQueue].[JobTypes] AS [jobType]
WHERE [jobType].[Code] = @__type_0

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (2ms) [Parameters=[@__priority_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [jobPriority].[Id], [jobPriority].[Code], [jobPriority].[Description], [jobPriority].[DisplayName], [jobPriority].[Name]
FROM [JobQueue].[JobPriorities] AS [jobPriority]
WHERE [jobPriority].[Code] = @__priority_0

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (1ms) [Parameters=[@__status_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [jobStatus].[Id], [jobStatus].[Code], [jobStatus].[Description], [jobStatus].[DisplayName], [jobStatus].[Name]
FROM [JobQueue].[JobStatuses] AS [jobStatus]
WHERE [jobStatus].[Code] = @__status_0

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (1ms) [Parameters=[@__frequency_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [scheduleFrequency].[Id], [scheduleFrequency].[Code], [scheduleFrequency].[Description], [scheduleFrequency].[DisplayName], [scheduleFrequency].[Name]
FROM [JobQueue].[ScheduleFrequencies] AS [scheduleFrequency]
WHERE [scheduleFrequency].[Code] = @__frequency_0

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (1ms) [Parameters=[@p0='?' (DbType = Guid), @p1='?' (DbType = Guid), @p2='?' (DbType = Guid)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [LifeCycle].[XPTOJobData] ([Id], [Foo], [Bar])
VALUES (@p0, @p1, @p2);

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (1ms) [Parameters=[@p3='?' (DbType = Guid), @p4='?' (DbType = Guid), @p5='?' (DbType = Int32), @p6='?' (DbType = Guid), @p7='?' (DbType = Guid), @p8='?' (DbType = Guid), @p9='?' (DbType = Guid), @p10='?' (DbType = Guid)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [JobQueue].[Jobs] ([Id], [YPTOJobDataId], [MaxRetries], [XPTOJobDataId], [OwnerId], [PriorityId], [StatusId], [TypeId])
VALUES (@p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10);

Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action method JobQueue.Controllers.JobsController.Post (JobQueue), returned result Microsoft.AspNetCore.Mvc.ObjectResult in 63.4302ms.

所有 FindByCode 扩展都遵循相同的逻辑:

public static Task<ScheduleFrequency> FindByCode(this IQueryable<ScheduleFrequency> queryable, EScheduleFrequency frequency)
{
    return queryable.AsNoTracking().SingleAsync(scheduleFrequency => scheduleFrequency.Code == frequency);
}

为什么没有执行第三个插入的任何想法?我尝试了很多小的改动和调整,但没有成功。无论如何,谢谢你的时间和帮助!

编辑1:我正在提出更多相关代码。

的DbContext

public class MyDbContext : DbContext
{
    ...

    public DbSet<User> Users { get; set; }
    public DbSet<XPTOJobData> XPTOJobData { get; set; }
    public DbSet<Job> Jobs { get; set; }
    public DbSet<JobPriority> JobPriorities { get; set; }
    public DbSet<JobSchedule> JobSchedules { get; set; }
    public DbSet<JobStatus> JobStatuses { get; set; }
    public DbSet<JobType> JobTypes { get; set; }
    public DbSet<ScheduleFrequency> ScheduleFrequencies { get; set; }

    ...

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new XPTOJobDataConfiguration());
        modelBuilder.ApplyConfiguration(new JobConfiguration());
        modelBuilder.ApplyConfiguration(new JobPriorityConfiguration());
        modelBuilder.ApplyConfiguration(new JobScheduleConfiguration());
        modelBuilder.ApplyConfiguration(new JobStatusConfiguration());
        modelBuilder.ApplyConfiguration(new JobTypeConfiguration());
        modelBuilder.ApplyConfiguration(new ScheduleFrequencyConfiguration());
    }
}

工作

public class Job
{
    // Properties
    public Guid Id { get; set; }
    public Guid? XPTOJobDataId { get; set; }
    public Guid OwnerId { get; set; }
    public Guid PriorityId { get; set; }
    public Guid StatusId { get; set; }
    public Guid TypeId { get; set; }
    public ushort MaxRetries { get; set; }

    // Navigation Properties
    public XPTOJobData XPTOJobData { get; set; }
    public User Owner { get; set; }
    public JobPriority Priority { get; set; }
    public JobStatus Status { get; set; }
    public JobType Type { get; set; }

    // Navigation Related Properties
    public ICollection<JobSchedule> JobSchedules => _jobSchedules?.ToList();
    private HashSet<JobSchedule> _jobSchedules;

    public Job()
    {
        _jobSchedules = new HashSet<JobSchedule>();
    }
}

JobPriority

public enum EJobPriority
{
    Normal,
    High,
    Immediate
}

public class JobPriority
{
    // Properties
    public Guid Id { get; set; }
    public EJobPriority Code { get; set; }
    public string Description { get; set; }
    public string DisplayName { get; set; }
    public string Name { get; set; }

    // Navigation Related Properties
    public ICollection<Job> Jobs => _jobs?.ToList();
    private HashSet<Job> _jobs;

    public JobPriority()
    {
        _jobs = new HashSet<Job>();
    }
}

JobSchedule

public class JobSchedule
{
    // Properties
    public Guid Id { get; set; }
    public bool Enabled { get; set; }
    public DateTime? EffectiveDate { get; set; }
    public DateTime? ExpiryDate { get; set; }
    public Guid FrequencyId { get; set; }
    public Guid JobId { get; set; }
    public string Name { get; set; }
    public DateTime? NextRunDate { get; set; }
    public bool NotifyCompletion { get; set; }
    public DateTime PreferredStartTime { get; set; }
    public string Recurrence { get; set; }

    // Navigation Properties
    public Job Job { get; set; }
    public ScheduleFrequency Frequency { get; set; }
}

JobConfiguration

public class JobConfiguration : AEntityTypeConfiguration<Job>
{
    protected override string TableName => "Jobs";
    protected override string SchemaName => Schemas.JobQueue;

    protected override void ConfigureForeignKeys(EntityTypeBuilder<Job> entity)
    {
        entity.HasOne(job => job.XPTOJobData)
            .WithMany()
            .HasConstraintName(CreateForeignKeyName("XPTOJobDataId"))
            .OnDelete(DeleteBehavior.SetNull);

        entity.HasOne(job => job.Owner)
            .WithMany(user => user.Jobs)
            .HasConstraintName(CreateForeignKeyName("OwnerId"))
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);

        entity.HasOne(job => job.Priority)
            .WithMany(jobPriority => jobPriority.Jobs)
            .HasConstraintName(CreateForeignKeyName("PriorityId"))
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);

        entity.HasOne(job => job.Status)
            .WithMany(jobStatus => jobStatus.Jobs)
            .HasConstraintName(CreateForeignKeyName("StatusId"))
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);

        entity.HasOne(job => job.Type)
            .WithMany(jobType => jobType.Jobs)
            .HasConstraintName(CreateForeignKeyName("TypeId"))
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);
    }
}

JobPriorityConfiguration

public class JobPriorityConfiguration : AEntityTypeConfiguration<JobPriority>
{
    protected override string TableName => "JobPriorities";
    protected override string SchemaName => Schemas.JobQueue;

    protected override void ConfigureProperties(EntityTypeBuilder<JobPriority> entity)
    {
        entity.Property(jobPriority => jobPriority.Code)
            .IsRequired();

        entity.Property(jobPriority => jobPriority.Description)
            .HasMaxLength(255)
            .IsRequired();

        entity.Property(jobPriority => jobPriority.DisplayName)
            .HasMaxLength(50)
            .IsRequired();

        entity.Property(jobPriority => jobPriority.Name)
            .HasMaxLength(50)
            .IsRequired();
    }

    protected override void ConfigureIndexes(EntityTypeBuilder<JobPriority> entity)
    {
        entity.HasIndex(x => x.Code)
            .IsUnique()
            .HasName(CreateUniqueKeyName("Code"));

        entity.HasIndex(x => x.Name)
            .IsUnique()
            .HasName(CreateUniqueKeyName("Name"));
    }
}

JobScheduleConfiguration

public class JobScheduleConfiguration : AEntityTypeConfiguration<JobSchedule>
{
    protected override string TableName => "JobSchedules";
    protected override string SchemaName => Schemas.JobQueue;

    protected override void ConfigureProperties(EntityTypeBuilder<JobSchedule> entity)
    {
        entity.Property(jobSchedule => jobSchedule.Name)
            .HasMaxLength(255)
            .IsRequired();

        entity.Property(jobSchedule => jobSchedule.Recurrence)
            .HasMaxLength(50);
    }

    protected override void ConfigureIndexes(EntityTypeBuilder<JobSchedule> entity)
    {
        entity.HasIndex(jobSchedule => jobSchedule.Name)
            .HasName(CreateIndexName("Name"));
    }

    protected override void ConfigureForeignKeys(EntityTypeBuilder<JobSchedule> entity)
    {
        entity.HasOne(jobSchedule => jobSchedule.Job)
            .WithMany(job => job.JobSchedules)
            .HasConstraintName(CreateForeignKeyName("JobId"))
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
    }
}

1 个答案:

答案 0 :(得分:1)

我认为您的副作用是由此签名后的属性引起的。

public ICollection<JobSchedule> JobSchedules => _jobSchedules?.ToList();

virtual ICollection<>的DbSet属性不再意味着同样的事情。这不会启用延迟加载导航属性。您必须在DbContext配置中启用它。

其他内容

根据帖子的内容,您可以证明EF 6可以迁移到EF Core。我认为你的问题的原因是副作用行为。 EF Core专注于常规第一种方法,而EF 6则需要详细配置。让这些惯例为你工作。

例如,从AEntityTypeConfiguration<>派生的类(您共享的类)几乎完全重述默认约定,但外键约束的显式命名除外。 .HasConstraintName(CreateForeignKeyName("XPTOJobDataId"))如果你能够切换到EF Core的Fkey命名方案,那么很多代码都不需要编写。至少需要编写3个类和一个接口。