Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Readonly properties in EF 4.1

I've faced with situation when I need to have EF readonly property in case of 'optimistic update'(you do not load current state of your domain object from database to check what properties are really changed. You just set your object as Modified and update it to database. You avoid redundant select and merge operations in this case).

You can't write something like this : DataContext.Entry(entity).Property(propertyName).IsModified = false;, because setting of 'false' value is not supported and you will get an exception. (in EF 4.1)

I've created a simple structure for registering readonly properties in repository. So, you can easy Modify just nonreadonly properties.

What do you think about this?

public abstract class RepositoryBase<T> where T : class
{   
 private const string MethodReferenceErrorFormat = "Expression '{0}' refers to a method, not a property.";
 private const string FieldReferenceErrorFormat = "Expression '{0}' refers to a field, not a property.";

 protected IList<PropertyInfo> _readOnlyProperties;
        /// <summary>
        /// This method is used to register readonly property for Entity.
        /// </summary>
        /// <param name="propertyLambda">Entity property as LambdaExpression</param>
        protected void RegisterReadOnlyProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda)
        {
            Guard.ArgumentNotNull(propertyLambda, "propertyLambda");

            var propertyMember = propertyLambda.Body as MemberExpression;
            if (propertyMember == null)
            {
                var exceptionMessage = string.Format(MethodReferenceErrorFormat, propertyLambda);
                throw new ArgumentException(exceptionMessage);
            }

            var propertyInfo = propertyMember.Member as PropertyInfo;
            if (propertyInfo == null)
            {
                var exceptionMessage = string.Format(FieldReferenceErrorFormat, propertyLambda);
                throw new ArgumentException(exceptionMessage);
            }

            _readOnlyProperties.Add(propertyInfo);
        }

         /// <summary>
         /// This method is used to attach domain object to DbContext and mark it as modified to save changes.
         /// </summary>
         /// <param name="entity">Detached entity</param>
        public void SetModified(T entity)
        {
            Guard.ArgumentNotNull(entity, "entity");

            //Mark whole entity as Modified, when collection of readonly properties is empty.
            if(_readOnlyProperties.Count == 0)
            {
                DataContext.Entry(entity).State = EntityState.Modified;
                return;
            }

             //Attach entity to DbContext.
             _dbSet.Attach(entity);

            //Mark all properties except readonly as Modified.
            var allProperties = entity.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
            var propertiesForUpdate = allProperties.Except(_readOnlyProperties);
            foreach (var propertyInfo in propertiesForUpdate)
            {
                DataContext.Entry(entity).Property(propertyInfo.Name).IsModified = true;
            }
        }
like image 280
zonder Avatar asked Apr 21 '11 19:04

zonder


1 Answers

This would work but I don't like the need to register modified properties directly in repository. You can forget about registered properties and code will accidentaly not save some changes - that will be bug which will be hard to find when reusing repository in complex scenarios. I like explicit definition of updated properties each time you call something like Update on your repository. Also I don't like reflection in the code. Unless you modify your code to get reflected data about each entity only once for whole application you are doing it wrong.

I wrote the answer for EFv4 but it can be easily modified to EFv4.1:

public void Update(T entity, params Expression<Func<T, object>>[] properties)
{
    _dbSet.Attach(entity);
    DbEntityEntry<T> entry = _context.Entry(entity);
    foreach (var selector in properties)
    {
        entry.Property(selector).IsModified = true;
    }
}

You will call it like:

repo.Update(entity, e => e.Name, e => e.Description);
like image 56
Ladislav Mrnka Avatar answered Nov 09 '22 11:11

Ladislav Mrnka