0

Suppose I have a generic data access layer method for updating records with something like this code:

public virtual void Update<P>(Expression<Func<T, P>> excludeColumn, params T[] items)
{
        foreach (T item in items)
        {
            _entities.Entry(item).State = EntityState.Modified;
            _entities.Entry(item).Property(excludeColumn).IsModified = false;
        }

        _entities.SaveChanges();
}

Here I am taking excludeColumn param for excluding column from update, and I passed value into this parameter like this

_companyProfileRepository.Update(x => x.EmailAddress, records);

x => x.EmailAddress is an expression which I pass into the generic Update method. My problem is I want to pass multiple columns into Update method, because sometimes I need to exclude more than just one column, but my method doesn't support multiple column mechanism.

Can anyone help me figure this out?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Fraz Zaki
  • 309
  • 2
  • 4
  • 16
  • 1
    My understanding is EF already tracks changes to your entities and only updates the modified properties, so why would you need to exclude columns? – saille Jul 11 '17 at 23:31
  • 1
    because I am sending with empty property for updating , even there is a value in database , doing because this is sensitive data and I don't want to show this on view , but user can update other values – Fraz Zaki Jul 11 '17 at 23:40
  • You may want to consider adding a layer between your Entities and your Views, a View Model for example - see this question: https://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc. – saille Jul 12 '17 at 03:05

4 Answers4

0

For the multiple-column case, you could add an overload that accepts the names of unmodified columns. Something like:

    public virtual void Update<TEntity>(IEnumerable<string> excludeColumnNames, params TEntity[] items)
    {
        foreach (var item in items)
        {
            _entities.Entry(item).State = EntityState.Modified;
            foreach (var cn in excludeColumnNames)
            {
                _entities.Entry(item).Property(cn).IsModified = false;
            }

        }
        _entities.SaveChanges();
    }
David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • Why perpetuate the use of the generic repository anti-pattern? http://www.ben-morris.com/why-the-generic-repository-is-just-a-lazy-anti-pattern/ How about using Repository pattern properly: https://martinfowler.com/eaaCatalog/repository.html – saille Jul 12 '17 at 03:27
  • 1
    With generic type constraints, a generic repository base class allows code-reuse. I have found it very useful when implementing repositories for different (but very similar) tables. –  Jul 12 '17 at 16:01
  • Except that re-use breaks the moment you discover that one table's update method is not the same as another's, which by the way is the problem the OP has just experienced. – saille Jul 12 '17 at 20:56
  • 1
    Nothing breaks - if needed you can make the update method an abstract or virtual method, implemented in the derived class. I'm looking at a generic repository written this way. –  Jul 13 '17 at 07:47
0

Using the Repository Pattern you can wrap your EF data layer in something that actually has meaning, rather than generic/abstract code that conceals intention. There are tons of examples on the web of Repository Pattern implemented generically, but I consider this an anti-pattern, and I'm not alone.

e.g. a UserProfileRepository could have an interface like this:

public interface IUserProfileRepository
{
  void UpdateUserProfile(IUserProfile p);
}

This is the kind of abstraction that makes sense to anyone reading your code because it makes its intent clear. Furthermore, the only reason for the above code to change is if there is actually a change to the way you are storing IUserProfile, and if you have some special handling to do when updating UserProfile, you have somewhere to put it.

Compare your:

void Update<TEntity>(IEnumerable<string> excludeColumnNames, params TEntity[] items)

which obscures meaning when calls to this method are sprinkled around your code to achieve many different outcomes, and if there's special handling for updating a specific entity type, you have nowhere to put it, so it ends up getting pushed up a layer and duplicated every time its needed.

Also consider that getting an entity and updating it are two very different operations. You might get all properties on an entity, but only update some of them, which I believe you are referring to in your question. I consider the way EF makes read and writes to entities 'feel' almost the same to be an awful abstraction that leads to a great deal of pain and suffering. Reading and writing data are very distinct use cases, as the CQRS pattern proves.

saille
  • 9,014
  • 5
  • 45
  • 57
0

If you want to pass multiple columns in, then pass them in...
Arrays or lists are the usual way.

public virtual void Update<P>(List<Expression<Func<T, P>>> excludeColumns, params T[] items)
{
     foreach (T item in items)
     {
         foreach (Expression<Func<T, P>> excludeColumn in excludeColumns)
         {
            _entities.Entry(item).State = EntityState.Modified;
            _entities.Entry(item).Property(excludeColumn).IsModified = false;       
         }
     }

    _entities.SaveChanges();
}

That'll be fine as long as each column has the same type P.

-1

I have a similar method, but using linq-to-sql. It's a bit different approach, but this works very well. You may need to adapt it to use with EF.

public class Data
{
    public static void Update<T>(Func<T, bool> where, Action<T> change)
    {
        IEnumerable<T> items = ((IEnumerable<T>)myDataContext.GetTable(typeof(T))).Where(where);

        foreach (T item in items)
        {
            change(item);
        }

        myDataContext.SubmitChanges();
    }
}

I use it like this:

Data.Update<User>(u => u.Id == 3, u =>
{
    u.Name = "John";
    u.Age = 25;
    u.Email = "john@email.com";
});

While this may not be a direct answer to this question, I believe this is relevant and will help.

Guilherme
  • 5,143
  • 5
  • 39
  • 60
  • Where does myDataContext come from - what is its scope? Why a static Update() method? What pattern is this - it seems to be a "static generic repository" anitpattern! – saille Jul 12 '17 at 03:23
  • @saille this is just example code. The data context should be created as per user configuration, connection strings, and so on. Also, this is irrelevant since it is different in EF. Also, it is a simple helper to reduce repeated code, nothing more, and should be used only when makes sense. Nothing wrong with it, except when someone makes bad use of it. – Guilherme Jul 12 '17 at 10:44