简体   繁体   English

实体框架代码优先和SQL Server 2012序列

[英]Entity Framework Code First and SQL Server 2012 Sequences

I was in the middle of implementing a database audit trail whereby CRUD operations performed through my controllers in my Web API project would serialize the old and new poco's and store their values for later retrieval (historical, rollback, etc...). 我正在实现一个数据库审计跟踪,在我的Web API项目中通过我的控制器执行的CRUD操作将序列化旧的和新的poco并存储它们的值以供以后检索(历史,回滚等)。

When I got it all working, I did not like how it made my controllers look during a POST because I ended up having to call SaveChanges() twice, once to get the ID for the inserted entity and then again to commit the audit record which needed to know that ID. 当我把它全部工作时,我不喜欢它在POST期间如何使我的控制器看起来因为我最终必须调用SaveChanges()两次,一次获取插入实体的ID然后再次提交审计记录需要知道这个ID。

I set out to convert the project (still in its infancy) to use sequences instead of identity columns. 我开始将项目(仍处于初期阶段)转换为使用序列而不是标识列。 This has the added bonus of further abstracting me from SQL Server, though that is not really an issue, but it also allows me to reduce the number of commits and lets me pull that logic out of the controller and stuff it into my service layer which abstracts my controllers from the repositories and lets me do work like this auditing in this "shim" layer. 这有额外的好处,可以从SQL Server中进一步抽象我,虽然这不是一个真正的问题,但它也允许我减少提交的数量,让我从控制器中取出逻辑并将其填充到我的服务层中从存储库中抽象出我的控制器,让我在这个“垫片”层中像这样的审计一样工作。

Once the Sequence object was created and a stored procedure to expose it, I created the following class: 创建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;
    }
}

Which simply provides two ways to get IDs, a single and a range. 其中只提供了两种获取ID,单一和范围的方法。

This all worked great during the first set of unit tests but as soon as I started testing it in a real world scenario, I quickly realized that a single call to GetNextId() would return the same value for the life of that context, until SaveChanges() is called, thus negating any real benefit. 这在第一组单元测试中都很有效,但是当我开始在真实场景中测试它时,我很快意识到对GetNextId()的单个调用将在该上下文的生命周期中返回相同的值,直到SaveChanges()被称为,从而否定任何真正的好处。

I am not sure if there is a way around this short of creating a second context (not an option) or going old school ADO.NET and making direct SQL calls and use AutoMapper to get to the same net result. 我不确定是否有办法绕过创建第二个上下文(不是选项)或者去旧学校ADO.NET并进行直接SQL调用并使用AutoMapper获得相同的净结果。 Neither of these are appeal to me so I am hoping someone else has an idea. 这些都没有吸引我,所以我希望其他人有一个想法。

Don't know if this might help you, but this is how I did my audit log trail using code first. 不知道这是否对您有所帮助,但这就是我首先使用代码执行审计日志跟踪的方法。 The following is coded into a class inheriting from DbContext. 以下内容编码为继承自DbContext的类。

in my constructor I have the following 在我的构造函数中,我有以下内容

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

This is my saving changes method wired up previously 这是我之前连接的保存更改方法

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

And these are the methods used previosly to build up the audit log details 这些是用于建立审计日志详细信息的方法

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

Hopefully this might point you into a direction that might help you solve your problem. 希望这可能会指向一个可以帮助您解决问题的方向。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM