问题
I can use temporal tables in SQL Server 2016. Entity Framework 6 unfortunately does not know this feature yet. Is there the possibility of a workaround to use the new querying options (see msdn) with Entity Framework 6?
I created a simple demo project with an employee temporal table:
I used the edmx to map the table to entity (thanks to Matt Ruwe):
Everything works fine with pure sql statements:
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();
}
Is it possible to add the history functionality to every entity without pure SQL? My solution as entity extension with reflection to manipulate the SQL query from IQuerable
isn't perfect.
Is there an existing extension or library to do this?
edit: (Based on the commentary by Pawel)
I tried to use a Table-valued Function:
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();
}
Do I have to create a function for each entity or is there a generic option?
回答1:
No, I am afraid, you cannot. I have been back & forth with Microsoft gurus
on this front.
This is a known issue.
And the best advice I have found is to use FromSql
as explained here.
回答2:
Yes you can with a little effort ...
Intercepting EFF intents when trying insert or update generated always columns and avoid errors like
"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."
After that it's work like a charm (already in production on Azure Db)
Example of implementation EFF6 based on columns (StartTime y EndTime) based on:
entity-framework-not-working-with-temporal-table
insert-record-in-temporal-table-using-c-sharp-entity-framework
dbset-attachentity-vs-dbcontext-entryentity-state-entitystate-modified
Thank you!
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());
}
}
}
来源:https://stackoverflow.com/questions/41514629/how-can-i-use-system-versioned-temporal-table-with-entity-framework