Search code examples
c#entity-frameworksql-server-cesql-server-ce-4

EF DbSet.Find() returns Detached Entity


I have a very simple Entity with just 4 fields: Name, State, City and ID and some Validations.

[Table("Places")]
public class Place : BindableBase, IDataErrorInfo
{
    private string name;
    private string city;
    private Estado state;
    private int id;

    [Required]
    [StringLength(500)]        
    public string Name
    {
        get { return this.name; }
        set { SetProperty(ref name, value); }
    }

    [Required]
    [StringLength(500)]
    public string City
    {
        get { return this.city; }
        set { SetProperty(ref city, value); }
    }

    [Required]        
    public State State
    {
        get { return this.state; }
        set { SetProperty(ref state, value); }
    }

    [Key]
    public int Id
    {
        get { return this.id; }
        set { SetProperty(ref id, value); }
    }

    [NotMapped]
    public bool IsValid
    {
        get 
        {
            return Validator.TryValidateObject(this, new ValidationContext(this), new Collection<ValidationResult>(), true);
        }
    }

    #region IDataErrorInfo Members
    /// <summary>
    /// IDataErrorInfo Interface Error Message for the object.
    /// </summary>
    [NotMapped]
    public string Error
    {
        get { throw new NotImplementedException(); }
    }

    public string this[string propertyName]
    {
        get 
        {
            var context = new ValidationContext(this)
            {
                MemberName = propertyName
            };

            var results = new Collection<ValidationResult>();
            bool isValid = Validator.TryValidateObject(this, context, results, true);

            if (!isValid)
            {
                ValidationResult result = results.SingleOrDefault(p =>
                                                                  p.MemberNames.Any(memberName =>
                                                                                    memberName == propertyName));

                return result == null ? null : result.ErrorMessage;
            }

            return null; 
        }
    }
    #endregion
}

I can add it to the DB just fine, but once I try to update it, I get an Exception.

I am using:

public void UpdatePlace(Place place)
{
    var entity = context.Places.Find(place.Id);

    if (entity == null)
    {
        throw new InvalidOperationException("Place not found.");
    }

    context.Entry(place).CurrentValues.SetValues(place);
    context.SaveChanges();
}

When I get to

context.Entry(place).CurrentValues.SetValues(place);

I get an Exception as:

System.InvalidOperationException

"Member 'CurrentValues' cannot be called for the entity of type 'Place' because the entity does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet."

context.Entry shows me that indeed the entity is detached

context.Entry shows detached

But DbSet.Find() method's documentation shows clearly that Find() should return an attached entity in case one is found in the DB:

Finds an entity with the given primary key values. If an entity with the given primary key values exists in the context, then it is returned immediately without making a request to the store. Otherwise, a request is made to the store for an entity with the given primary key values and this entity, if found, is attached to the context and returned. If no entity is found in the context or the store, then null is returned.

So when I try to get CurrentValues, since the entity is detached, it throws the Exception... but as far as I can see, there should be an attached entity, or null, not anything else....

I can't find anything about this error online, I am using SQL CE 4.0 as the DB, does anyone knows what's happening?

I think I can just Attach the entity every time I get it from Find but still I want to understand what's going on with my software, since this shouldn't be happening.


Solution

  • I think you should just change this line to:

      context.Entry(entity).CurrentValues.SetValues(place);