user32826
user32826

Reputation:

Refactoring code into a generic method

I have a reoccuring code block in my EntityFramework backed repository which I would like to genericise somehow and call as a method, so reuse the code rather than repeat it.

The current code block looks like this:

        // Archive deleted MyItems sections
        _t.MyItems.Where(x => x.ValidTo == null && !team.MyItems.Contains(x)).ToList().ForEach(x => x.ValidTo = DateTime.Now);

        // Add or update MyItems sections
        foreach (var MyItemsSection in team.MyItems)
        {
            if (MyItemsSection.Id == default(int))
            {
                MyItemsSection.ValidFrom = DateTime.Now;
                _t.MyItems.Add(MyItemsSection);
            }
            else
            {
                var _MyItemsSection = _t.MyItems.FirstOrDefault(x => x.Id == MyItemsSection.Id);
                context.Entry(_MyItemsSection).CurrentValues.SetValues(MyItemsSection);
            }
        }

_t is the EntityFramework connected object graph, while team is an identical type of object graph that has been disconnected and possibly updated externally. The goal here is to sync the two object graphs so the changes are persisted.

I need to pass in _t.MyItems and team.MyItems, where MyItems are to be genericised so the same method works for MyOtherItems and MySocks, MyUnderPants etc.

Is this at all possible?

Upvotes: 4

Views: 170

Answers (2)

user32826
user32826

Reputation:

In answer to my own question, here is the answer - what I was missing was the fact that you can require the incoming type as implementing a specific interface, and still have it available as the type wanted.

So, here is what I came up with:

public void UpdateEntities<TEntity>(ICollection<TEntity> pocoCollection, ICollection<TEntity> dbCollection)
        where TEntity : class, IEntity
    {
        // Archive deleted entities
        dbCollection.Where(x => x.ValidTo == null && !pocoCollection.Contains(x)).ToList().ForEach(x => x.ValidTo = DateTime.Now);

        // Add or update entities
        foreach (var entity in pocoCollection)
        {
            if (entity.Id == default(int))
            {
                entity.ValidFrom = DateTime.Now;
                dbCollection.Add(entity);
            }
            else
            {
                var _entity = dbCollection.FirstOrDefault(x => x.Id == entity.Id);
                context.Entry(_entity).CurrentValues.SetValues(entity);
            }
        }
    }

The part which I was looking for was the where TEntity : class, IEntity

In this solution, I have to make sure that my entities implement the interface IEntity, which simply is:

public interface IEntity
{
    int Id { get; set;}
    DateTime ValidFrom { get; set; }
    DateTime? ValidTo { get; set; }
}

This allows the compiler to quit complaining about the use of those properties, while I can still use the actual type so Entity Framework is also satisfied and left less confused about whats going on.

Hope this helps someone else.

Upvotes: 1

M.Stramm
M.Stramm

Reputation: 1309

You have two choices: Either you constrain your objects to a known base type which contains the properties and methods you want to access in the generic method or you use predicates to do the selection.

Constraints:

// base type
interface IFoo {
  int ID { get; set; }
}

  // generic method
  public List<T> Update<T>(List<T> graph1, List<T> graph2) where T : IFoo {
    var update = graph1.Intersect(graph2, (g1, g2) => { g1.ID == g2.ID }).ToList();
    return update;
  }

Predicates:

public void Update<T, U>(T _t, T team, Func<T, IList<U>> selector) 
{
    var _tItems = selector(_t);
    var teamItems = selector(team);

    // Archive deleted MyItems sections
    _tItems.Where(x => x.ValidTo == null && !teamItems.Contains(x)).ToList().ForEach(x => x.ValidTo = DateTime.Now);

    // Add or update MyItems sections
    foreach (var MyItemsSection in teamItems)
    {
        if (MyItemsSection.Id == default(int))
        {
            MyItemsSection.ValidFrom = DateTime.Now;
            _tItems.Add(MyItemsSection);
        }
        else
        {
            var _MyItemsSection = _tItems.FirstOrDefault(x => x.Id == MyItemsSection.Id);
            context.Entry(_MyItemsSection).CurrentValues.SetValues(MyItemsSection);
        }
    }
}

    //Usage:
    Update(_t, team, (t => t.MyItems));

But then again, what keeps you from writing a method which takes the lists as parameters?

As in public void Update<T>(IList<T> _tItems, IList<T> teamItems)

Upvotes: 1

Related Questions