Entity Framework Code First и последовательности SQL Server 2012

Я находился в процессе разработки журнала аудита базы данных, посредством которого операции CRUD, выполняемые через мои контроллеры в моем проекте Web API, могли бы сериализовать старые и новые документы и сохранять их значения для последующего извлечения (исторические данные, откат и т. Д.).

Когда я все это заработал, мне не понравилось, как это заставило мои контроллеры выглядеть во время POST, потому что мне пришлось позвонить SaveChanges() дважды, один раз, чтобы получить идентификатор для вставленной сущности, а затем снова, чтобы зафиксировать запись аудита, которая должна была знать этот идентификатор.

Я решил преобразовать проект (все еще в зачаточном состоянии), чтобы использовать последовательности вместо идентичных столбцов. Это дает дополнительный бонус за дальнейшее абстрагирование меня от SQL Server, хотя на самом деле это не проблема, но также позволяет мне уменьшить количество коммитов и позволяет извлекать эту логику из контроллера и вставлять ее в мой уровень обслуживания, который абстрагирует мои контроллеры от репозиториев и позволяет мне выполнять такую ​​же проверку в этом слое "подкладки".

Однажды Sequence Объект был создан и хранимая процедура для его раскрытия, я создал следующий класс:

public class SequentialIdProvider : ISequentialIdProvider
{
    private readonly IService<SequenceValue> _sequenceValueService;

    public SequentialIdProvider(IService<SequenceValue> 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("Unable to retrieve the next id's from the sequence.");
        }

        return value.First().FirstValue;
    }

    public IList<int> GetNextIds(int numberOfIds)
    {
        var values = _sequenceValueService.SelectQuery("GetSequenceIds @numberOfIds", new SqlParameter("numberOfIds", SqlDbType.Int) { Value = numberOfIds }).ToList();
        if (values.First() == null)
        {
            throw new Exception("Unable to retrieve the next id's from the sequence.");
        }

        var list = new List<int>();
        for (var i = values.First().FirstValue; i <= values.First().LastValue; i++)
        {
            list.Add(i);
        }

        return list;
    }
}

Который просто предоставляет два способа получения идентификаторов, один и диапазон.

Все это прекрасно работало во время первого набора юнит-тестов, но как только я начал тестировать его в реальном сценарии, я быстро понял, что один вызов GetNextId() будет возвращать то же значение для жизни этого контекста, пока SaveChanges() называется, тем самым сводя на нет любую реальную выгоду.

Я не уверен, есть ли способ обойти эту короткую позицию: создать второй контекст (не вариант) или перейти на ADO.NET старой школы и сделать прямые вызовы SQL и использовать AutoMapper, чтобы получить тот же чистый результат. Ни один из них не привлекает меня, поэтому я надеюсь, что у кого-то есть идея.

1 ответ

Не знаю, может ли это вам помочь, но именно так я и сделал свой журнал аудита, используя код в первую очередь. Следующее закодировано в класс, наследуемый от DbContext.

в моем конструкторе у меня есть следующее

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

Это мой метод сохранения изменений, подключенный ранее

void SavingChanges(object sender, EventArgs e) {
        Debug.Assert(sender != null, "Sender can't be null");
        Debug.Assert(sender is ObjectContext, "Sender not instance of ObjectContext");

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

        addedEntities.ToList().ForEach(a => {
            //Assign ids to objects that don't have
            if (a.Entity is IIdentity && (a.Entity as IIdentity).Id == Guid.Empty)
                (a.Entity as IIdentity).Id = Guid.NewGuid();

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

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

И это методы, которые использовались ранее для создания деталей журнала аудита

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;
    }

    /// <summary>
    /// Creates a string of all modified properties for an entity.
    /// </summary>
    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();
    }

    /// <summary>
    /// Creates a string of all modified properties' new values for an entity.
    /// </summary>
    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();
    }

Надеюсь, что это может указать вам направление, которое может помочь вам решить вашу проблему.

Другие вопросы по тегам