Reputation:
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
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
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