仅比较 entity framework 6 中日期时间的时间与 odp.net Oracle 12c

Compare only time from datetime in entity framework 6 with odp.net Oracle 12c

我正在使用 entity framework 6 与 oracle 和 Sql。 Timespan 数据类型不适用于 oracle。所以我将数据类型更改为日期时间。现在我只想将日期时间中的时间与 Linq 查询进行比较。前任。

var db0010016 = _idb0010016Rep.GetAll().Where(e => e.ExecutionTime.TimeOfDay == viewmodel.ExecutionTime).FirstOrDefault();

在上面的示例中,e.ExecutionTime 是日期时间,viewmodel.ExecutionTime 是时间跨度。我正在使用 timeofday 函数将其转换为时间跨度

上面的查询执行失败所以我使用了 DbFunctions.CreateTime() 函数

var db0010016 = _idb0010016Rep.FindBy(e => DbFunctions.CreateTime(e.ExecutionTime.Hour, e.ExecutionTime.Minute, e.ExecutionTime.Second) == exetime).FirstOrDefault();

上面的 ex exetime 是 timespan.still 我得到低于错误

{"Invalid parameter binding\r\nParameter name: ParameterName"}

能否将 oracle timespan 和 SQL datetime 转换为字符串,然后进行比较。喜欢:

var db0010016 = _idb0010016Rep.GetAll().Where(e => e.ExecutionTime.TimeOfDay.ToString() == viewmodel.ExecutionTime.ToString()).FirstOrDefault()

由于 oracle 的日期和时间问题,我们只使用字符串:

using(MyDbContext ctx = new MyDbContext())
{
    TimeSpan myTime = new TimeSpan(12, 00, 00);
    string myTimeString = myTime.ToString("hh':'mm':'ss");
    List<ExecutionObjects> tmp = ctx.ExecutionObjects.Where(a => a.ExecutionTime.EndsWith(myTimeString)).ToList();

    // Access field in source with seperated DateTime-property.
    tmp.ForEach(e => Console.WriteLine(e.ExecutionTimeDateTime.ToShortDateString()));
}

在源代码中你可以添加一个 DateTime-parsing-属性:

