0

I am accepting 3 fields from a user. All 3 fields are mandatory to be entered. Look at the entity configuration below - all 3 fields in the EntityConfiguration has been marked as Required.

This works perfectly when I am Saving a new record to the database. However, now I require to Update an entry. In that case If I ONLY want to update the UserName, and Password - there's an exception thrown because Age can't be Null. How can I resolved this ?

InnerException = {"Cannot insert the value NULL into column 'Age', table 'DBFFFF.dbo.Users'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated."}

CODE

public class User
{
    public int Id{ get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public string Age { get; set; }
}

public class UserEntityConfiguration : IEntityTypeConfiguration<User>
{
    public void Configure(EntityTypeBuilder<User> builder)
    {
        builder.ToTable("Users");

        builder.HasKey(c => c.Id);

        builder.Property(c => c.UserName)
                       .IsRequired(true);

        builder.Property(c => c.Password )
            .IsRequired(true);

        builder.Property(c => c.Age )
            .IsRequired(true); 
    }
}
    public async Task<string> UpdateUs(User u)
    {
        _dbCont.Update(u);
        await _dbCont.SaveChangesAsync();
        return "ok";
    }
Illep
  • 16,375
  • 46
  • 171
  • 302
  • 2
    How are you updating the entity? That code is more relevant than the configuration – DTul Sep 02 '21 at 06:29
  • I have updated my code. Please have a look – Illep Sep 02 '21 at 06:37
  • `_dbCont.Update(u);` <-- You probably don't need to do this. Anyway, this is also the cause of the issue because your `User u` object has an un-set `Age` property which EF interprets as a _changed property_ and so thinks the value should be stored as `NULL` (also, you should store DoB as a `date` in your database, not an `Age` value, because a person's age changes every year). – Dai Sep 02 '21 at 06:46
  • 1
    This is not problem, what you send, EF updates it. I mean you are sending this value as a null and it understand that you want to update this field as a null and I think it is so normal process. – ilyas varol Sep 02 '21 at 06:48
  • 1
    `builder.Property(c => c.Password )` -- **DO NOT STORE YOUR USERS' PASSWORDS AS PLAINTEXT!!!!!** You must hash and salt them instead. – Dai Sep 02 '21 at 06:52
  • 1
    @Dai Yes. It is Salted and hashed. – Illep Sep 02 '21 at 06:57
  • Then why are you using a single `string Password` value instead of `byte[] PasswordHash` and `byte[] PasswordSalt`? – Dai Sep 02 '21 at 07:02
  • @Dai Thanks for pointing out. I will make that change. BTW. I have only included part of the Entity class here that I thought was relevant to this problem. Anyway , I didn't know that I could use byte[] as the datatype to store the password hash. Thanks – Illep Sep 02 '21 at 07:04

3 Answers3

3

Your problem is here:

    public async Task<string> UpdateUs(User u)
    {
        _dbCont.Update(u);
        await _dbCont.SaveChangesAsync();
        return "ok";
    }

I assume that your call-site for UpdateUs looks like this:

User u = new User()
{
    Id = 123,
    UserName = "foobar"
};

await UpdateUs(u);
  • Note that the above code does not set any values for Age and Password

    • AND YOU MUST NOT STORE USERS' PASSWORDS AS PLAINTEXT!
  • ...but then your code calls DbContext.Update...

    • ...which basically tells EF that "this object u now represents the current-state of the entity with Id = 123 including the fact that Age = null", which is not what you want.
      • I assume what you actually want is for EF to only consider the UserName property as updated and to ignore the other non-key properties (Password and Age) which have not yet been set.
  • Also, I get the feeling that you're using your entity classes as mutable view-models (e.g. in ASP.NET or WPF). You should not do that because an entity object represents persisted business-data state, it does not represent in-memory mutable UI state.

    • Also, view-models need additional annotations and members that entity types don't have. For example, an "Edit User" view-model needs two password fields ("New password" and "Confirm new password"), this is why we have separate classes for view-models even when they have the same members as an entity class.

Anyway, the correct solution for this (assuming that you still want to use your class User as an internal DTO), is to use Attach and set IsModified on the properties you're updating.

Like so:

    public async Task< UpdateUserAsync(User u)
    {
        // Preconditions:
        if( u is null ) throw new ArgumentNullException(nameof(u));
        if( u.Id == default ) throw new ArgumentException( message: "User's Id is not set.", paramName: nameof(u) );

        // 

        this.dbContext.Users.Attach( u );

        var entry = this.dbContext.Entry( u );
        if( u.UserName != null ) entry.Property( x => x.UserName ).IsModified = true;
        if( u.Age      != null ) entry.Property( x => x.Age      ).IsModified = true;

        this.dbContext.Configuration.ValidateOnSaveEnabled = false; // <-- Only do this if necessary.

        Int32 rowCount = await this.dbContext.SaveChangesAsync();
        if( rowCount != 1 ) throw new InvalidOperationException( "No rows (or too many rows) were updated." );
    }
Dai
  • 141,631
  • 28
  • 261
  • 374
  • Woah Thanks. In addition to the problem I had, there's more detail here. Thank you I have learnt a lot from your detailed answer. – Illep Sep 02 '21 at 07:09
  • Just a quick question. Is there a way to find what What properties of the Objects are Null and set IsModified - true; ? Like for example at times Age can be null and some other times Password or UserName can be null. Is there a way to detect these as well ? – Illep Sep 02 '21 at 07:11
  • @Illep If `Age` can be `null` then why is it marked as `Required` (i.e. `NOT NULL`)? As I said, you should _not_ be using Entity Classes as view-models. It sounds like you should create a separate `class UserDetailsForm` and write a method that maps that to (and from) your `User` entity object. – Dai Sep 02 '21 at 07:24
0

I think this will be helpful answer. You can ignore the specific field(s) when you do update

public async Task<string> UpdateUser(User u)
{
    _dbCont.Update(u);
    _dbCont.Entry(u).Property(x => x.Age).IsModified = false;
    await _dbCont.SaveChangesAsync();
    return "ok";
}
Yaseer
  • 506
  • 2
  • 14
-2

With the below configuration, you are making Age field not nullable.

builder.Property(c => c.Age ).IsRequired(true); 

Remove IsRequired(true); Configuration should be as below.

builder.Property(c => c.Age ); 
Ibrahim ULUDAG
  • 450
  • 3
  • 9
  • I want to keep it as Not Nullable. This is very useful for me when I am saving records. Is there a way to temporarily enable fields Nullable ? – Illep Sep 02 '21 at 06:45
  • 1
    The OP _doesn't_ want to make the column `NULL`able, they want to stop EF doing `UPDATE SET Age = NULL`. – Dai Sep 02 '21 at 06:46
  • @Dai Yes. Exactly. This is what I require to do – Illep Sep 02 '21 at 06:47