0

I currently have 2 classes that are mapped to each other. I am receiving the following error:

The MERGE statement conflicted with the FOREIGN KEY constraint "FK_Stories_Users_UserId". The conflict occurred in database "StoriesDb", table "dbo.Users", column 'Id'.

I am trying to add the UserId FK to the new Story entity I just created. My classes look something like this (snipped irrelevant properties)

public class Story : ITimestamp
{
    public Story() {}

    public int Id { get; set; }

    public string Description { get; set; }
    public string DescriptionMarkdown { get; set; }
    public string Title { get; set; }
    public Guid UserId { get; set; }

    public virtual User User { get; set; }

    public virtual DateTime CreatedDate { get; set; }
    public virtual DateTime ModifiedDate { get; set; }
}    

public class User : ITimestamp
{
    public Guid Id { get; set; }
    public string Username { get; set; }
    public string Email { get; set; }
    public string PasswordHash { get; set; }
    public string PasswordSalt { get; set; }

    public virtual IList<Comment> Comments { get; set; } = new List<Comment>();
    public virtual DateTime CreatedDate { get; set; }
    public virtual DateTime ModifiedDate { get; set; }
}

I add the new story like this:

public async Task<StorySummaryViewModel> Create(CreateViewModel model, string username, Guid userId)
{
      var story = await StoriesDbContext.Stories.AddAsync(new Story() {
                Title = model.Title,
                DescriptionMarkdown = model.DescriptionMarkdown,
                Description = CommonMarkConverter.Convert(model.DescriptionMarkdown),
                UserId = userId
      });

      var rowCount = await StoriesDbContext.SaveChangesAsync();
      [...]
}

Through some of the other Create methods, I know that adding a new entity object to a second new entity's navigation property will save correctly. However, at this point I do not want to be able to modify the User entity when saving a Story entity, which is why I set the UserId property (marked as FK). But it doesn't seem to be working.

I have also compared the UserId passed to the method against the database data and they are both the same value.

The technique of only setting the FK Id property is something I use in EF6 and it works as expected.

Am I missing something in my DbContext or in my mappings that would allow me to SaveChanges the new entity by foreign key id?

This question is very similar to this StackOverflow question. But unfortunately it only deals with newly created entities.

Edit I am including the mapping I have for both story and user.

public class StoryMap : IEntityTypeConfiguration<Story>
    {
        public void Map(EntityTypeBuilder<Story> builder)
        {
            builder.HasKey(e => e.Id);
            builder.Property(s => s.Url).IsRequired();
            builder.Property(s => s.Title).IsRequired();

            builder.HasOne(e => e.User).WithMany().HasForeignKey(e => e.UserId);
            builder.HasMany(e => e.Comments).WithOne(c => c.Story).HasForeignKey(c => c.StoryId);
            builder.HasMany(e => e.Votes).WithOne(v => v.Story).HasForeignKey(v => v.StoryId);
        }
    }

public class UserMap : IEntityTypeConfiguration<User>
{
    public void Map(EntityTypeBuilder<User> builder)
    {
        builder.HasKey(u => u.Id);
        builder.HasIndex(u => u.Email);

        builder.Property(u => u.PasswordHash).IsRequired();
        builder.Property(u => u.PasswordSalt).IsRequired();
        builder.Property(u => u.Username).IsRequired();
        builder.Property(u => u.Email).IsRequired();

        // Navigation
        [..snip..]
        builder.HasMany(u => u.Stories).WithOne(c => c.User).HasForeignKey(s => s.UserId);
    }
}

Edit for migration

migrationBuilder.CreateTable(
                name: "Stories",
                columns: table => new
                {
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    CreatedDate = table.Column<DateTime>(nullable: false),
                    Description = table.Column<string>(nullable: true),
                    DescriptionMarkdown = table.Column<string>(nullable: true),
                    ModifiedDate = table.Column<DateTime>(nullable: false),
                    Title = table.Column<string>(nullable: false),
                    Url = table.Column<string>(nullable: false),
                    UserId = table.Column<Guid>(nullable: false),
                    UserIsAuthor = table.Column<bool>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Stories", x => x.Id);
                    table.ForeignKey(
                        name: "FK_Stories_Users_UserId",
                        column: x => x.UserId,
                        principalTable: "Users",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Restrict);
                });
Community
  • 1
  • 1
Matt R
  • 2,577
  • 5
  • 30
  • 46

1 Answers1

0

I use same technique (set UserId value instead full User reference) and it works correctly, with only one addition - I use explicit FK-binding between UserId and User properties, like this:

public Guid UserId { get; set; }

[ForeignKey(nameof(UserId))]
public virtual User User { get; set; }

Check you database (or migration script) - may be EF created two different UserId-like properties in your Story without this attribute?

Of course you may use fluent API instead of attribute, if you wish.

Dmitry
  • 16,110
  • 4
  • 61
  • 73
  • Have a look at the StoryMap class? Does it look ok? – Matt R Mar 09 '17 at 16:44
  • Yes, it looks good. I never worked with IEntityTypeConfiguration, but it looks like correct fluent configuration. Do you use migrations? What code is generated for this entity? Or, check SQL DB - does FLs looks like you want? – Dmitry Mar 09 '17 at 17:04
  • I added the migration that is created, the User table has no mention of the StoryId which is fine. As for IEntityTypeConfiguration, its an interfaced used in the DbContext to allow the mapping classes to be easily executed. – Matt R Mar 09 '17 at 17:16
  • Migration looks good. Your code *should* work. Double-check everything, try on new/empty database (regenerate it from single migration). You missed something (like `UserIsAuthor` in migration, which is not in your original entity). – Dmitry Mar 09 '17 at 20:56