public class ExecutionObject
{
    [Column("ColExecutionTime")]
    public string ExecutionTime { get; set; }
    [NotMapped]
    public DateTime ExecutionTimeDateTime {
        get
        {
            return DateTime.ParseExact(this.ExecutionTime, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
        }
        set
        {
            this.ExecutionTime = value.ToString("yyyy-MM-dd HH:mm:ss");
        }
    }
}

不是最漂亮的版本,但可以用。

这是 DbFunctions 中基于 Oracle 的问题。如果您激活 sql-log,您会看到使用了一个未知的函数 "CREATETIME()"。

激活sql-日志: ctx.Database.Log = Console.WriteLine;

日志将如下所示:

SELECT *
  FROM "ExecutionTimes" "Extent1"
 WHERE ((((CREATETIME (EXTRACT (HOUR FROM (CAST (
[...]

如果您只想比较可以使用 DbFunctions 中受支持的 EDM 规范函数的时间(请参阅 here)。 很遗憾 DbFunction.CreateTime 不受支持。

不过,如果您对以秒为单位的比较感兴趣,您可以这样做:

        var refTime = new DateTime(2017, 12, 13, 09, 30, 31);
        using (this.ctx = new MyContext())
        {
            var results = this.ctx.Groupings.Where(e => DbFunctions.DiffSeconds(e.EndDate, refTime) % 86400 == 0).ToList();
        }

在这里,您使用 EDM 函数 DiffSeconds 以秒为单位求差,并用一天中的秒数取模。

执行的查询是:

select
  "Extent1"."GROUP_TYPE" as "GROUP_TYPE",
  "Extent1"."GROUP_ENTITY_ID" as "GROUP_ENTITY_ID",
  "Extent1"."ITEM_ENTITY_ID" AS "ITEM_ENTITY_ID",
  "Extent1"."DATE_START" as "DATE_START",
  "Extent1"."DATE_END" AS "DATE_END"
from "MYSCHEMA"."ENTITY_GROUP_REL" "Extent1" where (0 = (mod( extract( day from ( cast(:p__linq__0 as timestamp(9)) -  cast("Extent1"."DATE_END" as timestamp(9))))*24*60*60 +  extract( hour from( cast(:p__linq__0 as timestamp(9)) -  cast("Extent1"."DATE_END" as timestamp(9))))*60*60 +  extract( minute from ( cast(:p__linq__0 as timestamp(9)) -  cast("Extent1"."DATE_END" as timestamp(9))))*60 +  extract( second from ( cast(:p__linq__0 as timestamp(9)) -  cast("Extent1"."DATE_END" as timestamp(9)))) ,86400)))

如您所见,它可以正确转换为服务器端的 oracle 函数。

希望对你有所帮助,

尼古拉

EF6 查询转换器不支持 DateTime.TimeOfDay,Oracle 提供程序不支持 DbFunctions.CreateTimeTimeSpan parameters/constants.

按照另一个答案的建议,在将存储从 DateTime 切换到 string 之前还有一些选项。

首先,对于相等性检查,您可以通过将时间分量提取到单独的变量(查询参数)中来比较时间分量:

var hour = viewmodel.ExecutionTime.Hours;
var minute = viewmodel.ExecutionTime.Minutes;
var second = viewmodel.ExecutionTime.Seconds;

var db0010016 = _idb0010016Rep.FindBy(e => 
    e.ExecutionTime.Hour == hour && e.ExecutionTime.Minute == minute && e.ExecutionTime.Second == second)
    .FirstOrDefault();

或进入假DateTime变量(queryParameter):

var executionTime = DateTime.Today + viewmodel.ExecutionTime;
var db0010016 = _idb0010016Rep.FindBy(e => 
    e.ExecutionTime.Hour == executionTime.Hour && e.ExecutionTime.Minute == executionTime.Minute && e.ExecutionTime.Second == executionTime.Second)
    .FirstOrDefault();

其次,您可以将时间转换为秒。这还允许您执行任何比较:

var executionTime = (int)viewmodel.ExecutionTime.TotalSeconds;
var db0010016 = _idb0010016Rep.FindBy(e => 
    60 * 60 * e.ExecutionTime.Hour + 60 * e.ExecutionTime.Minute + e.ExecutionTime.Second == executionTime)
    .FirstOrDefault();

但是手动完成所有这些操作非常烦人且容易出错。我可以提供的是提供自定义扩展方法的小实用程序 class:

public static partial class QueryableExtensions
{
    public static IQueryable<T> ConvertTimeSpans<T>(this IQueryable<T> source)
    {
        var expr = new TimeSpanConverter().Visit(source.Expression);
        return source == expr ? source : source.Provider.CreateQuery<T>(expr);
    }

    class TimeSpanConverter : ExpressionVisitor
    {
        static readonly Expression<Func<DateTime, int>> ConvertTimeOfDay = dt =>
            60 * (60 * dt.Hour + dt.Minute) + dt.Second;

        static int ConvertTimespan(TimeSpan ts) => 
            60 * (60 * ts.Hours + ts.Minutes) + ts.Seconds;

        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Type == typeof(TimeSpan))
            {
                if (node.Member.DeclaringType == typeof(DateTime) && node.Member.Name == nameof(DateTime.TimeOfDay))
                    return ConvertTimeOfDay.ReplaceParameter(0, base.Visit(node.Expression));
                // Evaluate the TimeSpan value, convert and wrap it into closure (to keep non const semantics) 
                return ConvertTimespan(base.VisitMember(node).Evaluate<TimeSpan>()).ToClosure().Body;
            }
            return base.VisitMember(node);
        }

        protected override Expression VisitBinary(BinaryExpression node)
        {
            if (node.Left.Type == typeof(TimeSpan))
                return Expression.MakeBinary(node.NodeType, Visit(node.Left), Visit(node.Right));
            return base.VisitBinary(node);
        }
    }

    static T Evaluate<T>(this Expression source) => Expression.Lambda<Func<T>>(source).Compile().Invoke();

    static Expression<Func<T>> ToClosure<T>(this T value) => () => value;

    static Expression ReplaceParameter(this LambdaExpression source, int index, Expression target) =>
        new ParameterReplacer { Source = source.Parameters[index], Target = target }.Visit(source.Body);

    class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node) => node == Source ? Target : node;
    }
}

它使用两个小的自定义 ExpressionVisitor classes 来转换 DateTime.TimeOfDay 属性 和 TimeSpan class 类似于您的 viewModel.ExecutionTime.

现在您应该像这样使用您的原始查询:

var db0010016 = _idb0010016Rep.GetAll()
   .Where(e => e.ExecutionTime.TimeOfDay == viewmodel.ExecutionTime)
   .ConvertTimeStamps() // the magic happens here
   .FirstOrDefault();

如果您想使用毫秒而不是秒,只需将 TimeSpanConverter class 中的前两个语句更改如下:

static readonly Expression<Func<DateTime, int>> ConvertTimeOfDay = dt =>
    1000 * (60 * (60 * dt.Hour + dt.Minute) + dt.Second) + dt.Millisecond;

static int ConvertTimespan(TimeSpan ts) => 
    1000 * (60 * (60 * ts.Hours + ts.Minutes) + ts.Seconds) + ts.Milliseconds;