簡體   English   中英

如何在實體框架中使用系統版本的臨時表?

[英]How can I use System-Versioned Temporal Table with Entity Framework?

我可以在 SQL Server 2016 中使用時態表。不幸的是,Entity Framework 6 還不知道這個功能。 在 Entity Framework 6 中使用新的查詢選項(請參閱msdn )是否有解決方法的可能性?

我使用員工臨時表創建了一個簡單的演示項目:

在此處輸入圖片說明

我使用 edmx 將表映射到實體( 感謝 Matt Ruwe ):

在此處輸入圖片說明

使用純 sql 語句一切正常:

using (var context = new TemporalEntities())
{
    var employee = context.Employees.Single(e => e.EmployeeID == 2);
    var query = 
      $@"SELECT * FROM [TemporalTest].[dbo].[{nameof(Employee)}]
         FOR SYSTEM_TIME BETWEEN
         '0001-01-01 00:00:00.00' AND '{employee.ValidTo:O}'
         WHERE EmployeeID = 2";
    var historyOfEmployee = context.Employees.SqlQuery(query).ToList();
}    

是否可以在沒有純 SQL 的情況下為每個實體添加歷史功能? 我的解決方案作為帶有反射的實體擴展來操作來自IQuerable的 SQL 查詢並不完美。 是否有現有的擴展或庫可以做到這一點?

編輯:(基於Pawel的評論)

我嘗試使用表值函數:

CREATE FUNCTION dbo.GetEmployeeHistory(
    @EmployeeID int, 
    @startTime datetime2, 
    @endTime datetime2)
RETURNS TABLE
AS
RETURN 
(
    SELECT 
        EmployeeID,
        [Name], 
        Position, 
        Department, 
        [Address],
        ValidFrom,
        ValidTo
    FROM dbo.Employee
    FOR SYSTEM_TIME BETWEEN @startTime AND @endTime
    WHERE EmployeeID = @EmployeeID
);
using (var context = new TemporalEntities())
{
    var employee = context.Employees.Single(e => e.EmployeeID == 2);
    var historyOfEmployee =
      context.GetEmployeeHistory(2, DateTime.MinValue, employee.ValidTo).ToList();
} 

我必須為每個實體創建一個函數還是有一個通用選項?

不,恐怕你不能。 在這方面,我一直在與Microsoft gurus

這是一個已知問題 我已經找到了最好的建議是使用FromSql作為解釋在這里

是的,你可以稍微努力一下......

在嘗試插入或更新生成的始終列時攔截 EFF 意圖並避免諸如

"Cannot insert an explicit value into a GENERATED ALWAYS column in table 'xxx.dbo.xxxx'. Use INSERT with a column list to exclude the GENERATED ALWAYS column, or insert a DEFAULT into GENERATED ALWAYS column."

之后它就像一個魅力(已經在 Azure Db 上生產)

基於列 (StartTime y EndTime)的實現EFF6示例基於:

實體框架不工作時態表

使用 c-sharp-entity-framework 在臨時表中插入記錄

dbset-attachentity-vs-dbcontext-entryentity-state-entitystate-modified

謝謝!

using System.Data.Entity.Infrastructure.Interception;
using System.Data.Entity.Core.Common.CommandTrees;
using System.Data.Entity.Core.Metadata.Edm;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;

namespace Ubiquité.Clases
{

    /// <summary>
    /// Evita que EFF se haga cargo de ciertos campos que no debe tocar Ej: StartTime y EndTime
    ///     de las tablas versionadas o bien los row_version por ejemplo
    ///     https://stackoverflow.com/questions/40742142/entity-framework-not-working-with-temporal-table
    ///     https://stackoverflow.com/questions/44253965/insert-record-in-temporal-table-using-c-sharp-entity-framework
    ///     https://stackoverflow.com/questions/30987806/dbset-attachentity-vs-dbcontext-entryentity-state-entitystate-modified
    /// </summary>
    /// <remarks>
    /// "Cannot insert an explicit value into a GENERATED ALWAYS column in table 'xxx.dbo.xxxx'.
    /// Use INSERT with a column list to exclude the GENERATED ALWAYS column, or insert a DEFAULT
    /// into GENERATED ALWAYS column."
    /// </remarks>
    internal class TemporalTableCommandTreeInterceptor : IDbCommandTreeInterceptor
    {
        private static readonly List<string> _namesToIgnore = new List<string> { "StartTime", "EndTime" };

        public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
        {
            if (interceptionContext.OriginalResult.DataSpace == DataSpace.SSpace)
            {
                var insertCommand = interceptionContext.Result as DbInsertCommandTree;
                if (insertCommand != null)
                {
                    var newSetClauses = GenerateSetClauses(insertCommand.SetClauses);

                    var newCommand = new DbInsertCommandTree(
                        insertCommand.MetadataWorkspace,
                        insertCommand.DataSpace,
                        insertCommand.Target,
                        newSetClauses,
                        insertCommand.Returning);

                    interceptionContext.Result = newCommand;
                }

                var updateCommand = interceptionContext.Result as DbUpdateCommandTree;
                if (updateCommand != null)
                {
                    var newSetClauses = GenerateSetClauses(updateCommand.SetClauses);

                    var newCommand = new DbUpdateCommandTree(
                        updateCommand.MetadataWorkspace,
                        updateCommand.DataSpace,
                        updateCommand.Target,
                        updateCommand.Predicate,
                        newSetClauses,
                        updateCommand.Returning);

                    interceptionContext.Result = newCommand;
                }
            }
        }

