始终使用“ now”的值在整个交易中 [英] Consistently using the value of "now" throughout the transaction

查看:56
本文介绍了始终使用“ now”的值在整个交易中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找在整个交易中使用一致的当前日期和时间值的指南。

I'm looking for guidelines to using a consistent value of the current date and time throughout a transaction.

通过交易,我宽松地指一种应用程序服务方法,例如方法通常至少在我的应用程序中执行一个SQL事务。

By transaction I loosely mean an application service method, such methods usually execute a single SQL transaction, at least in my applications.

此问题的答案是将当前日期置于周围环境中,例如 DateTimeProvider ,并在各处使用它代替 DateTime.UtcNow

One approach described in answers to this question is to put the current date in an ambient context, e.g. DateTimeProvider, and use that instead of DateTime.UtcNow everywhere.

但是,这种方法的目的只是使设计可以进行单元测试,而我还想防止由于不必要地多次查询 DateTime.UtcNow 而导致的错误。例如:

However the purpose of this approach is only to make the design unit-testable, whereas I also want to prevent errors caused by unnecessary multiple querying into DateTime.UtcNow, an example of which is this:

// In an entity constructor:
this.CreatedAt = DateTime.UtcNow;
this.ModifiedAt = DateTime.UtcNow;

此代码创建的实体的创建和修改日期略有不同,而人们希望这些属性相等

This code creates an entity with slightly differing created and modified dates, whereas one expects these properties to be equal right after the entity was created.

此外,环境上下文很难在Web应用程序中正确实现,因此我想出了另一种方法:

Also, an ambient context is difficult to implement correctly in a web application, so I've come up with an alternative approach:


  • DeterministicTimeProvider 类注册为每个生命周期范围的实例 AKA Web应用程序中每个HTTP请求的实例依赖性。

  • 将其构造方法注入到应用程序服务中并传递

  • 使用 IDateTimeProvider.UtcNow 方法代替通常的 DateTime。 UtcNow / DateTimeOffset.UtcNow 到处都可以获取当前日期和时间。

  • The DeterministicTimeProvider class is registered as an "instance per lifetime scope" AKA "instance per HTTP request in a web app" dependency.
  • It is constructor-injected to an application service and passed into constructors and methods of entities.
  • The IDateTimeProvider.UtcNow method is used instead of the usual DateTime.UtcNow / DateTimeOffset.UtcNow everywhere to get the current date and time.

这里是实现:

/// <summary>
/// Provides the current date and time.
/// The provided value is fixed when it is requested for the first time.
/// </summary>
public class DeterministicTimeProvider: IDateTimeProvider
{
    private readonly Lazy<DateTimeOffset> _lazyUtcNow =
        new Lazy<DateTimeOffset>(() => DateTimeOffset.UtcNow);

    /// <summary>
    /// Gets the current date and time in the UTC time zone.
    /// </summary>
    public DateTimeOffset UtcNow => _lazyUtcNow.Value;
}

这是一个好方法吗?缺点是什么?有更好的选择吗?

Is this a good approach? What are the disadvantages? Are there better alternatives?

推荐答案

嗯。.这对于 CodeReview.SE StackOverflow 更好,但可以肯定-我会咬。

Hmmm.. this feels like a better question for CodeReview.SE than for StackOverflow, but sure - I'll bite.


这是一个好方法吗?

Is this a good approach?

如果正确使用,在您描述的情况下,此方法是合理的。它实现了两个既定目标:

If used correctly, in the scenario you described, this approach is reasonable. It achieves the two stated goals:


  1. 使代码更具可测试性。这是我称为模拟时钟的常见模式,并且在许多设计良好的应用程序中都可以找到。

  1. Making your code more testable. This is a common pattern I call "Mock the Clock", and is found in many well-designed apps.

将时间锁定为单个值。这不太常见,但是您的代码确实达到了这个目标。

Locking the time to a single value. This is less common, but your code does achieve that goal.




What are the disadvantages?




  1. 由于您为每个请求都创建了另一个新对象,因此它将创建一个少量的额外内存使用和垃圾收集器的额外工作。这有点不切实际,因为这通常适用于具有每个请求生存期的所有对象,包括控制器。

  1. Since you are creating another new object for each request, it will create a mild amount of additional memory usage and additional work for the garbage collector. This is somewhat of a moot point since this is usually how it goes for all objects with per-request lifetime, including the controllers.

从时钟获取读数之前,需要花费大量时间,这是由于加载对象和延迟加载导致的额外工作所致。但是,它可以忽略不计-可能在几毫秒的数量级。

There is a tiny fraction of time being added before you take the reading from the clock, caused by the additional work being done in loading the object and from doing lazy loading. It's negligible though - probably on the order of a few milliseconds.

由于值被锁定,因此始终存在您(或其他使用您的代码)可能会引入一个细微的错误,因为忘记了下一个请求之前该值不会更改。您可能会考虑其他命名约定。例如,将其称为 requestRecievedTime或类似的名称。

Since the value is locked down, there's always the risk that you (or another developer who uses your code) might introduce a subtle bug by forgetting that the value won't change until the next request. You might consider a different naming convention. For example, instead of "now", call it "requestRecievedTime" or something like that.

与上一项类似,也存在提供商面临的风险可能加载了错误的生命周期。您可能会在新项目中使用它,而忘记设置实例化,将其作为单例加载。然后,针对所有请求的值将被锁定。您可以采取很多措施来强制执行此操作,因此请务必对其进行注释。 < summary> 标签是个好地方。

Similar to the previous item, there's also the risk that your provider might be loaded with the wrong lifecycle. You might use it in a new project and forget to set the instancing, loading it up as a singleton. Then the values are locked down for all requests. There's not much you can do to enforce this, so be sure to comment it well. The <summary> tag is a good place.

您可能会发现需要当前时间在无法进行构造函数注入的情况下-例如静态方法。您必须重构以使用实例方法,或者必须将时间或时间提供者作为参数传递给静态方法。

You may find you need the current time in a scenario where constructor injection isn't possible - such as a static method. You'll either have to refactor to use instance methods, or will have to pass either the time or the time-provider as a parameter into the static method.




还有更好的选择吗?

Are there better alternatives?

是,请参见迈克的答案

您还可以考虑 Noda Time ,它通过 IClock 界面和 SystemClock FakeClock 实现。但是,这两种实现都被设计为单例。他们可以帮助测试,但无法实现将时间限制为每个请求一个值的第二个目标。您始终可以编写实现该功能的实现。

You might also consider Noda Time, which has a similar concept built in, via the IClock interface, and the SystemClock and FakeClock implementations. However, both of those implementations are designed to be singletons. They help with testing, but they don't achieve your second goal of locking the time down to a single value per request. You could always write an implementation that does that though.

这篇关于始终使用“ now”的值在整个交易中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