c# - Entity Framework 代码优先和 SQL Server 2012 序列

标签 c# sql entity-framework sql-server-2012 sequence-sql

我正在实现一个数据库审计跟踪,通过我的 Web API 项目中的 Controller 执行的 CRUD 操作将序列化旧的和新的 poco 并存储它们的值以供以后检索(历史、回滚等...) .

当我全部工作时,我不喜欢它让我的 Controller 在 POST 期间看起来如何,因为我最终不得不调用 SaveChanges() 两次,一次是为了获取插入的 ID实体,然后再次提交需要知道该 ID 的审计记录。

我着手将项目(仍处于起步阶段)转换为使用序列而不是标识列。这有进一步从 SQL Server 中抽象出来的额外好处,虽然这不是真正的问题,但它也允许我减少提交次数并让我将该逻辑从 Controller 中提取出来并将其填充到我的服务层中从存储库中抽象出我的 Controller ,并让我在这个“垫片”层中进行类似审计的工作。

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

它简单地提供了两种获取 ID 的方法,一个是单个 ID,一个是一个范围。

这一切在第一组单元测试中运行良好,但当我开始在真实场景中进行测试时,我很快意识到调用 GetNextId() 会返回相同的结果该上下文生命周期的值(value),直到 SaveChanges() 被调用,从而否定任何真正的好处。

我不确定是否有办法解决这个问题,即创建第二个上下文(不是一个选项)或使用老式的 ADO.NET 并进行直接 SQL 调用并使用 AutoMapper 获得相同的最终结果。这些都对我没有吸引力,所以我希望其他人有想法。

最佳答案

不知道这是否对您有帮助,但这是我首先使用代码进行审计日志跟踪的方式。 以下编码为继承自 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();
    }

希望这可以为您指明方向,帮助您解决问题。

关于c# - Entity Framework 代码优先和 SQL Server 2012 序列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24127721/

相关文章:

c# - 在 C# 中创建自定义绑定(bind)

mysql - 在MySQL中如何编写SQL来搜索字段中的单词?

C# 使用模式将两个字符串列表相交

c# - 在 WEB API 方法中处理通用对象的正确方法是什么?

sql - Postgres : UPDATE a boolean column that sets all other booleans in that column to false

c# - 如何在 C# 中将 System.Collection.IEnumerable 转换为 List<T>?

.net - 在应用程序中添加动态数据透视表的最佳方式

c# - 如何使用内存数据库中的Entity Framework核心自动递增列?

entity-framework - 如何在Entity Framework 6 DbContext.Database.BeginTransaction中配置事务超时?

c# - WPF 设计器中标记扩展参数的设计时检查