Ich war mitten dabei, eine Datenbank-Audit-Spur zu implementieren, bei der CRUD-Operationen, die durch meine Controller in meinem Web-API-Projekt ausgeführt wurden, die alten und neuen POCOs serialisieren und ihre Werte zur späteren Abfrage speichern sollten (historisch, Rollback, etc...).
Als ich alles zum Laufen gebracht hatte, gefiel mir nicht, wie es meine Controller während eines POST-Vorgangs aussehen ließ, weil ich SaveChanges()
zweimal aufrufen musste, einmal um die ID für das eingefügte Entity abzurufen und dann erneut, um den Audit-Eintrag zu bestätigen, der diese ID benötigte.
Ich habe mein Projekt (das sich noch in den Anfängen befand) umgestellt, um Sequenzen anstelle von Identitätsspalten zu verwenden. Dies hat den zusätzlichen Vorteil, mich weiter von SQL Server zu abstrahieren, obwohl das nicht wirklich ein Problem ist, aber es erlaubt mir auch, die Anzahl der Commits zu reduzieren und diese Logik aus dem Controller herauszuziehen und in meine Service-Schicht einzufügen, die meine Controller von den Repositories abstrahiert und es mir ermöglicht, Arbeiten wie diese Überwachung in dieser "Shim"-Schicht zu erledigen.
Sobald das Sequence
-Objekt erstellt und eine gespeicherte Prozedur erstellt wurde, habe ich die folgende Klasse erstellt:
public class SequentialIdProvider : ISequentialIdProvider
{
private readonly IService _sequenceValueService;
public SequentialIdProvider(IService sequenceValueService)
{
_sequenceValueService = sequenceValueService;
}
public int GetNextId()
{
var value = _sequenceValueService.SelectQuery("GetSequenceIds @numberOfIds", new SqlParameter("numberOfIds", SqlDbType.Int) { Value = 1 }).ToList();
if (value.First() == null)
{
throw new Exception("Die nächsten IDs aus der Sequenz konnten nicht abgerufen werden.");
}
return value.First().FirstValue;
}
public IList GetNextIds(int numberOfIds)
{
var values = _sequenceValueService.SelectQuery("GetSequenceIds @numberOfIds", new SqlParameter("numberOfIds", SqlDbType.Int) { Value = numberOfIds }).ToList();
if (values.First() == null)
{
throw new Exception("Die nächsten IDs aus der Sequenz konnten nicht abgerufen werden.");
}
var list = new List();
for (var i = values.First().FirstValue; i <= values.First().LastValue; i++)
{
list.Add(i);
}
return list;
}
}
Das bietet einfach zwei Möglichkeiten, IDs zu erhalten, einzeln und in einem Bereich.
Dies funktionierte während des ersten Satzes von Unit-Tests super, aber sobald ich es in einem realen Szenario getestet habe, wurde mir schnell klar, dass ein einziger Aufruf von GetNextId()
den gleichen Wert für die Lebensdauer dieses Kontexts zurückgeben würde, bis SaveChanges()
aufgerufen wird, was jeglichen realen Nutzen zunichte macht.
Ich bin mir nicht sicher, ob es einen Ausweg aus diesem Dilemma gibt, abgesehen von der Erstellung eines zweiten Kontexts (keine Option) oder dem Rückgriff auf ADO.NET im alten Stil und dem direkten Aufruf von SQL-Befehlen und der Verwendung von AutoMapper, um zum gleichen Ergebnis zu gelangen. Beides spricht mich nicht an, also hoffe ich, dass jemand anderes eine Idee hat.