Ответ 1
Нет, боюсь, вы не можете. Я был туда-сюда с Microsoft gurus
на этом фронте.
Это известная проблема. И лучший совет, который я нашел, это использовать FromSql
как описано здесь.
Я могу использовать временные таблицы в SQL Server 2016. Entity Framework 6, к сожалению, пока не знает эту функцию. Существует ли возможность обходного пути для использования новых параметров запросов (см. Msdn) в Entity Framework 6?
Я создал простой демонстрационный проект с временной таблицей сотрудников:
Я использовал edmx для сопоставления таблицы с сущностью (спасибо Мэтту Руве):
Все отлично работает с чистыми выражениями 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();
}
Нужно ли создавать функцию для каждой сущности или есть общая опция?
Нет, боюсь, вы не можете. Я был туда-сюда с 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)
Пример реализации 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());
}
}
}
Создайте представление с временной таблицей (история) и сопоставьте это представление с инфраструктурой Entity Framework.
Egs:
CREATE VIEW V_View
AS
select * from hist.TableHistory
И отобразите структуру V_View в Entity Framework