【B】替换 Quartz.net 默认使用的 MySql.Data 为 Mysqlconnector 的学习过程
文章轉載授權級別:B
無論是 Quartz.net 還是 MySql.Data 都是我們比較熟悉的庫了,Quartz.net 如果配置為使用 MySql 數(shù)據(jù)庫做持久化時,默認是硬編碼了使用 MySql.Data 來操作 MySql 數(shù)據(jù)庫的。下面是我的一些個人訴求和實踐,和大家共同探討一下。
0x01 為什么要替換 MySql.Data
MySql.Data 雖然是官方驅動,但實際上實現(xiàn)質量并不是很高。我和一些朋友都經歷過在使用它做長時間的連接查詢時異常崩潰,在?老農?菊巨?的推薦以后,我一直使用 MySqlConnector 這個 MySql 驅動。
因為我的項目都是使用了 MySqlConnector 這個驅動,此驅動為了兼容官方的寫法,類名和命名空間基本保持一致,這就導致如果你同時引用這兩個庫后,再想創(chuàng)建 MySqlConnection 對象時,編譯器無法識別到底是哪一個程序集的類型。
因此如果你項目中已經使用了 MySqlConnector,那么最好還是替換掉 Quarzt.net 默認使用的驅動。
0x02?測試 Quartz.net 使用 MySql.Data
在本地 MySql 數(shù)據(jù)庫中創(chuàng)建測試數(shù)據(jù)庫 quartz, 并通過執(zhí)行腳本?https://raw.githubusercontent.com/dotnetcore/DotnetSpider/master/src/DotnetSpider.Portal/DDL/MySql.sql?來創(chuàng)建所需要的表
首先創(chuàng)建一個空的 Console 項目并添加 Quartz 這個包
修改 Program.cs 代碼如下
直接運行可以發(fā)現(xiàn)程序異常退出了,異常信息如下:
Unhandled Exception: Quartz.SchedulerException: Could not Initialize DataSource: myDs ---> System.ArgumentException: Error while reading metadata information for provider 'MySql' Parameter name: providerName ---> Quartz.SchedulerConfigException: Could not parse property 'connectionType' into correct data type: Could not load file or assembly 'MySql.Data, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.---> System.IO.FileNotFoundException: Could not load file or assembly 'MySql.Data, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.可以得到明確的錯誤提示:未能找到 MySql.Data 程序集
引用 MySql.Data 包并重新運行程序,可以發(fā)現(xiàn)程序正常運行了,結果如下:
0x03 測試添加 MySqlconnector
在上一小節(jié)中,我們成功的使用 MySql 數(shù)據(jù)庫做為 Quartz 的持久化。在上面代碼的基礎之上,我們直接引用 MysqlConnector 包,看一下會有什么影響。運行后發(fā)現(xiàn)程序運行正常,那是不是意味著 MySql.Data 和 MysqlConnector 可以和平共處呢?如果我們想自己使用 MySqlConnection 時會發(fā)生什么呢?編譯器報了如下錯誤:
Program.cs(23, 28): [CS0433] The type 'MySqlConnection' exists in both 'MySql.Data, Version=8.0.17.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' and 'MySqlConnector, Version=0.59.0.0, Culture=neutral, PublicKeyToken=d33d3e53aa5f8c92'這個結果很容易想到,因為兩個程序集里有相同的命名空間,相同的類型,所以無法判斷到底應該使用哪個類型。那為什么并不影響 Quartz.net 的正常運行呢?答案也是比較容易想到的:它用了反射,指定了程序集和類型。我們測試如下代碼:
var ass = AppDomain.CurrentDomain.Load("MySqlConnector"); var type = ass.GetType("MySql.Data.MySqlClient.MySqlConnection");var conn = (IDbConnection) Activator.CreateInstance( type, "Database='dotnetspider';Data Source=my.com;password=1qazZAQ!;User ID=root;Port=3306;");有經驗的同學肯定知道以上代碼是能夠正常運行的。
0x04 翻看 quartz.net 的源碼并嘗試替換
在查看了 Quartz.net 的源碼之后發(fā)現(xiàn),會發(fā)現(xiàn)針對 MySql 的配置有如下:
# MySQL quartz.dbprovider.MySql.productName=MySQL, MySQL provider quartz.dbprovider.MySql.assemblyName=MySql.Data quartz.dbprovider.MySql.connectionType=MySql.Data.MySqlClient.MySqlConnection, MySql.Data quartz.dbprovider.MySql.commandType=MySql.Data.MySqlClient.MySqlCommand, MySql.Data quartz.dbprovider.MySql.parameterType=MySql.Data.MySqlClient.MySqlParameter, MySql.Data quartz.dbprovider.MySql.parameterDbType=MySql.Data.MySqlClient.MySqlDbType, MySql.Data quartz.dbprovider.MySql.parameterDbTypePropertyName=MySqlDbType quartz.dbprovider.MySql.parameterNamePrefix=? quartz.dbprovider.MySql.exceptionType=MySql.Data.MySqlClient.MySqlException, MySql.Data quartz.dbprovider.MySql.useParameterNamePrefixInParameterCollection=true quartz.dbprovider.MySql.bindByName=true quartz.dbprovider.MySql.dbBinaryTypeName=Blob果然,這里 hardcode 的程序為 MySql.Data,那么我們是否可以通過修改配置文件來替換數(shù)據(jù)庫驅動呢?在第一章節(jié)的測試代碼中,我們添加配置后的代碼如下:
var properties = new NameValueCollection {{"quartz.dbprovider.MySql.assemblyName", "MySqlConnector"},{"quartz.jobStore.type", "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"},{"quartz.jobStore.dataSource", "myDs"},{"quartz.serializer.type", "binary"},{"quartz.dataSource.myDs.provider", "MySql"},{"quartz.dataSource.myDs.connectionString","Database='quartz';Data Source=localhost;password=1qazZAQ!;User ID=root;Port=3306;"} };刪除干擾因素:MySql.Data 包后運行程序,發(fā)現(xiàn)程序異常如下:
Unhandled Exception: Quartz.SchedulerException: Could not Initialize DataSource: myDs ---> System.ArgumentException: Error while reading metadata information for provider 'MySql' Parameter name: providerName ---> Quartz.SchedulerConfigException: Could not parse property 'connectionType' into correct data type: Could not load file or assembly 'MySql.Data, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.---> System.IO.FileNotFoundException: Could not load file or assembly 'MySql.Data, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMarkHandle stackMark, IntPtr pPrivHostBinder, Boolean loadTypeFromPartialName, ObjectHandleOnStack type, ObjectHandleOnStack keepalive)at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean loadTypeFromPartialName)at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark, Boolean loadTypeFromPartialName)at System.RuntimeType.GetType(String typeName, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark)at System.Type.GetType(String typeName, Boolean throwOnError)at Quartz.Util.ObjectUtils.ConvertValueIfNecessary(Type requiredType, Object newValue) in C:\projects\quartznet\src\Quartz\Util\ObjectUtils.cs:line 69at Quartz.Util.ObjectUtils.SetPropertyValue(Object target, String propertyName, Object value) in C:\projects\quartznet\src\Quartz\Util\ObjectUtils.cs:line 200at Quartz.Util.ObjectUtils.SetObjectProperties(Object obj, NameValueCollection props) in C:\projects\quartznet\src\Quartz\Util\ObjectUtils.cs:line 151--- End of inner exception stack trace ---at Quartz.Util.ObjectUtils.SetObjectProperties(Object obj, NameValueCollection props) in C:\projects\quartznet\src\Quartz\Util\ObjectUtils.cs:line 154at Quartz.Impl.AdoJobStore.Common.EmbeddedAssemblyResourceDbMetadataFactory.GetDbMetadata(String providerName) in C:\projects\quartznet\src\Quartz\Impl\AdoJobStore\Common\EmbeddedAssemblyResourceDbMetadataFactory.cs:line 64--- End of inner exception stack trace ---at Quartz.Impl.AdoJobStore.Common.EmbeddedAssemblyResourceDbMetadataFactory.GetDbMetadata(String providerName) in C:\projects\quartznet\src\Quartz\Impl\AdoJobStore\Common\EmbeddedAssemblyResourceDbMetadataFactory.cs:line 70at Quartz.Impl.AdoJobStore.Common.DbProvider.GetDbMetadata(String providerName) in C:\projects\quartznet\src\Quartz\Impl\AdoJobStore\Common\DbProvider.cs:line 112at Quartz.Impl.AdoJobStore.Common.DbProvider..ctor(String dbProviderName, String connectionString) in C:\projects\quartznet\src\Quartz\Impl\AdoJobStore\Common\DbProvider.cs:line 74at Quartz.Impl.StdSchedulerFactory.Instantiate() in C:\projects\quartznet\src\Quartz\Impl\StdSchedulerFactory.cs:line 607--- End of inner exception stack trace ---at Quartz.Impl.StdSchedulerFactory.Instantiate() in C:\projects\quartznet\src\Quartz\Impl\StdSchedulerFactory.cs:line 616at Quartz.Impl.StdSchedulerFactory.GetScheduler(CancellationToken cancellationToken) in C:\projects\quartznet\src\Quartz\Impl\StdSchedulerFactory.cs:line 1114at quartz.demo.Program.Main(String[] args) in /Users/lewis/Documents/codes/quartz.demo/quartz.demo/Program.cs:line 44at quartz.demo.Program.Main(String[] args) in /Users/lewis/Documents/codes/quartz.demo/quartz.demo/Program.cs:line 67at quartz.demo.Program.<Main>(String[] args)可以猜測這個配置項目并沒有起作用。通過堆棧信息,我們發(fā)現(xiàn)錯誤是在 DbProvider.cs:line 74,以此為突破口逐行代碼掃過去,原來 DbProvider 靜態(tài)加載了2個 DbMetadataFactory,一個是通過配置文件、另一個是通過內嵌的資源文件,而配置文件它卻是通過 ConfigurationManager 這個類來獲取的,也就是說我們代碼里的配置并沒有真正的生效。于是我們可以在項目中添加 App.config 文件并修改內容為如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration><configSections><section name="quartz" type="System.Configuration.NameValueSectionHandler, System.Configuration.ConfigurationManager, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" /></configSections><quartz><add key="quartz.dbprovider.MySql.assemblyName" value="MySqlConnector" /><add key="quartz.dbprovider.MySql.commandType" value="MySql.Data.MySqlClient.MySqlCommand, MySqlConnector"/><add key="quartz.dbprovider.MySql.parameterType" value="MySql.Data.MySqlClient.MySqlParameter, MySqlConnector"/><add key="quartz.dbprovider.MySql.parameterDbType" value="MySql.Data.MySqlClient.MySqlDbType, MySqlConnector"/><add key="quartz.dbprovider.MySql.parameterDbTypePropertyName" value="MySqlDbType"/><add key="quartz.dbprovider.MySql.connectionType" value="MySql.Data.MySqlClient.MySqlConnection, MySqlConnector"/><add key="quartz.dbprovider.MySql.parameterNamePrefix" value="?"/><add key="quartz.dbprovider.MySql.exceptionType" value="MySql.Data.MySqlClient.MySqlException, MySqlConnector"/></quartz> </configuration>重啟程序可以發(fā)現(xiàn)正常運行了,回過頭來看代碼,我們能夠發(fā)現(xiàn),驅動相關的配置是從內嵌配置和 App.config 中讀取的,而數(shù)據(jù)庫實例的配置則可以通過參數(shù)傳遞到 Factory 中。那么是否可以完全通過配置文件來設置呢?修改配置文件如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration><configSections><section name="quartz" type="System.Configuration.NameValueSectionHandler, System.Configuration.ConfigurationManager, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" /></configSections><quartz><add key="quartz.dbprovider.MySql.assemblyName" value="MySqlConnector" /><add key="quartz.dbprovider.MySql.commandType" value="MySql.Data.MySqlClient.MySqlCommand, MySqlConnector"/><add key="quartz.dbprovider.MySql.parameterType" value="MySql.Data.MySqlClient.MySqlParameter, MySqlConnector"/><add key="quartz.dbprovider.MySql.parameterDbType" value="MySql.Data.MySqlClient.MySqlDbType, MySqlConnector"/><add key="quartz.dbprovider.MySql.parameterDbTypePropertyName" value="MySqlDbType"/><add key="quartz.dbprovider.MySql.connectionType" value="MySql.Data.MySqlClient.MySqlConnection, MySqlConnector"/><add key="quartz.dbprovider.MySql.parameterNamePrefix" value="?"/><add key="quartz.dbprovider.MySql.exceptionType" value="MySql.Data.MySqlClient.MySqlException, MySqlConnector"/><add key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"/><add key="quartz.jobStore.dataSource" value="myDs"/><add key="quartz.serializer.type" value="binary"/><add key="quartz.dataSource.myDs.provider" value="MySql"/><add key="quartz.dataSource.myDs.connectionString" value="Database='quartz';Data Source=localhost;password=1qazZAQ!;User ID=root;Port=3306;"/></quartz> </configuration>同時代碼中修改一行:
scheduler = await new StdSchedulerFactory().GetScheduler();重啟程序后得到結論:程序正常運行。
0x05 初步小結??
至此,基本上已經清楚了,因為我基本上已經不再使用 App.config,當我嘗試使用 quarzt.net 從網(wǎng)上找到的資料大多是配置 *.properties 文件,于是為了找替代,我通過構造傳入了必要參數(shù)進 factory 來構造 scheduler。但這個參數(shù)迷惑了我以為所有的配置都會從此生效,實際上是對數(shù)據(jù)庫驅動相關的(DbMetadata) 是只能通過配置文件來配置。
0x06 其它方式
因為現(xiàn)在我們基本上都是使用 appsettings.json 來做配置了, App.config 實在是使用得少,可能有人就會對此有癖好不想添加一個新的配置文件來解決這個問題。那么接著往下分析代碼,我們會發(fā)現(xiàn),實際上作者提供了一個配置:
quartz.dataSource.myDs.connectionProvider.type即我們是可以自己實際數(shù)據(jù)庫相關的 Provider 的,于是我們回到第一小節(jié)的 NameCollection 中修改為只有一個配置:
{ "quartz.dataSource.myDs.connectionProvider.type", "quartz.demo.MySqlDbProvider, quartz.demo"}同時添加如下 Provider 的實現(xiàn):
public class MySqlDbProvider : IDbProvider {public string Provider { get; set; }public MySqlDbProvider(){ConnectionString = "Database='quartz';Data Source=localhost;password=1qazZAQ!;User ID=root;Port=3306;";var metadata = new MySqlMetadata();Metadata = metadata;}public void Initialize(){}public DbCommand CreateCommand(){return new MySqlCommand();}public DbConnection CreateConnection(){return new MySqlConnection(ConnectionString);}public void Shutdown(){}public string ConnectionString { get; set; }public DbMetadata Metadata { get; } }public class MySqlMetadata : DbMetadata {private readonly Enum _dbBinaryType;public MySqlMetadata(){var dbBinaryTypeName = "Blob";_dbBinaryType = (Enum) Enum.Parse(Type.GetType("MySql.Data.MySqlClient.MySqlDbType, MySqlConnector"),dbBinaryTypeName);DbBinaryTypeName = dbBinaryTypeName;var parameterDbTypePropertyName = "MySqlDbType";ParameterDbTypePropertyName = parameterDbTypePropertyName;ConnectionType = Type.GetType("MySql.Data.MySqlClient.MySqlConnection, MySqlConnector");CommandType = Type.GetType("MySql.Data.MySqlClient.MySqlCommand, MySqlConnector");ParameterDbType = Type.GetType("MySql.Data.MySqlClient.MySqlDbType, MySqlConnector");ParameterType = Type.GetType("MySql.Data.MySqlClient.MySqlParameter, MySqlConnector");ParameterDbTypeProperty = ParameterType.GetProperty("MySqlDbType");if (ParameterDbTypeProperty == null){throw new ArgumentException($"Couldn't parse parameter db type for database type '{ProductName}'");}ExceptionType = Type.GetType("MySql.Data.MySqlClient.MySqlException, MySqlConnector");}public override string AssemblyName => "MySqlConnector";public override string ProductName => "MySQL, MySQL provider";public override string ParameterNamePrefix => "?";public override bool BindByName => true;public override Enum DbBinaryType => _dbBinaryType; }直接運行驗證是不是正常。以上 Provider 實現(xiàn)是 hardcode 各項值,實際可以通過從 appsettings.json 中讀取來設置。這樣就達到:
統(tǒng)一使用一個驅動庫
不添加額外配置文件
0x07 總結
開源的好處就是我們可以直接查閱代碼去學習,并找出其中的關鍵點然后自己可以做各種各樣的 workaround。.NET 在國內的情況是真的岌岌可危了,希望 .neter 能夠多花時間在開源生態(tài)的建設上,少打嘴炮,多研究代碼,多寫代碼。
總結
以上是生活随笔為你收集整理的【B】替换 Quartz.net 默认使用的 MySql.Data 为 Mysqlconnector 的学习过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AWS加入.NET Foundation
- 下一篇: asp.netcore3.0 使用 Db