如果接口的实现由使用代码契约的数据库支持,如何确保它们具有连接字符串
本文关键字:确保 字符串 连接 何确保 实现 接口 代码 如果 支持 数据库 契约 | 更新日期: 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,这只是一个例子…)。我们将此实现称为DefaultPersonManager
0。
由于DbBackedPersonManager
需要一个连接字符串,我们可以在DbBackedPersonManager
的AddPerson
方法实现中添加一个前置条件:
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…)中没有配置连接字符串,则我们的先决条件将确保我们的应用程序、服务或库不满足与存储在数据库后端的人员合作的合同!
旁注
这只是许多其他域如何确保一系列条件的示例,由于代码契约契约类的限制,这些条件无法使用常规多态性和代码契约进行验证