【问题标题】:Ensure Singleton call db query only once确保 Singleton 只调用一次数据库查询
【发布时间】:2013-02-18 21:15:52
【问题描述】:

我正在尝试创建一个负责读取所有用户访问设置的对象。

我已经创建了这样的类:

public class SettingsManager
{
    private static string _connString =
        @"Data Source=MyDB;Initial Catalog=IT;Integrated Security=True;Asynchronous Processing=true;";
    private const string _spName = "SettingTest";
    private IEnumerable<string> _mySettings;
    private static readonly Lazy<SettingsManager> _instance = new Lazy<SettingsManager>(() => new SettingsManager());

    private SettingsManager()
    {
        //Console.WriteLine("Hello from constructor");
        if (_mySettings == null)
            _mySettings = ReadSettings();
    }

    public static SettingsManager Instance
    {
        get { return _instance.Value; }
    }

    public bool CanDo(string setting)
    {
        return _mySettings.Contains(setting);
    }

    private IEnumerable<string> ReadSettings()
    {
        try
        {
            using (var conn = new SqlConnection(_connString))
            {
                using (var cmd = new SqlCommand())
                {
                    cmd.Connection = conn;
                    cmd.CommandText = _spName;
                    cmd.CommandType = CommandType.StoredProcedure;
                    conn.Open();
                    using (var reader = cmd.ExecuteReader())
                    {
                        return reader.Select(SettingParser).ToList();
                    }
                }
            }
        }
        catch
        {
        }
        return null;
    }

    private string SettingParser(SqlDataReader r)
    {
        try
        {
            return r[0] is DBNull ? null : r[0].ToString();
        }
        catch
        {
        }
        return null;
    }
}

和 SqlDataReader 扩展

public static class DBExtensions
{
    public static IEnumerable<T> Select<T>(
        this SqlDataReader reader, Func<SqlDataReader, T> projection)
    {
        while (reader.Read())
        {
            yield return projection(reader);
        }
    }
}

然后在我的应用程序中,我可以这样称呼它:

SettingsManager.Instance.CanDo("canWrite")

这返回真/假值取决于用户访问级别。

我的问题是:

  1. 这个线程安全吗?数据库是否有可能被多次查询?如何预防?

  2. 我应该使用 await 和 async 吗?我只查询 db 一次。我该如何改进呢? (await 和 async 对我来说真的很新,因为我刚从 .NET3.5 迁移到 4.5)

【问题讨论】:

标签: c# singleton


【解决方案1】:

1) 这个线程安全吗?

是的。

是否有可能多次查询数据库?

没有。

2) 我应该使用 await 和 async 吗?

这取决于您是否需要对数据库进行异步访问。如果需要,可以使用异步 ADO.NET API。

【讨论】:

  • 简单、快速且不错的答案 :) 只是想确保我的代码没问题 :)
  • 是的,您的代码是安全的。 Lazy&lt;T&gt; 是一个线程安全的类,因此您的单例模式已正确实现。此外,您通过将所有 IDisposable 资源包装在 using 语句中来正确处理它们,这很棒。
【解决方案2】:

1) 是的,这是线程安全的

线程安全的单例: 此实现使用内部类使 .NET 实例化完全惰性化。只有 GetInstance() 会触发类型初始化器;所以,这个版本和经典版本一样懒惰。而且,它的运行速度与任何其他版本一样快。

public sealed class Singleton
{

   private Singleton() {}

   public static Singleton GetInstance()
   {
      return NestedSingleton.singleton;
   }

   class NestedSingleton
   {
      internal static readonly Singleton singleton = new Singleton();

      static NestedSingleton() {}
   }
}

2)这取决于您是否要异步访问数据库。

【讨论】:

  • 您不能在静态成员中使用this 关键字。
  • 1.使用 this 进行锁定的反模式,2. Lazy 实际上是线程安全的
  • @CuongLe - 是的,Lazy 是线程安全的 - 我只需要确保我只会调用一次我的查询。我的代码应该没问题,但也许有办法改进它:)
【解决方案3】:
  1. 这个线程安全吗?

是的。您正在正确使用 Lazy&lt;T&gt; 类型以确保线程安全。

  1. 我应该使用 await 和 async 吗?

我会推荐它。现在,当您的应用程序代码第一次调用SettingsManager.Instance.CanDo("canWrite") 时,它将阻塞直到数据库响应。也可以有一个async 实现,您的消费者可以在其中执行(await SettingsManager.Instance).CanDo("canWrite")(或await SettingsManager.CanDoAsync("canWrite"))。这意味着您的消费者在等待数据库时不会阻塞。

您可以使用Lazy&lt;T&gt; 的async 就绪版本,称为AsyncLazy&lt;T&gt;、originally developed by Stephen Toub 和included in my AsyncEx library。

【讨论】:

  • 感谢您的回答,我想我尝试使用 async 做 CanDo 的第二个版本 :)
猜你喜欢
  • 2019-08-19
  • 1970-01-01
  • 2011-05-09
  • 2020-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-14
  • 2019-10-02
相关资源
最近更新 更多