3 Stimmen

Entity Framework Code First und SQL Server 2012 Sequenzen

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.

0voto

3dd Punkte 2520

Ich weiß nicht, ob dir das helfen könnte, aber so habe ich mein Audit-Protokoll mit Code First erstellt. Im Folgenden ist der Code in eine Klasse eingefügt, die von DbContext erbt.

In meinem Konstruktor habe ich folgendes:

IObjectContextAdapter objectContextAdapter = (this as IObjectContextAdapter);
objectContextAdapter.ObjectContext.SavingChanges += SavingChanges;

Dies ist meine Methode zum Speichern von Änderungen, die zuvor verbunden wurde:

void SavingChanges(object sender, EventArgs e) {
        Debug.Assert(sender != null, "Sender kann nicht null sein");
        Debug.Assert(sender is ObjectContext, "Sender ist nicht eine Instanz von ObjectContext");

        ObjectContext context = (sender as ObjectContext);
        IEnumerable modifiedEntities = context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
        IEnumerable addedEntities = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added);

        addedEntities.ToList().ForEach(a => {
            //weist IDs Objekten ohne IDs zu
            if (a.Entity is IIdentity && (a.Entity as IIdentity).Id == Guid.Empty)
                (a.Entity as IIdentity).Id = Guid.NewGuid();

            this.Set().Add(AuditLogEntryFactory(a, _AddedEntry));
        });

        modifiedEntities.ToList().ForEach(m => {
            this.Set().Add(AuditLogEntryFactory(m, _ModifiedEntry));
        });
    }

Und dies sind die zuvor verwendeten Methoden, um die Details des Audit-Protokolls aufzubauen:

private AuditLogEntry AuditLogEntryFactory(ObjectStateEntry entry, string entryType) {
        AuditLogEntry auditLogEntry = new AuditLogEntry() {
            EntryDate = DateTime.Now,
            EntryType = entryType,
            Id = Guid.NewGuid(),
            NewValues = AuditLogEntryNewValues(entry),
            Table = entry.EntitySet.Name,
            UserId = _UserId
        };

        if (entryType == _ModifiedEntry) auditLogEntry.OriginalValues = AuditLogEntryOriginalValues(entry);

        return auditLogEntry;
    }

    /// 
    /// Erstellt eine Zeichenfolge aller geänderten Eigenschaften für ein Entity.
    /// 
    private string AuditLogEntryOriginalValues(ObjectStateEntry entry) {
        StringBuilder stringBuilder = new StringBuilder();

        entry.GetModifiedProperties().ToList().ForEach(m => {
            stringBuilder.Append(String.Format("{0} = {1},", m, entry.OriginalValues[m]));
        });

        return stringBuilder.ToString();
    }

    /// 
    /// Erstellt eine Zeichenfolge aller geänderten Eigenschaften und ihrer neuen Werte für ein Entity.
    /// 
    private string AuditLogEntryNewValues(ObjectStateEntry entry) {
        StringBuilder stringBuilder = new StringBuilder();

        for (int i = 0; i < entry.CurrentValues.FieldCount; i++) {
            stringBuilder.Append(String.Format("{0} = {1},",
                entry.CurrentValues.GetName(i), entry.CurrentValues.GetValue(i)));
        }

        return stringBuilder.ToString();
    }

Hoffentlich kann dich das in eine Richtung bringen, die dir bei der Lösung deines Problems helfen könnte.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X