Как использовать временную таблицу System-Versioned с платформой Entity Framework?

Я могу использовать временные таблицы в SQL Server 2016. Entity Framework 6, к сожалению, пока не знает эту функцию. Существует ли возможность обходного пути для использования новых параметров запросов (см. Msdn) в Entity Framework 6?

Я создал простой демонстрационный проект с временной таблицей сотрудников:

enter image description here

Я использовал edmx для сопоставления таблицы с сущностью (спасибо Мэтту Руве):

enter image description here

Все отлично работает с чистыми выражениями SQL:

using (var context = new TemporalEntities())
{
    var employee = context.Employees.Single(e => e.EmployeeID == 2);
    var query = 
      [email protected]"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? Мое решение как расширение сущности с отражением манипулирования SQL-запросом из IQuerable не идеально. Существует ли существующее расширение или библиотека для этого?

редактировать: (на основе комментария Павла)

Я пытался использовать табличную функцию:

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

Нужно ли создавать функцию для каждой сущности или есть общая опция?

Ответы

Ответ 1

Нет, боюсь, вы не можете. Я был туда-сюда с Microsoft gurus на этом фронте.

Это известная проблема. И лучший совет, который я нашел, это использовать FromSql как описано здесь.

Ответ 2

Да, вы можете с небольшим усилием...

Перехват 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)

Пример реализации EFF6 на основе столбцов (StartTime y EndTime) на основе:

сущность-каркасный не работоспособный-с временным столом

вставить рекорд-в-височной стол-используя-до-диез-сущность-рамки

dbset-attachentity-против-DbContext-entryentity-состояние- entitystate-модифицированные

Спасибо!

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
    ///     /questions/843951/entity-framework-not-working-with-temporal-table
    ///     /questions/13798517/insert-record-in-temporal-table-using-c-entity-framework
    ///     /questions/90507/dbsetattachentity-vs-dbcontextentryentitystate-entitystatemodified
    /// </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());
        }
    }
}

Ответ 3

Создайте представление с временной таблицей (история) и сопоставьте это представление с инфраструктурой Entity Framework.

Egs:

CREATE VIEW V_View
AS 
select * from hist.TableHistory

И отобразите структуру V_View в Entity Framework