8
I have a model called Entity
, this model has links (1 - N) with three other models.
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 certain occasion, a method (let’s call Edit
) receives an instance of this model and needs to verify which properties have been modified (based on the model 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 among all the items in the list which have been added, changed or deleted (this comparing to another list, see below).
Example of how the method is Edit
:
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, as you can see, practically the same block of code is repeated three times, and it does basically the same thing.
I thought of making a generic method, where I could leave all the code repeated and pass the different parts by parameter.
What I’ve done so far is 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 excerpt existentInfo.SingleOrDefault(x => x.Description == info.Description);
obviously accuses a build error, after all the property Description
does not exist within T
, 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 to classes that implement this interface, the problem is that I can’t "hardcodar" to Expression, because in each case Expression should use different properties, see the method Edit
:
[...].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 receiving 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 into the
SingleOrDefault
? - Taking into account the circumstances set out above.If not, is there any way I can improve on this method and avoid so much repetition?
Is there any other approach I can use that will help me solve this problem?
The first option already suits me, because the selection I make is in a list in memory. Thank you, was as I wanted =).
– Jéf Bueno