Voglio verificare se qualche messaggio esiste già prima di aggiungerlo al database, ma la mia query corrente carica l'intera tabella in memoria. La query generata dal mio codice è fondamentalmente solo select * from tableName
.
Come posso riscrivere questa query per essere valutata nel database?
public void AddMessages(IEnumerable<Message> messages)
{
if (messages == null)
throw new ArgumentNullException(nameof(messages));
var duplicates = (from currMsg in context.Messages
where messages.Any(msg =>
msg.Prop1 == currMsg.Prop1 &&
msg.Prop2 == currMsg.Prop2 &&
msg.Prop3 == currMsg.Prop3)
select currMsg);
var messagesWithoutDuplicates = messages.Except(duplicates);
context.Messages.AddRange(messagesWithoutDuplicates);
context.SaveChanges();
}
Potrei anche eseguirlo in un ciclo, ma poi creerei molte chiamate db invece di 1 e preferirei farlo in una singola chiamata.
Poiché non esiste un modo semplice per farlo in una singola chiamata, ho deciso di sacrificare le prestazioni e mantenere la leggibilità e la testabilità. Questa è la mia soluzione:
using (var transaction = context.Database.BeginTransaction())
{
try
{
foreach (var message in messages)
{
var exists = context.Messages.Any(msg => msg.Prop1 == message.Prop1 &&
msg.Prop2 == message.Prop2 &&
msg.Prop3 == message.Prop3 &&);
if (!exists)
{
context.Messages.Add(message);
}
}
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
_logger.Error(ex);
transaction.Rollback();
throw;
}
}
A seconda del caso d'uso, potrebbe essere necessario inserirli uno per uno e fidarsi dell'indice univoco del database (ne hai uno, giusto?) Per rimetterlo in faccia se è un duplicato.
Vi sono due punti deboli nel codice oltre al consumo di memoria: concorrenza (cosa succede se qualcun altro inserisce mentre controlli i duplicati) e il fatto che i record da inserire potrebbero essere duplicati che non hai controllato.