我在SQL Server 2012中有一个从事务中调用的C#CLR(.NET 4.5)存储过程。 CLR调用需要TLS 1.2的WebService。
如果我使用 permission_set = UNSAFE 创建程序集,一切正常,但我真的想避免这种情况,而是使用 EXTERNAL_ACCESS 。然而,这是一个真正的挑战。
使用 EXTERNAL_ACCESS :
时有两个问题1)我从CLR写入日志文件(用于维护和调试目的),由于不允许锁定,因此无效:
private static readonly object Locker = new object();
public static string LogMessage(string message)
{
message = String.Format("{0}: {1}" + Environment.NewLine, DateTime.Now, message);
lock (Locker)
{
var file = new FileStream(GetConfigValue("LogFile"), FileMode.Append, FileAccess.Write);
var sw = new StreamWriter(file);
sw.Write(message);
sw.Flush();
sw.Close();
}
return message;
}
错误消息:
System.Security.HostProtectionException:尝试执行CLR主机禁止的操作。
受保护的资源(仅在完全信任的情况下可用)是:所有需求的资源是:同步,ExternalThreading
我可以改为登录到数据库表,但由于对CLR的调用来自事务,因此错误会导致回滚,这也会回滚日志记录。我可以使用事件日志,但如果可能的话,我真的不想这样做。
2)我调用的Web服务需要TLS 1.2,但不允许设置ServerCertificateValidationCallback
:
ServicePointManager.ServerCertificateValidationCallback = AcceptAllCertifications;
错误消息:
System.Security.SecurityException:请求类型为'System.Security.Permissions.SecurityPermission,mscorlib,Version = 4.0.0.0,Culture = neutral,PublicKeyToken = b77a5c561934e089'的权限失败。
System.Security.SecurityException:
在System.Security.CodeAccessSecurityEngine.Check(对象需求,StackCrawlMark& stackMark,Boolean isPermSet) 在System.Security.CodeAccessPermission.Demand() 在System.Net.ServicePointManager.set_ServerCertificateValidationCallback(RemoteCertificateValidationCallback value) 在AcmeClr.StoredProcedures.ProcessPayment(SqlMoney金额,SqlString ticketNo,SqlString& resultCode,SqlString& resultText)
在这个问题上是否存在 - 有没有办法在不使用UNSAFE的情况下完成这项工作?
答案 0 :(得分:1)
对于问题#1(使用锁来管理同时写入同一日志文件的多个线程/会话),除了UNSAFE
模式之外,没有办法使用锁。然而,好消息是你可能不需要首先使用锁。所有锁定真正完成的是确保同时发生的两个LogMessage()
呼叫不会发生冲突,其中一个发生错误。这应该可以通过使用允许传入FileStream constructor选项/枚举的FileShare的另一个重载来解决。您应该能够传递FileShare.ReadWrite
以防止错误。您已将DateTime.Now
连接到message
,以便在较晚的消息之后写入更早的消息时帮助解决序列问题。
但在该特定争用之外,即使使用锁定,您仍然会遇到两个会话执行SQLCLR WebService存储过程的问题,几乎同时交错其消息。你如何区分消息?我建议在存储过程的启动时创建一个新的Guid
并将其连接到每个消息中,以便您可以将特定调用的消息与存储过程相关联(我假设您的代码有多个位置,调用LogMessage()
)并将这些消息与其他调用区分开(无论是否并发)。
如果出于任何原因,上面的两个部分(FileShare.ReadWrite
并将Guid连接到message
)不会阻止错误,那么您可以忘记FileShare
选项(虽然我仍然希望将其设置为Read
,以便我可以轻松地检查文件正在编写)而不是将Guid连接到message
,将其附加到{{1}的末尾} value,就在文件扩展名之前。然后,特定呼叫的消息自动相互关联,并与其他并发呼叫区分开。它只是意味着你有一堆日志文件。
另外三个想法:
通过在GetConfigValue("LogFile")
方法中使用lock
,实际上增加了阻塞,因为跨不同会话的此方法的并发调用必须等待锁所有者释放锁。毕竟,这首先是锁定点,对吧?除了处理事务之外,你真的不想延长它们,绝对必要,并且通过SQLCLR进行Web服务调用的本质是你已经使事务依赖于网络延迟和响应性。外部系统(即使是内部系统)。
你提到过:
我可以改为登录到数据库表,但由于对CLR的调用来自事务,因此错误会导致回滚,这也会回滚日志记录。
不一定。如果你使用进程内" Context Connection",那么是的,这是真的。如果您使用常规/外部连接,如果不在连接字符串中指定LogMessage()
关键字,或者您指定Enlist
,则也是如此。但是,如果指定Enlist = true;
,那么它应该是单独/断开连接的事务,如果调用存储过程的事务被回滚,则不会回滚。
Enlist = false;
进行常规/外部连接。对于问题#2(设置Enlist = false;
),遗憾的是,您无法做到这一点。尝试使用ServicePointManager.ServerCertificateValidationCallback
将UseSSL
属性设置为true
以支持FTPS时,我遇到了同样的问题。在执行此操作时,我还没有找到一种方法需要将程序集标记为FtpWebRequest
。
附注:
我同意将大会保持为PERMISSION_SET = UNSAFE
而不是EXTERNAL_ACCESS
的愿望,但鉴于这是您的代码和,您没有使用UNSAFE
1)用于引入任何第三方库或不受支持的.NET Framework库,实际风险级别非常低。
此外,希望您使用非对称密钥或基于证书的登录,以允许程序集具有非UNSAFE
权限,而不将数据库设置为{ {1}}。
除非问题中显示的代码是简化的,否则只发布必要的代码"为了传达即时问题,您缺少围绕外部资源的错误处理。如果进程崩溃,则日志文件将被锁定,直到App Domain被卸载,如果您未能正确处置SAFE
和TRUSTWORTHY ON
。您可以在这5行中添加try / finally结构,也可以让编译器使用StreamWriter
构造/宏来为您执行此操作。