I have a template called Entity
, this template has links (1 - N) with three other templates.
public class Entity
{
// Outras propriedades removidas para brevidade
public virtual List<SpecificInfo> SpecificInfo { get; set; }
public virtual List<EntityContact> Contacts { get; set; }
public virtual List<EntityAddress> Addresses { get; set; }
}
On a given occasion, a method (let's call Edit
) receives an instance of this model and needs to check which properties have been modified (based on the model that is in the database). In the case of these three properties, a more detailed check is necessary since they are lists of objects where I need to check which of the items in the list have been added, changed or deleted (compare this with another list, see below).
Example of how method Edit
is:
private void Edit(Entity model)
{
//Início do código removido
var existentSpecificInfo = _db.EntitiesSpecificInfo.Where(info => info.EntityId == id).ToList();
var validatedSpecificInfo = new List<EntitySpecificInfo>();
foreach (var info in model.Entity.SpecificInfo)
{
var existentInfo = existentSpecificInfo.SingleOrDefault(x => x.Description == info.Description);
if (existentInfo != null)
{
info.Id = existentInfo.Id;
_db.Entry(existentInfo).State = EntityState.Detached;
_db.Entry(info).State = EntityState.Modified;
validatedSpecificInfo.Add(existentInfo);
}
else
{
_db.Entry(info).State = EntityState.Added;
}
}
existentSpecificInfo.RemoveAll(x => validatedSpecificInfo.Contains(x));
existentSpecificInfo.ForEach(x => _db.Entry(x).State = EntityState.Deleted);
//Verificar os contatos enviados
var existentContacts = _db.EntitiesContacts.Where(x => x.EntityId == id).ToList();
var validatedExistentContacts = new List<EntityContact>();
foreach (var contact in model.Entity.Contacts)
{
var existentContact = existentContacts.SingleOrDefault(x => x.Id == contact.Id);
if (existentContact != null)
{
contact.Id = existentContact.Id;
_db.Entry(existentContact).State = EntityState.Detached;
_db.Entry(contact).State = EntityState.Modified;
validatedExistentContacts.Add(existentContact);
}
else
{
_db.Entry(contact).State = EntityState.Added;
}
}
existentContacts.RemoveAll(x => validatedExistentContacts.Contains(x));
existentContacts.ForEach(x => _db.Entry(x).State = EntityState.Deleted);
//Verificar os endereços enviados
var existentAddresses = _db.EntitiesAddresses.Where(x => x.EntityId == id).ToList();
var validatedExistentAddresses = new List<EntityAddress>();
foreach (var address in model.Entity.Addresses)
{
var existentAddress = existentAddresses.SingleOrDefault(x => x.Id == address.Id);
if (existentAddress != null)
{
address.Id = existentAddress.Id;
_db.Entry(existentAddress).State = EntityState.Detached;
_db.Entry(address).State = EntityState.Modified;
validatedExistentAddresses.Add(existentAddress);
}
else
{
_db.Entry(address).State = EntityState.Added;
}
}
existentAddresses.RemoveAll(x => validatedExistentAddresses.Contains(x));
existentAddresses.ForEach(x => _db.Entry(x).State = EntityState.Deleted);
}
It turns out that, as you can see, practically the same block of code is repeated three times, and it does basically the same thing.
I thought of doing a generic method, where I could leave all the code repeated and pass the different parts by parameter.
What I've done so far, looks like this:
public void Test<T>(IEnumerable<T> infoList, Func<T, bool> selector) where T : class
{
var existentInfo = _db.Set<T>().Where(selector).ToList();
var validatedInfo = new List<T>();
foreach (var info in infoList)
{
var existentAttr = existentInfo
.SingleOrDefault(x => x.Description == info.Description);
// Vide obs abaixo
if (existentAttr != null)
{
info.Id = existentAttr.Id;
_db.Entry(existentAttr).State = EntityState.Detached;
_db.Entry(info).State = EntityState.Modified;
validatedInfo.Add(existentAttr);
}
else
{
_db.Entry(info).State = EntityState.Added;
}
}
existentInfo.RemoveAll(x => validatedInfo.Contains(x));
existentInfo.ForEach(x => _db.Entry(x).State = EntityState.Deleted);
}
// O uso seria algo como:
Test<SpecificInfo>(model.SpecificInfo, (inf => inf.EntityId == model.Id));
Test<EntityContact>(model.Contacts, (c => c.EntityId == model.Id));
Test<EntityAddress>(model.Addresses, (ad => ad.EntityId == model.Id));
The existentInfo.SingleOrDefault(x => x.Description == info.Description);
obviously has a compile error, after all the Description
property does not exist within T
, it may even exist, but the compiler has no way of knowing. Of course I could create an interface and restrict the execution of the method for classes that implement this interface, the problem is that I can not "hardcodar" the expression, because in each case the expression must use different properties , see the Edit
method:
[...].SingleOrDefault(x => x.Description == info.Description); // 1º bloco
[...].SingleOrDefault(x => x.Id == contact.Id); // 2º bloco
[...].SingleOrDefault(x => x.Id == address.Id); // 3º bloco
This could also be solved by getting the expression via parameter, as is done with the variable selector
, the problem is that I have no idea how to do this expression being that I need to use the object that is inside the foreach.
Is there any way to parameterize this expression that goes inside SingleOrDefault
? - Taking into account the circumstances presented above.
If not, is there any way I can improve this method and avoid so much repetition?
Is there any other approach I can use that will help me solve this problem?