cl0ud
cl0ud

Reputation: 1614

Foreign key not set to null after referenced row deleted

Given the two classes below:

public class PortalUser : IdentityUser
{
    public Guid? RefreshTokenId { get; set; }
    public RefreshToken RefreshToken { get; set; }

}

public class RefreshToken
{
    public Guid Id { get; set; }
    public string Token { get; set; }
    public string UserId { get; set; }
    public PortalUser User { get; set; }

    public RefreshToken(string token, string userId)
    {
        Token = token;
        UserId = userId;
    }
}

I set up a one to one relationship - a user can have a single refresh token or none at all. See the configuration below.

 public void Configure(EntityTypeBuilder<PortalUser> builder)
 {
     builder.HasOne(x => x.RefreshToken).WithOne(x => x.User)
                                  .HasForeignKey<PortalUser>(x => x.RefreshTokenId).OnDelete(DeleteBehavior.SetNull);
 }

public class RefreshTokenConfiguration : IEntityTypeConfiguration<RefreshToken>
{
    public void Configure(EntityTypeBuilder<RefreshToken> builder)
    {
        builder.ToTable("AspNetUserRefreshTokens");
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Id).ValueGeneratedOnAdd();
        builder.Property(x => x.Token).IsRequired();
        builder.HasOne(x => x.User).WithOne(x => x.RefreshToken)
                                   .HasForeignKey<RefreshToken>(x => x.UserId).OnDelete(DeleteBehavior.Cascade);

    }
}

Now when deleting a user the cascading delete works fine - the refresh token gets deleted aswell but when I delete a single refresh token , the FK in the PortalUser is STILL SET.

This doesnt seem right to me , any idea what I am doing wrong?

Upvotes: 0

Views: 100

Answers (1)

timur
timur

Reputation: 14567

I tried to reproduce your issue and as far as I can tell your configuration is correct. One thing that you might have missed (or just didn't include into the question) is how you wire the EntityTypeBuilder<PortalUser> into your DB context. I was successful with the following configuration:

DbContext.cs

public class DbContext: IdentityDbContext<PortalUser>
{
    public DbSet<RefreshToken> AspNetUserRefreshTokens { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder builder)
    {
        builder.UseSqlServer("needs a connection string");

    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfiguration(new RefreshTokenConfiguration());
        modelBuilder.ApplyConfiguration(new PortalUserConfiguration()); // i suspect this piece can be your issue, check if you've got it hooked up
    }

    public class PortalUserConfiguration : IEntityTypeConfiguration<PortalUser>
    {
        public void Configure(EntityTypeBuilder<PortalUser> builder)
        {
            builder.HasOne(x => x.RefreshToken)
                .WithOne(x => x.User)
                .HasForeignKey<PortalUser>(x => x.RefreshTokenId)
                .OnDelete(DeleteBehavior.SetNull);
        }
    }

    public class RefreshTokenConfiguration : IEntityTypeConfiguration<RefreshToken>
    {
        public void Configure(EntityTypeBuilder<RefreshToken> builder)
        {
            builder.ToTable("AspNetUserRefreshTokens");
            builder.HasKey(x => x.Id);
            builder.Property(x => x.Id).ValueGeneratedOnAdd();
            builder.Property(x => x.Token).IsRequired();
            builder.HasOne(x => x.User).WithOne(x => x.RefreshToken)
                .HasForeignKey<RefreshToken>(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
        }
    }
}

My test bench looks like so:

Program.cs

class Program
{
    static void Main(string[] args)
    {
        var ctx = new DbContext();
        //ctx.Database.EnsureCreated(); // you probably don't need this step
        var user = new PortalUser();
        var t = new RefreshToken("test", user.Id);
        user.RefreshToken = t;
        ctx.Users.Add(user);
        ctx.SaveChanges();

        var token = ctx.AspNetUserRefreshTokens.Find(t.Id);
        ctx.Entry(token).State = EntityState.Deleted;
        ctx.SaveChanges();

        var u = ctx.Users.Find(user.Id);
        Console.Write($"RefreshTokenId {u.RefreshTokenId}");
        Console.ReadKey();
    }
}

Upvotes: 1

Related Questions