        private static ReadOnlyCollection<DbModificationClause> GenerateSetClauses(IList<DbModificationClause> modificationClauses)
        {
            var props = new List<DbModificationClause>(modificationClauses);
            props = props.Where(_ => !_namesToIgnore.Contains((((_ as DbSetClause)?.Property as DbPropertyExpression)?.Property as EdmProperty)?.Name)).ToList();

            var newSetClauses = new ReadOnlyCollection<DbModificationClause>(props);
            return newSetClauses;
        }
    }

    /// <summary>
    /// registra TemporalTableCommandTreeInterceptor con EFF
    /// </summary>
    public class MyDBConfiguration : DbConfiguration
    {
        public MyDBConfiguration()
        {
            DbInterception.Add(new TemporalTableCommandTreeInterceptor());
        }
    }
}

我只使用 Entity Framework Core 對此進行了測試,但我認為應該可以使用類似的解決方案。

正如@StephenWitherden 所提到的,目前有一個未解決的問題來支持 EF Core 的開箱即用:

https://github.com/dotnet/efcore/issues/4693

我寫了一個完整的指南,如何在沒有任何第三方庫的情況下使用 Entity Framework Core 實現它:

https://stackoverflow.com/a/64244548/3850405

在此處添加了對臨時表的初始支持: e7c0b9d (模型/元數據部分)和此處的4b25a88 (查詢部分),並將在下一個預覽版(預覽版 8)以及當前的每晚比特中提供。

用法:

將實體映射到臨時表可以在 OnModelCreating 中完成,如下所示:

modelBuilder.Entity<MyTemporalEntity>().ToTable(tb => tb.IsTemporal());

還支持其他配置 - 歷史表名稱/模式、期間開始和期間結束列的名稱

modelBuilder.Entity<MyTemporalEntity>().ToTable(tb => tb.IsTemporal(ttb =>
{
    ttb.HasPeriodStart("SystemTimeStart");
    ttb.HasPeriodEnd("SystemTimeEnd");
    ttb.WithHistoryTable("MyHistoryTable", "mySchema");
}));

支持遷移,因此可以將現有實體轉換為時間實體。

查詢:

var myDate = new DateTime(2020, 1, 1);
context.MyTemporalEntities.TemporalAsOf(myDate).Where(e => e.Id < 10);

支持的操作: TemporalAsOfTemporalAllTemporalBetweenTemporalFromToTemporalContainedIn

一些限制和注意事項

  • 使用時間操作的查詢總是被標記為“NoTracking”。 此類查詢可能會返回具有相同鍵的多個實體,否則 EF 將無法正確解析它們的身份。

  • DbSet直接支持臨時操作,而不是IQueryable 在繼承的情況下,它們不能應用於OfType操作。 相反,使用:

context.Set<MyDerivedEntity>().TemporalAsOf(...);
  • 只有AsOf操作支持導航擴展,因為它是唯一保證結果圖一致性的時間操作。 對於其他時間操作,必須使用Join手動創建導航。

  • 擴展導航時,目標實體也必須映射到時態表。 臨時操作從源傳播到目標。 不支持從時間實體導航到非時間實體。

context.Customers.TemporalAsOf(new DateTime(2020, 1, 1)).Select(c => c.Orders)

將在 2020 年 1 月 1 日返回客戶及其訂單。臨時操作會自動應用於客戶和訂單。

  • 不支持對映射到時態表的參數進行設置操作(例如 Concat、Except)。 (問題在此處跟蹤#25365

來自毛馬爾的報價

使用DatabaseGenerated屬性為您的實體類型添加元數據類

[MetadataType(typeof(Delegations_Metadata))]
public partial class Delegations
{
    public partial class Delegations_Metadata
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 
        public DateTime SysStartTime { get; set; }

        [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 
        public DateTime SysEndTime { get; set; }

    }
}

覆蓋DbContext.SaveChanges以從創建和更新命令中顯式排除具有DatabaseGenerated屬性的屬性

public override int SaveChanges()
{
    foreach (var entry in ChangeTracker.Entries()
        .Where(x => x.State == EntityState.Modified || x.State == EntityState.Added))
    {
        var notMappedProperties = entry.Entity.GetType().GetProperties()
            .Where(x => x.GetCustomAttribute<DatabaseGeneratedAttribute>()
            ?.DatabaseGeneratedOption == DatabaseGeneratedOption.Computed);

        foreach (var notMappedProperty in notMappedProperties)
            entry.Property(notMappedProperty.Name).IsModified = false;
    }

    return base.SaveChanges();
}

此解決方案適用於:

  • EF6
  • 數據庫優先方法
  • 帶有GENERATED ALWAYS列的系統版本表

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM