如果接口的实现由使用代码契约的数据库支持,如何确保它们具有连接字符串

本文关键字:确保 字符串 连接 何确保 实现 接口 代码 如果 支持 数据库 契约 | 更新日期: 2023-09-27 18:20:27

想象一下你有一个这样的界面:

public interface IPersonManager
{
     public void AddPerson(string name);
}

以及我们将称之为CCD_ 1的实现。假设我们希望确保IPersonManager的任何实现都不能给出null或空字符串作为AddPerson(string name)的参数。就这一点而言,我们将实现如下契约类:

[ContractClassFor(typeof(IPersonManager))]
public abstract class IPersonManagerContract : IPersonManager
{
   public void AddPerson(string name)
   {
       Contract.Requires(!string.IsNullOrEmpty(name), "Person's name cannot be a null or empty string");
   }
}

我们将用ContractClassAttribute属性装饰我们的IPersonManager接口:

[ContractClass(typeof(IPersonManagerContractClass))]
public interface IPersonManager
{
     public void AddPerson(string name);
}

我们讨论了DefaultPersonManager。它看起来像这样的类:

public class DefaultPersonManager
{
    private readonly List<string> _personNames = new List<string>();
    public void AddPerson(string name)
    {   
        // "name" argument will be verified by contract class!
        _personNames.Add(name);
    }
}

好吧

现在我们需要实现一个新的IPersonManager实现,它与DefaultPersonManager的不同之处在于AddPerson应该将人名持久化到SQL数据库(即SQLServer,这只是一个例子…)。我们将此实现称为DefaultPersonManager0。

由于DbBackedPersonManager需要一个连接字符串,我们可以在DbBackedPersonManagerAddPerson方法实现中添加一个前置条件:

public void AddPerson(string name)
{
     Contract.Requires(ConfigurationManager.ConnectionStrings["SomeConnectionStringId"] != null, "A connection string is required in your application/web configuration file");
}

错误:代码契约编译器会说AddPerson实现了一个接口成员,因此我们不能添加Requires阅读我发现Jon Skeet回答的问答,它在某种程度上与很久以前的主题有关。)。

如何确保特定的实现强制要求连接字符串才能正常工作

如果接口的实现由使用代码契约的数据库支持,如何确保它们具有连接字符串

将连接字符串要求添加到具体实现的构造函数中,即

public class DbBackedPersonManager : IPersonManager
{
    private readonly string _connectionString;
    public DbBackPersonManager()
    {
        Contract.Requires(ConfigurationManager.ConnectionStrings["SomeConnectionStringId"] != null, "A connection string is required in your application/web configuration file");
        _connectionString = ConfigurationManager.ConnectionStrings["SomeConnectionStringId"];
    }
    [ContractInvariantMethod]
    private void ObjectInvariant()
    {
        Contract.Invariant(_connectionString != null);
    }
    // Interface implementation snipped...
}

然后,如果连接字符串存在,则只能实例化并随后使用DbBackedPersonManager的实例。

就我个人而言,我只需要取一个string connectionString参数,让实例化器提供值(无论如何,他们都必须读取ConfigurationManager.ConnectionStrings)。

一种方法可能是创建一个名为IWithSqlDbBackend(或您偏好的任何标识符…)的不相关接口,如下所示:

public interface IWithSqlDbBackend
{
     string ConnectionString { get; }
     string ConnectionStringId { get; set; }
}

稍后,我们将需要创建这样一个契约类:

[ContractClassFor(typeof(IWithSqlDbBackend))]
public abstract class IWithSqlDbBackendContract : IWithSqlDbBackend
{
    public string ConnectionString
    {
        get 
        {  
            Contract.Requires(!string.IsNullOrEmpty(ConnectionStringId), "Connection string id cannot be null or empty");
            Contract.Requires(ConfigurationManager.ConnectionStrings[ConnectionStringId] != null, "Connection string must be configured");
            Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "A connection string cannot be null");
            return null;
        }
    }
    public string ConnectionStringId
    {
        get
        {
            Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "A connection string identifier cannot be null or empty");
            return null;
        }
    }
}

我们还需要用所谓的ContractClassAttribute:来装饰IWithSqlDbBackend接口

[ContractClass(typeof(IWithSqlDbBackendContract))]
public interface IWithSqlDbBackend
{
    ...
}

并在CCD_ 21中实现了该接口。我将在这里添加实现签名:

public class DbBackedPersonManager : IPersonManager, IWithSqlDbBackend

最后,如果我们创建了DbBackedPersonManager的实例,并尝试调用AddPerson方法实现,但之前在application/web配置文件(即web.config或app.config…)中没有配置连接字符串,则我们的先决条件将确保我们的应用程序、服务或库不满足与存储在数据库后端的人员合作的合同!

旁注

这只是许多其他域如何确保一系列条件的示例,由于代码契约契约类的限制,这些条件无法使用常规多态性和代码契约进行验证