├── LICENSE ├── README.md ├── Release ├── MySql.Data.dll ├── ServerDatabaseVersionUpdateHelper.exe └── 测试样例表格 │ ├── config.txt │ ├── mydb.sql │ ├── mydb_new.sql │ └── 新旧两版本数据库差异说明.txt ├── ServerDatabaseVersionUpdateHelper ├── AppValues.cs ├── ConfigReader.cs ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.resx ├── MySQLOperateHelper.cs ├── MySql.Data.dll ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── ServerDatabaseVersionUpdateHelper.csproj ├── ServerDatabaseVersionUpdateHelper.sln ├── ServerDatabaseVersionUpdateHelper.v12.suo ├── TableCompareRule.cs ├── TableInfo.cs └── Utils.cs ├── 使用说明.txt └── 软件运行截图.png /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ServerDatabaseVersionUpdateHelper 2 | 本工具可以对比新旧两个版本的服务器软件所用数据库,并根据配置(可设置对某表格忽略对比、仅比较表结构、比较结构和数据),展示两库差异并生成将旧版本数据库的表结构和数据升级为新版本的SQL


3 | 运行截图
4 | ![](https://github.com/zhangqi-ulua/ServerDatabaseVersionUpdateHelper/blob/master/%E8%BD%AF%E4%BB%B6%E8%BF%90%E8%A1%8C%E6%88%AA%E5%9B%BE.png)
5 | 6 | ## 赞助 7 | 如果您觉得软件还不错,并且愿意请作者喝杯咖啡的话,欢迎打赏
8 | 9 | 10 | -------------------------------------------------------------------------------- /Release/MySql.Data.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-ulua/ServerDatabaseVersionUpdateHelper/e295eef1874782cdac8ac39261d857409959e28f/Release/MySql.Data.dll -------------------------------------------------------------------------------- /Release/ServerDatabaseVersionUpdateHelper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-ulua/ServerDatabaseVersionUpdateHelper/e295eef1874782cdac8ac39261d857409959e28f/Release/ServerDatabaseVersionUpdateHelper.exe -------------------------------------------------------------------------------- /Release/测试样例表格/config.txt: -------------------------------------------------------------------------------- 1 | # 该配置文件用于设置进行数据库对比时的规则,其中以#开头的行为注释行,会和空白行一样被忽略读取 2 | 3 | # 下面配置数据库表格的对比规则,以@开头,配置以英文冒号分隔的键值对,冒号左边声明数据库表名,右边声明对比方式(0为忽略,不进行比较,哪怕该表被删除或新增;1为仅比较表结构;2为比较结构及数据) 4 | 5 | @server_config:2 6 | @user:1 7 | @user_hero:1 8 | @config_system:2 9 | @config_hero:2 10 | @config_hero_equipment:2 11 | @config_prop:2 12 | 13 | # 下面配置对某张表格进行数据对比时忽略的列名(不允许忽略主键列),以!开头,键名为表名,键值为要忽略的列名,不同列名之间用|分隔 14 | !server_config:lastUpdateUser|lastUpdateTime 15 | 16 | # 下面配置对某张表格进行数据对比时忽略对特定列中特定取值的行进行比较,以$开头,一行配置一条忽略记录,键名为表名,键值为列名后面在英文小括号中声明数据值,使用正则表达式进行模糊匹配,若需要同时满足多个列为特定值,规则之间用&&连接 17 | # 比如下面的配置表示忽略对server_config中key列中值为“channelId”并且上次修改者为“管理员1”的行进行比较。注意若要精确匹配,需使用正则表达式中的^和$表示字符串开头和结尾 18 | $server_config:key(^channelId$)&&lastUpdateUser(^管理员1$) 19 | $server_config:key(^openTime$) -------------------------------------------------------------------------------- /Release/测试样例表格/mydb.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS `mydb` /*!40100 DEFAULT CHARACTER SET eucjpms COLLATE eucjpms_bin */; 2 | USE `mydb`; 3 | -- MySQL dump 10.13 Distrib 5.7.9, for Win64 (x86_64) 4 | -- 5 | -- Host: 127.0.0.1 Database: mydb 6 | -- ------------------------------------------------------ 7 | -- Server version 5.7.13-log 8 | 9 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 10 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 11 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 12 | /*!40101 SET NAMES utf8 */; 13 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 14 | /*!40103 SET TIME_ZONE='+00:00' */; 15 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 16 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 17 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 18 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 19 | 20 | -- 21 | -- Table structure for table `config_hero` 22 | -- 23 | 24 | DROP TABLE IF EXISTS `config_hero`; 25 | /*!40101 SET @saved_cs_client = @@character_set_client */; 26 | /*!40101 SET character_set_client = utf8 */; 27 | CREATE TABLE `config_hero` ( 28 | `heroId` int(11) NOT NULL COMMENT '英雄ID', 29 | `name` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '英雄名称', 30 | `rare` int(11) DEFAULT NULL COMMENT '稀有度(11-13)', 31 | `type` int(11) DEFAULT NULL COMMENT '英雄职业(1:法师,2:战士,3:牧师,4:勇士)', 32 | `defaultStar` int(11) DEFAULT '0' COMMENT '英雄初始星数', 33 | PRIMARY KEY (`heroId`) 34 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='游戏配置表-英雄'; 35 | /*!40101 SET character_set_client = @saved_cs_client */; 36 | 37 | -- 38 | -- Dumping data for table `config_hero` 39 | -- 40 | 41 | LOCK TABLES `config_hero` WRITE; 42 | /*!40000 ALTER TABLE `config_hero` DISABLE KEYS */; 43 | INSERT INTO `config_hero` VALUES (1,'英雄法师',11,1,1),(2,'英雄战士',11,2,1),(3,'英雄牧师',11,3,1),(4,'英雄勇士',11,4,1); 44 | /*!40000 ALTER TABLE `config_hero` ENABLE KEYS */; 45 | UNLOCK TABLES; 46 | 47 | -- 48 | -- Table structure for table `config_hero_equipment` 49 | -- 50 | 51 | DROP TABLE IF EXISTS `config_hero_equipment`; 52 | /*!40101 SET @saved_cs_client = @@character_set_client */; 53 | /*!40101 SET character_set_client = utf8 */; 54 | CREATE TABLE `config_hero_equipment` ( 55 | `id` int(11) NOT NULL COMMENT 'ID', 56 | `heroId` int(11) DEFAULT NULL COMMENT '英雄ID', 57 | `heroQuality` int(11) DEFAULT NULL COMMENT '英雄品阶', 58 | `seq` int(11) DEFAULT NULL COMMENT '装备槽位序号', 59 | `propId` int(11) DEFAULT NULL COMMENT '装备ID', 60 | `equipRank` int(11) DEFAULT NULL COMMENT '穿戴等级限制', 61 | PRIMARY KEY (`id`) 62 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='游戏配置表-英雄可穿戴装备'; 63 | /*!40101 SET character_set_client = @saved_cs_client */; 64 | 65 | -- 66 | -- Dumping data for table `config_hero_equipment` 67 | -- 68 | 69 | LOCK TABLES `config_hero_equipment` WRITE; 70 | /*!40000 ALTER TABLE `config_hero_equipment` DISABLE KEYS */; 71 | INSERT INTO `config_hero_equipment` VALUES (1,1,10,1,200001,1),(2,1,10,2,200002,1),(3,1,10,3,200003,1),(4,1,10,4,200004,1),(5,1,20,1,200005,5),(6,1,20,2,200006,5),(7,1,20,3,200007,5),(8,1,20,4,200008,5),(9,1,21,1,200001,10),(10,1,21,2,200002,10),(11,1,21,3,200003,10),(12,1,21,4,200004,10),(13,1,30,1,200005,20),(14,1,30,2,200006,20),(15,1,30,3,200007,20),(16,1,30,4,200008,20),(17,1,31,1,200001,25),(18,1,31,2,200002,25),(19,1,31,3,200003,25),(20,1,31,4,200004,25),(21,1,32,1,200005,30),(22,1,32,2,200006,30),(23,1,32,3,200007,30),(24,1,32,4,200008,30),(25,1,40,1,200001,40),(26,1,40,2,200002,40),(27,1,40,3,200003,40),(28,1,40,4,200004,40),(29,1,41,1,200005,45),(30,1,41,2,200006,45),(31,1,41,3,200007,45),(32,1,41,4,200008,45),(33,1,42,1,200001,50),(34,1,42,2,200002,50),(35,1,42,3,200003,50),(36,1,42,4,200004,50),(37,1,43,1,200005,55),(38,1,43,2,200006,55),(39,1,43,3,200007,55),(40,1,43,4,200008,55); 72 | /*!40000 ALTER TABLE `config_hero_equipment` ENABLE KEYS */; 73 | UNLOCK TABLES; 74 | 75 | -- 76 | -- Table structure for table `config_prop` 77 | -- 78 | 79 | DROP TABLE IF EXISTS `config_prop`; 80 | /*!40101 SET @saved_cs_client = @@character_set_client */; 81 | /*!40101 SET character_set_client = utf8 */; 82 | CREATE TABLE `config_prop` ( 83 | `id` int(11) NOT NULL COMMENT '道具ID', 84 | `type` int(11) DEFAULT NULL COMMENT '道具类型 (1:经验道具,2:装备,3:装备碎片,4:英雄灵魂石)', 85 | `subType` int(11) DEFAULT NULL COMMENT '子类型', 86 | `name` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '名称', 87 | `desc` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '描述', 88 | `quality` int(11) DEFAULT NULL COMMENT '品质', 89 | `icon` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '图标', 90 | `sellPrice` int(11) DEFAULT NULL COMMENT '铜币卖出价格(填-1表示不允许卖)', 91 | PRIMARY KEY (`id`) 92 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='游戏配置表-道具'; 93 | /*!40101 SET character_set_client = @saved_cs_client */; 94 | 95 | -- 96 | -- Dumping data for table `config_prop` 97 | -- 98 | 99 | LOCK TABLES `config_prop` WRITE; 100 | /*!40000 ALTER TABLE `config_prop` DISABLE KEYS */; 101 | INSERT INTO `config_prop` VALUES (100001,1,-1,'小号经验药水','使用增加英雄经验100点',1,'item1',500),(100002,1,-1,'中号经验药水','使用增加英雄经验200点',2,'item1',800),(200001,2,-1,'盾牌','盾牌',1,'item1',800),(200002,2,-1,'弓箭','弓箭',1,'item1',800),(200003,2,-1,'长矛','长矛',1,'item1',800),(200004,2,-1,'护甲','护甲',1,'item1',800),(200005,2,-1,'头盔','头盔',1,'item1',800),(200006,2,-1,'匕首','匕首',1,'item1',800),(200007,2,-1,'禅杖','禅杖',1,'item1',800),(200008,2,-1,'大刀','大刀',1,'item1',800); 102 | /*!40000 ALTER TABLE `config_prop` ENABLE KEYS */; 103 | UNLOCK TABLES; 104 | 105 | -- 106 | -- Table structure for table `config_system` 107 | -- 108 | 109 | DROP TABLE IF EXISTS `config_system`; 110 | /*!40101 SET @saved_cs_client = @@character_set_client */; 111 | /*!40101 SET character_set_client = utf8 */; 112 | CREATE TABLE `config_system` ( 113 | `systemId` int(11) NOT NULL COMMENT '系统ID', 114 | `systemName` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '系统名称', 115 | `help` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '系统帮助信息', 116 | `openConditionRankLimit` int(11) DEFAULT NULL COMMENT '所需玩家等级(不限填-1)', 117 | `openConditionVipLimit` int(11) DEFAULT NULL COMMENT '所需Vip等级(不限填-1)', 118 | `openConditionLevelLimit` int(11) DEFAULT NULL COMMENT '所需通关关卡(不限填-1)', 119 | `rewardType1` int(11) DEFAULT NULL COMMENT '类型', 120 | `rewardId1` int(11) DEFAULT NULL COMMENT 'id', 121 | `rewardCount1` int(11) DEFAULT NULL COMMENT '数量', 122 | `rewardType12` int(11) DEFAULT NULL, 123 | `rewardId2` int(11) DEFAULT NULL, 124 | `rewardCount2` int(11) DEFAULT NULL, 125 | PRIMARY KEY (`systemId`) 126 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='游戏配置表-游戏模块'; 127 | /*!40101 SET character_set_client = @saved_cs_client */; 128 | 129 | -- 130 | -- Dumping data for table `config_system` 131 | -- 132 | 133 | LOCK TABLES `config_system` WRITE; 134 | /*!40000 ALTER TABLE `config_system` DISABLE KEYS */; 135 | INSERT INTO `config_system` VALUES (1,'普通关卡','通过普通关卡可以获得一定几率掉落的道具和英雄经验,快来让你的英雄挑战吧!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),(2,'精英关卡','通过精英关卡可以获得一定几率掉落的稀有英雄碎片用于英雄升阶,每天挑战次数有限哦!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),(3,'竞技场','这里是各位英雄彼此切磋较量的场所,有丰富的奖励给予强大的英雄',25,-1,-1,1,100001,10,NULL,NULL,NULL),(4,'商店','在这里你可以花费金币或者钻石来兑换你想要的道具,商店会在每天9点、15点、21点进货新的一批商品哟,欢迎客官前来购买',15,-1,-1,1,100001,5,NULL,NULL,NULL); 136 | /*!40000 ALTER TABLE `config_system` ENABLE KEYS */; 137 | UNLOCK TABLES; 138 | 139 | -- 140 | -- Table structure for table `server_config` 141 | -- 142 | 143 | DROP TABLE IF EXISTS `server_config`; 144 | /*!40101 SET @saved_cs_client = @@character_set_client */; 145 | /*!40101 SET character_set_client = utf8 */; 146 | CREATE TABLE `server_config` ( 147 | `id` int(11) NOT NULL AUTO_INCREMENT, 148 | `key` varchar(45) NOT NULL COMMENT '键名', 149 | `value` varchar(45) NOT NULL COMMENT '键值', 150 | `comment` varchar(45) DEFAULT NULL COMMENT '说明', 151 | `lastUpdateUser` varchar(45) DEFAULT NULL COMMENT '上次修改者', 152 | `lastUpdateTime` varchar(45) DEFAULT NULL COMMENT '上次修改时间', 153 | PRIMARY KEY (`id`), 154 | UNIQUE KEY `key_UNIQUE` (`key`) 155 | ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COMMENT='服务器属性'; 156 | /*!40101 SET character_set_client = @saved_cs_client */; 157 | 158 | -- 159 | -- Dumping data for table `server_config` 160 | -- 161 | 162 | LOCK TABLES `server_config` WRITE; 163 | /*!40000 ALTER TABLE `server_config` DISABLE KEYS */; 164 | INSERT INTO `server_config` VALUES (1,'channelId','1','所属渠道(1:苹果官方,2:谷歌官方)','管理员1','2016-10-10 8:00:00'),(2,'serverId','1','服务器编号','管理员1','2016-10-10 8:00:00'),(3,'openTime','2016-11-10 8:00:00','开服时间','管理员1','2016-10-10 8:00:00'),(4,'userConfigNickNameMaxLength','10','玩家昵称最大字数','管理员1','2016-10-10 8:00:00'),(5,'userConfigMaxRank','80','该版本中玩家等级上限','管理员1','2016-10-10 8:00:00'); 165 | /*!40000 ALTER TABLE `server_config` ENABLE KEYS */; 166 | UNLOCK TABLES; 167 | 168 | -- 169 | -- Table structure for table `test` 170 | -- 171 | 172 | DROP TABLE IF EXISTS `test`; 173 | /*!40101 SET @saved_cs_client = @@character_set_client */; 174 | /*!40101 SET character_set_client = utf8 */; 175 | CREATE TABLE `test` ( 176 | `id` int(11) NOT NULL, 177 | PRIMARY KEY (`id`) 178 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='临时测试'; 179 | /*!40101 SET character_set_client = @saved_cs_client */; 180 | 181 | -- 182 | -- Dumping data for table `test` 183 | -- 184 | 185 | LOCK TABLES `test` WRITE; 186 | /*!40000 ALTER TABLE `test` DISABLE KEYS */; 187 | /*!40000 ALTER TABLE `test` ENABLE KEYS */; 188 | UNLOCK TABLES; 189 | 190 | -- 191 | -- Table structure for table `user` 192 | -- 193 | 194 | DROP TABLE IF EXISTS `user`; 195 | /*!40101 SET @saved_cs_client = @@character_set_client */; 196 | /*!40101 SET character_set_client = utf8 */; 197 | CREATE TABLE `user` ( 198 | `id` int(11) NOT NULL AUTO_INCREMENT, 199 | `nickName` varchar(45) NOT NULL COMMENT '昵称', 200 | `gold` int(11) DEFAULT '0' COMMENT '金钱数', 201 | `diamond` int(11) DEFAULT '0' COMMENT '钻石数', 202 | PRIMARY KEY (`id`) 203 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='玩家信息表'; 204 | /*!40101 SET character_set_client = @saved_cs_client */; 205 | 206 | -- 207 | -- Dumping data for table `user` 208 | -- 209 | 210 | LOCK TABLES `user` WRITE; 211 | /*!40000 ALTER TABLE `user` DISABLE KEYS */; 212 | INSERT INTO `user` VALUES (1,'张三',100,100),(2,'李四',200,200),(3,'王五',300,300); 213 | /*!40000 ALTER TABLE `user` ENABLE KEYS */; 214 | UNLOCK TABLES; 215 | 216 | -- 217 | -- Table structure for table `user_hero` 218 | -- 219 | 220 | DROP TABLE IF EXISTS `user_hero`; 221 | /*!40101 SET @saved_cs_client = @@character_set_client */; 222 | /*!40101 SET character_set_client = utf8 */; 223 | CREATE TABLE `user_hero` ( 224 | `id` int(11) NOT NULL AUTO_INCREMENT, 225 | `userId` int(11) NOT NULL COMMENT '玩家id', 226 | `heroId` int(11) NOT NULL COMMENT '英雄id', 227 | `rank` int(11) NOT NULL DEFAULT '0' COMMENT '英雄等级', 228 | `hasEquipment1` tinyint(1) DEFAULT '0' COMMENT '是否穿戴了第一个槽位的装备', 229 | `hasEquipment2` tinyint(1) DEFAULT '0' COMMENT '是否穿戴了第二个槽位的装备', 230 | `hasEquipment3` tinyint(1) DEFAULT '0' COMMENT '是否穿戴了第三个槽位的装备', 231 | `hasEquipment4` tinyint(1) DEFAULT '0' COMMENT '是否穿戴了第四个槽位的装备', 232 | PRIMARY KEY (`id`) 233 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='玩家拥有英雄信息'; 234 | /*!40101 SET character_set_client = @saved_cs_client */; 235 | 236 | -- 237 | -- Dumping data for table `user_hero` 238 | -- 239 | 240 | LOCK TABLES `user_hero` WRITE; 241 | /*!40000 ALTER TABLE `user_hero` DISABLE KEYS */; 242 | INSERT INTO `user_hero` VALUES (1,1,1,30,1,0,0,0),(2,2,1,40,0,1,1,1); 243 | /*!40000 ALTER TABLE `user_hero` ENABLE KEYS */; 244 | UNLOCK TABLES; 245 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 246 | 247 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 248 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 249 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 250 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 251 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 252 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 253 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 254 | 255 | -- Dump completed on 2016-12-06 15:59:05 256 | -------------------------------------------------------------------------------- /Release/测试样例表格/mydb_new.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS `mydb_new` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_bin */; 2 | USE `mydb_new`; 3 | -- MySQL dump 10.13 Distrib 5.7.9, for Win64 (x86_64) 4 | -- 5 | -- Host: 127.0.0.1 Database: mydb_new 6 | -- ------------------------------------------------------ 7 | -- Server version 5.7.13-log 8 | 9 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 10 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 11 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 12 | /*!40101 SET NAMES utf8 */; 13 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 14 | /*!40103 SET TIME_ZONE='+00:00' */; 15 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 16 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 17 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 18 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 19 | 20 | -- 21 | -- Table structure for table `config_hero` 22 | -- 23 | 24 | DROP TABLE IF EXISTS `config_hero`; 25 | /*!40101 SET @saved_cs_client = @@character_set_client */; 26 | /*!40101 SET character_set_client = utf8 */; 27 | CREATE TABLE `config_hero` ( 28 | `heroId` int(11) NOT NULL COMMENT '英雄ID', 29 | `name` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '英雄名称', 30 | `rare` int(11) NOT NULL DEFAULT '11' COMMENT '稀有度(11-13),默认为11', 31 | `type` int(11) DEFAULT NULL COMMENT '英雄职业(1:法师,2:战士,3:牧师,4:勇士)', 32 | `defaultStar` int(11) DEFAULT '0' COMMENT '英雄初始星数', 33 | `isOpen` tinyint(1) NOT NULL DEFAULT '1' COMMENT '当前是否在游戏中开放(即可在英雄图鉴看到,可以被抽卡抽到)', 34 | PRIMARY KEY (`heroId`) 35 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='游戏配置表-英雄'; 36 | /*!40101 SET character_set_client = @saved_cs_client */; 37 | 38 | -- 39 | -- Dumping data for table `config_hero` 40 | -- 41 | 42 | LOCK TABLES `config_hero` WRITE; 43 | /*!40000 ALTER TABLE `config_hero` DISABLE KEYS */; 44 | INSERT INTO `config_hero` VALUES (1,'英雄法师',11,1,1,1),(2,'英雄战士',11,2,1,1),(3,'英雄牧师',12,3,1,1),(4,'英雄勇士',11,4,1,1); 45 | /*!40000 ALTER TABLE `config_hero` ENABLE KEYS */; 46 | UNLOCK TABLES; 47 | 48 | -- 49 | -- Table structure for table `config_hero_equipment` 50 | -- 51 | 52 | DROP TABLE IF EXISTS `config_hero_equipment`; 53 | /*!40101 SET @saved_cs_client = @@character_set_client */; 54 | /*!40101 SET character_set_client = utf8 */; 55 | CREATE TABLE `config_hero_equipment` ( 56 | `id` int(11) NOT NULL COMMENT 'ID', 57 | `heroId` int(11) NOT NULL COMMENT '英雄ID', 58 | `heroQuality` int(11) NOT NULL COMMENT '英雄品阶', 59 | `seq` int(11) NOT NULL COMMENT '装备槽位序号', 60 | `propId` int(11) NOT NULL COMMENT '装备ID', 61 | `equipRank` int(11) NOT NULL COMMENT '穿戴等级限制', 62 | PRIMARY KEY (`id`), 63 | UNIQUE KEY `LOGIC_INDEX` (`heroId`,`heroQuality`,`seq`) 64 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='游戏配置表-英雄可穿戴装备'; 65 | /*!40101 SET character_set_client = @saved_cs_client */; 66 | 67 | -- 68 | -- Dumping data for table `config_hero_equipment` 69 | -- 70 | 71 | LOCK TABLES `config_hero_equipment` WRITE; 72 | /*!40000 ALTER TABLE `config_hero_equipment` DISABLE KEYS */; 73 | INSERT INTO `config_hero_equipment` VALUES (1,1,10,1,200001,1),(2,1,10,2,200002,1),(3,1,10,3,200003,1),(4,1,10,4,200004,1),(5,1,20,1,200005,5),(6,1,20,2,200006,5),(7,1,20,3,200007,5),(8,1,20,4,200008,5),(9,1,21,1,200001,10),(10,1,21,2,200002,10),(11,1,21,3,200003,10),(12,1,21,4,200004,10),(13,1,30,1,200005,20),(14,1,30,2,200006,20),(15,1,30,3,200007,20),(16,1,30,4,200008,20),(17,1,31,1,200001,25),(18,1,31,2,200002,25),(19,1,31,3,200003,25),(20,1,31,4,200004,25),(21,1,32,1,200005,30),(22,1,32,2,200006,30),(23,1,32,3,200007,30),(24,1,32,4,200008,30),(25,1,40,1,200001,40),(26,1,40,2,200002,40),(27,1,40,3,200003,40),(28,1,40,4,200004,40),(29,1,41,1,200005,45),(30,1,41,2,200006,45),(31,1,41,3,200007,45),(32,1,41,4,200008,45),(33,1,42,1,200001,50),(34,1,42,2,200002,50),(35,1,42,3,200003,50),(36,1,42,4,200004,50),(37,1,43,1,200005,55),(38,1,43,2,200006,55),(39,1,43,3,200007,55),(40,1,43,4,200008,60); 74 | /*!40000 ALTER TABLE `config_hero_equipment` ENABLE KEYS */; 75 | UNLOCK TABLES; 76 | 77 | -- 78 | -- Table structure for table `config_prop` 79 | -- 80 | 81 | DROP TABLE IF EXISTS `config_prop`; 82 | /*!40101 SET @saved_cs_client = @@character_set_client */; 83 | /*!40101 SET character_set_client = utf8 */; 84 | CREATE TABLE `config_prop` ( 85 | `id` int(11) NOT NULL COMMENT '道具ID', 86 | `type` int(11) DEFAULT NULL COMMENT '道具类型 (1:经验道具,2:装备,3:装备碎片,4:英雄灵魂石)', 87 | `subType` int(11) DEFAULT NULL COMMENT '子类型', 88 | `name` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '名称', 89 | `desc` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '描述', 90 | `quality` int(11) DEFAULT NULL COMMENT '品质', 91 | `icon` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '图标', 92 | `sellPrice` int(11) DEFAULT NULL COMMENT '铜币卖出价格(填-1表示不允许卖)', 93 | PRIMARY KEY (`id`) 94 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='游戏配置表-道具'; 95 | /*!40101 SET character_set_client = @saved_cs_client */; 96 | 97 | -- 98 | -- Dumping data for table `config_prop` 99 | -- 100 | 101 | LOCK TABLES `config_prop` WRITE; 102 | /*!40000 ALTER TABLE `config_prop` DISABLE KEYS */; 103 | INSERT INTO `config_prop` VALUES (100001,1,-1,'小号经验药水','使用增加英雄经验100点',1,'item1',500),(100002,1,-1,'中号经验药水','使用增加英雄经验200点',2,'item1',800),(200001,2,-1,'盾牌','盾牌',1,'item1',800),(200002,2,-1,'弓箭','弓箭',1,'item1',800),(200003,2,-1,'长矛','长矛',1,'item1',800),(200004,2,-1,'护甲','护甲',1,'item1',800),(200005,2,-1,'头盔','头盔',1,'item1',800),(200006,2,-1,'匕首','匕首',1,'item1',800),(200007,2,-1,'禅杖','禅杖',1,'item1',800),(200008,2,-1,'大刀','大刀',1,'item1',800),(200009,1,-1,'钱袋','增加100金币',1,'item1',100); 104 | /*!40000 ALTER TABLE `config_prop` ENABLE KEYS */; 105 | UNLOCK TABLES; 106 | 107 | -- 108 | -- Table structure for table `config_system` 109 | -- 110 | 111 | DROP TABLE IF EXISTS `config_system`; 112 | /*!40101 SET @saved_cs_client = @@character_set_client */; 113 | /*!40101 SET character_set_client = utf8 */; 114 | CREATE TABLE `config_system` ( 115 | `systemId` int(11) NOT NULL COMMENT '系统ID', 116 | `systemName` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '系统名称', 117 | `help` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '系统帮助信息', 118 | `openConditionRankLimit` int(11) DEFAULT NULL COMMENT '所需玩家等级(不限填-1)', 119 | `openConditionVipLimit` int(11) DEFAULT NULL COMMENT '所需Vip等级(不限填-1)', 120 | `openConditionLevelLimit` int(11) DEFAULT NULL COMMENT '所需通关关卡(不限填-1)', 121 | `rewardType` int(11) DEFAULT NULL COMMENT '类型', 122 | `rewardId` int(11) DEFAULT NULL COMMENT 'id', 123 | `rewardCount` int(11) DEFAULT NULL COMMENT '数量', 124 | PRIMARY KEY (`systemId`) 125 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='游戏配置表-游戏系统模块'; 126 | /*!40101 SET character_set_client = @saved_cs_client */; 127 | 128 | -- 129 | -- Dumping data for table `config_system` 130 | -- 131 | 132 | LOCK TABLES `config_system` WRITE; 133 | /*!40000 ALTER TABLE `config_system` DISABLE KEYS */; 134 | INSERT INTO `config_system` VALUES (1,'普通关卡','通过普通关卡可以获得一定几率掉落的道具和英雄经验,快来让你的英雄挑战吧!',NULL,NULL,NULL,NULL,NULL,NULL),(2,'精英关卡','通过精英关卡可以获得一定几率掉落的稀有英雄碎片用于英雄升阶,每天挑战次数有限哦!',NULL,NULL,NULL,NULL,NULL,NULL),(3,'竞技场','这里是各位英雄彼此切磋较量的场所,有丰富的奖励给予强大的英雄',25,-1,-1,1,100001,10),(4,'商店','在这里你可以花费金币或者钻石来兑换你想要的道具,商店会在每天9点、15点、21点进货新的一批商品哟,欢迎客官前来购买',15,-1,-1,1,100001,5); 135 | /*!40000 ALTER TABLE `config_system` ENABLE KEYS */; 136 | UNLOCK TABLES; 137 | 138 | -- 139 | -- Table structure for table `server_config` 140 | -- 141 | 142 | DROP TABLE IF EXISTS `server_config`; 143 | /*!40101 SET @saved_cs_client = @@character_set_client */; 144 | /*!40101 SET character_set_client = utf8 */; 145 | CREATE TABLE `server_config` ( 146 | `id` int(11) NOT NULL AUTO_INCREMENT, 147 | `key` varchar(45) NOT NULL COMMENT '键名', 148 | `value` varchar(45) NOT NULL COMMENT '键值', 149 | `comment` varchar(45) DEFAULT NULL COMMENT '说明', 150 | `lastUpdateUser` varchar(45) DEFAULT NULL COMMENT '上次修改者', 151 | `lastUpdateTime` varchar(45) DEFAULT NULL COMMENT '上次修改时间', 152 | PRIMARY KEY (`id`), 153 | UNIQUE KEY `key_UNIQUE` (`key`) 154 | ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COMMENT='服务器属性'; 155 | /*!40101 SET character_set_client = @saved_cs_client */; 156 | 157 | -- 158 | -- Dumping data for table `server_config` 159 | -- 160 | 161 | LOCK TABLES `server_config` WRITE; 162 | /*!40000 ALTER TABLE `server_config` DISABLE KEYS */; 163 | INSERT INTO `server_config` VALUES (1,'channelId','2','所属渠道(1:苹果官方,2:谷歌官方)','管理员1','2016-12-12 8:00:00'),(2,'serverId','2','服务器编号','管理员1','2016-12-12 8:00:00'),(3,'openTime','2016-11-10 8:00:00','开服时间','管理员1','2016-10-10 8:00:00'),(4,'userConfigNickNameMaxLength','10','玩家昵称最大字数','管理员1','2016-10-10 8:00:00'),(5,'userConfigMaxRank','90','该版本中玩家等级上限','管理员1','2016-12-12 8:00:00'); 164 | /*!40000 ALTER TABLE `server_config` ENABLE KEYS */; 165 | UNLOCK TABLES; 166 | 167 | -- 168 | -- Table structure for table `user` 169 | -- 170 | 171 | DROP TABLE IF EXISTS `user`; 172 | /*!40101 SET @saved_cs_client = @@character_set_client */; 173 | /*!40101 SET character_set_client = utf8 */; 174 | CREATE TABLE `user` ( 175 | `id` int(11) NOT NULL AUTO_INCREMENT, 176 | `nickName` varchar(45) NOT NULL COMMENT '昵称', 177 | `gold` int(11) DEFAULT '0' COMMENT '金钱数', 178 | `diamond` int(11) DEFAULT '0' COMMENT '钻石数', 179 | PRIMARY KEY (`id`) 180 | ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='玩家信息表'; 181 | /*!40101 SET character_set_client = @saved_cs_client */; 182 | 183 | -- 184 | -- Dumping data for table `user` 185 | -- 186 | 187 | LOCK TABLES `user` WRITE; 188 | /*!40000 ALTER TABLE `user` DISABLE KEYS */; 189 | INSERT INTO `user` VALUES (1,'张三',100,100),(2,'李四',200,200),(4,'赵六',0,0); 190 | /*!40000 ALTER TABLE `user` ENABLE KEYS */; 191 | UNLOCK TABLES; 192 | 193 | -- 194 | -- Table structure for table `user_hero` 195 | -- 196 | 197 | DROP TABLE IF EXISTS `user_hero`; 198 | /*!40101 SET @saved_cs_client = @@character_set_client */; 199 | /*!40101 SET character_set_client = utf8 */; 200 | CREATE TABLE `user_hero` ( 201 | `id` int(11) NOT NULL AUTO_INCREMENT, 202 | `userId` int(11) NOT NULL COMMENT '玩家id', 203 | `heroId` int(11) NOT NULL COMMENT '英雄id', 204 | `rank` int(11) NOT NULL DEFAULT '0' COMMENT '英雄等级', 205 | `hasEquipment1` tinyint(1) DEFAULT '0' COMMENT '是否穿戴了第一个槽位的装备', 206 | `hasEquipment2` tinyint(1) DEFAULT '0' COMMENT '是否穿戴了第二个槽位的装备', 207 | `hasEquipment3` tinyint(1) DEFAULT '0' COMMENT '是否穿戴了第三个槽位的装备', 208 | `hasEquipment4` tinyint(1) DEFAULT '0' COMMENT '是否穿戴了第四个槽位的装备', 209 | PRIMARY KEY (`id`) 210 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='玩家拥有英雄信息'; 211 | /*!40101 SET character_set_client = @saved_cs_client */; 212 | 213 | -- 214 | -- Dumping data for table `user_hero` 215 | -- 216 | 217 | LOCK TABLES `user_hero` WRITE; 218 | /*!40000 ALTER TABLE `user_hero` DISABLE KEYS */; 219 | INSERT INTO `user_hero` VALUES (1,1,1,30,1,0,0,0),(2,2,1,40,0,1,1,1); 220 | /*!40000 ALTER TABLE `user_hero` ENABLE KEYS */; 221 | UNLOCK TABLES; 222 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 223 | 224 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 225 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 226 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 227 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 228 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 229 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 230 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 231 | 232 | -- Dump completed on 2016-12-06 15:58:19 233 | -------------------------------------------------------------------------------- /Release/测试样例表格/新旧两版本数据库差异说明.txt: -------------------------------------------------------------------------------- 1 | 新版本中进行了以下修改: 2 | 3 | ———————————————————————————————— 4 | test表: 5 | 6 | 1、删除了此表 7 | ———————————————————————————————— 8 | config_hero表: 9 | 10 | 1、新增isOpen列,默认值设为1 11 | 2、将rare列设为NOT NULL,默认值改为11,并修改注释 12 | 3、将heroId为3的英雄rare改为12 13 | ———————————————————————————————— 14 | config_hero_equipment表: 15 | 16 | 1、heroId、heroQuality、seq、propId、equipRank列设为NOT NULL 17 | 2、新增UNIQUE索引,涉及heroId、heroQuality、seq列 18 | 3、修改heroId=1,heroQuality=43,seq=4行中,equipRank为60 19 | ———————————————————————————————— 20 | config_prop表: 21 | 22 | 1、新增1条数据 23 | ———————————————————————————————— 24 | config_system表: 25 | 26 | 1、删除rewardType2、rewardId2、rewardCount2列 27 | 2、将rewardType1、rewardId1、rewardCount1列名去掉后面的1 28 | 3、修改表注释 29 | ———————————————————————————————— 30 | server_config表: 31 | 32 | 1、userConfigMaxRank属性改为90 33 | 2、channelIdId改为2(配置文件中设为忽略) 34 | 3、serverId改为2 35 | ———————————————————————————————— 36 | user表:(配置文件中设为忽略数据对比) 37 | 38 | 1、玩家王五违规被删除 39 | 2、新增玩家赵六 -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/AppValues.cs: -------------------------------------------------------------------------------- 1 | using MySql.Data.MySqlClient; 2 | using System.Collections.Generic; 3 | 4 | public class AppValues 5 | { 6 | /// 7 | /// 新版本数据库的连接字符串 8 | /// 9 | public static string ConnectStringForNewDatabase = null; 10 | 11 | /// 12 | /// 老版本数据库的连接字符串 13 | /// 14 | public static string ConnectStringForOldDatabase = null; 15 | 16 | /// 17 | /// 配置文件所在路径 18 | /// 19 | public static string ConfigFilePath = null; 20 | 21 | /// 22 | /// 从配置文件中读取的数据库对比规则(key:表名, value:对比规则) 23 | /// 24 | public static Dictionary AllTableCompareRule = null; 25 | 26 | // 以下依次为新旧数据库连接对象、Schema名、已存在的表名 27 | public static MySqlConnection OldConn = null; 28 | public static MySqlConnection NewConn = null; 29 | public static string OldSchemaName = null; 30 | public static string NewSchemaName = null; 31 | public static List OldExistTableNames = null; 32 | public static List NewExistTableNames = null; 33 | // 新旧数据库中所有表格结构信息(key:表名, value:TableInfo类) 34 | public static Dictionary OldTableInfo = null; 35 | public static Dictionary NewTableInfo = null; 36 | } 37 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/ConfigReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | 7 | public class ConfigReader 8 | { 9 | public static Dictionary LoadConfig(string filePath, out string errorString) 10 | { 11 | if (!File.Exists(filePath)) 12 | { 13 | errorString = string.Format("输入路径为{0}的配置文件不存在", filePath); 14 | return null; 15 | } 16 | 17 | Dictionary allTableCompareRule = new Dictionary(); 18 | 19 | using (StreamReader reader = new StreamReader(filePath, Encoding.UTF8)) 20 | { 21 | string line = null; 22 | int lineNumber = 0; 23 | while ((line = reader.ReadLine()) != null) 24 | { 25 | ++lineNumber; 26 | 27 | // 忽略以#开头的注释行和空行 28 | if (string.IsNullOrEmpty(line) || line.StartsWith("#")) 29 | continue; 30 | 31 | // 以@开头的为表格对比方式配置 32 | if (line.StartsWith("@")) 33 | { 34 | // 配置以英文冒号分隔的键值对,冒号左边声明数据库表名,右边声明对比方式(0为忽略,不进行比较;1为仅比较表结构;2为比较结构及数据) 35 | string[] keyAndVale = line.Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries); 36 | if (keyAndVale.Length != 2) 37 | { 38 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以@开头的表格对比方式配置,需用英文冒号分隔数据库表名和对比方式", lineNumber, line); 39 | return null; 40 | } 41 | string tableName = keyAndVale[0].Substring(1).Trim(); 42 | if (string.IsNullOrEmpty(tableName)) 43 | { 44 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以@开头的表格对比方式配置中,未声明表格名", lineNumber, line); 45 | return null; 46 | } 47 | int compareWayNumber; 48 | if (int.TryParse(keyAndVale[1], out compareWayNumber) == false) 49 | { 50 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以@开头的表格对比方式配置中,键值\"{2}\"不是合法的对比方式数字,请按以下规则配置:0为忽略,不进行比较;1为仅比较表结构;2为比较结构及数据", lineNumber, line, keyAndVale[1]); 51 | return null; 52 | } 53 | if (Enum.IsDefined(typeof(TableCompareWays), compareWayNumber) == false || compareWayNumber == (int)TableCompareWays.UnDefine) 54 | { 55 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以@开头的表格对比方式配置中,键值\"{2}\"不是合法的对比方式数字,请按以下规则配置:0为忽略,不进行比较;1为仅比较表结构;2为比较结构及数据", lineNumber, line, keyAndVale[1]); 56 | return null; 57 | } 58 | 59 | if (!allTableCompareRule.ContainsKey(tableName)) 60 | allTableCompareRule.Add(tableName, new TableCompareRule()); 61 | 62 | TableCompareRule tableCompareRule = allTableCompareRule[tableName]; 63 | if (tableCompareRule.CompareWay != TableCompareWays.UnDefine) 64 | { 65 | errorString = string.Format("第{0}行的配置\"{1}\"重复对表格{2}进行对比方式配置,之前已经声明了对比方式为{3}", lineNumber, line, tableName, (int)tableCompareRule.CompareWay); 66 | return null; 67 | } 68 | 69 | tableCompareRule.CompareWay = (TableCompareWays)compareWayNumber; 70 | } 71 | // 以!开头的为配置对某张表格进行数据对比时忽略的列名 72 | else if (line.StartsWith("!")) 73 | { 74 | // 键名为表名,键值为要忽略的列名,不同列名之间用|分隔 75 | string[] keyAndVale = line.Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries); 76 | if (keyAndVale.Length != 2) 77 | { 78 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以!开头的对某张表格进行数据对比时忽略的列名的配置,需用英文冒号分隔数据库表名和列名", lineNumber, line); 79 | return null; 80 | } 81 | string tableName = keyAndVale[0].Substring(1).Trim(); 82 | if (string.IsNullOrEmpty(tableName)) 83 | { 84 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以!开头的对某张表格进行数据对比时忽略的列名的配置,未声明表格名", lineNumber, line); 85 | return null; 86 | } 87 | string[] columnNames = keyAndVale[1].Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries); 88 | if (columnNames.Length < 1) 89 | { 90 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以!开头的对某张表格进行数据对比时忽略的列名的配置,未声明忽略哪些列", lineNumber, line); 91 | return null; 92 | } 93 | 94 | if (!allTableCompareRule.ContainsKey(tableName)) 95 | allTableCompareRule.Add(tableName, new TableCompareRule()); 96 | 97 | TableCompareRule tableCompareRule = allTableCompareRule[tableName]; 98 | if (tableCompareRule.CompareIgnoreColumn.Count > 0) 99 | { 100 | errorString = string.Format("第{0}行的配置\"{1}\"重复对表格{2}配置数据对比时忽略的列名,之前已进行过声明", lineNumber, line, tableName); 101 | return null; 102 | } 103 | 104 | for (int i = 0; i < columnNames.Length; ++i) 105 | tableCompareRule.CompareIgnoreColumn.Add(columnNames[i].Trim()); 106 | } 107 | // 以$开头的为配置对某张表格进行数据对比时忽略对特定列中特定取值的行进行比较 108 | else if (line.StartsWith("$")) 109 | { 110 | // 键名为表名,键值为列名后面在英文小括号中声明数据值,使用正则表达式进行模糊匹配,若需要同时满足多个列为特定值,规则之间用&&连接 111 | // 比如“$server_config:key(^channelId$)&&lastUpdateUser(^管理员1$)”表示忽略对server_config中key列中值为“channelId”并且上次修改者为“管理员1”的行进行比较。注意若要精确匹配,需使用正则表达式中的^和$表示字符串开头和结尾 112 | // 考虑到正则表达式中冒号为特殊符号,不使用split分隔键值对,而是查找最左边的冒号 113 | int colonIndex = line.IndexOf(":"); 114 | if (colonIndex == -1) 115 | { 116 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以$开头的对某张表格进行指定行忽略的配置,需用英文冒号分隔数据库表名和特殊值声明", lineNumber, line); 117 | return null; 118 | } 119 | string tableName = line.Substring(1, colonIndex - 1).Trim(); 120 | if (string.IsNullOrEmpty(tableName)) 121 | { 122 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以$开头的对某张表格进行指定行忽略的配置,未声明表格名", lineNumber, line); 123 | return null; 124 | } 125 | string configString = line.Substring(colonIndex + 1).Trim(); 126 | if (string.IsNullOrEmpty(configString)) 127 | { 128 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以$开头的对某张表格进行指定行忽略的配置,需在冒号后声明的键值格式为列名后面在英文小括号中声明数据值,使用正则表达式进行模糊匹配,若需要同时满足多个列为特定值,规则之间用&&连接", lineNumber, line); 129 | return null; 130 | } 131 | 132 | Dictionary oneIgnoreLineInfo = new Dictionary(); 133 | // 用&&分隔需同时满足的多个忽略特定值配置 134 | string[] allIgnoreConfigString = configString.Split(new string[] { "&&" }, StringSplitOptions.RemoveEmptyEntries); 135 | for (int i = 0; i < allIgnoreConfigString.Length; ++i) 136 | { 137 | string oneIgnoreConfigString = allIgnoreConfigString[i].Trim(); 138 | int leftBracketIndex = oneIgnoreConfigString.IndexOf("("); 139 | int rightBracketIndex = oneIgnoreConfigString.LastIndexOf(")"); 140 | if (leftBracketIndex != -1 && rightBracketIndex > leftBracketIndex) 141 | { 142 | string columnName = oneIgnoreConfigString.Substring(0, leftBracketIndex).Trim(); 143 | if (string.IsNullOrEmpty(columnName)) 144 | { 145 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以$开头的对某张表格进行指定行忽略的配置中,\"{2}\"未在括号前声明列名", lineNumber, line, oneIgnoreConfigString); 146 | return null; 147 | } 148 | string ignoreValue = oneIgnoreConfigString.Substring(leftBracketIndex + 1, rightBracketIndex - leftBracketIndex - 1); 149 | if (string.IsNullOrEmpty(ignoreValue)) 150 | { 151 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以$开头的对某张表格进行指定行忽略的配置中,\"{2}\"未在列名后面在英文小括号中声明数据值", lineNumber, line, oneIgnoreConfigString); 152 | return null; 153 | } 154 | 155 | Regex regex = new Regex(ignoreValue); 156 | oneIgnoreLineInfo.Add(columnName, regex); 157 | } 158 | else 159 | { 160 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以$开头的对某张表格进行指定行忽略的配置中,\"{2}\"不符合键值格式为列名后面在英文小括号中声明数据值", lineNumber, line, oneIgnoreConfigString); 161 | return null; 162 | } 163 | } 164 | 165 | if (!allTableCompareRule.ContainsKey(tableName)) 166 | allTableCompareRule.Add(tableName, new TableCompareRule()); 167 | 168 | TableCompareRule tableCompareRule = allTableCompareRule[tableName]; 169 | tableCompareRule.CompareIgnoreData.Add(oneIgnoreLineInfo); 170 | } 171 | else 172 | { 173 | errorString = string.Format("第{0}行的配置\"{1}\"不合法,以非法字符\"{2}\"开头,请根据说明文件进行配置", lineNumber, line, line[0]); 174 | return null; 175 | } 176 | } 177 | } 178 | 179 | errorString = null; 180 | return allTableCompareRule; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace ServerDatabaseVersionUpdateHelper 2 | { 3 | partial class MainForm 4 | { 5 | /// 6 | /// 必需的设计器变量。 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// 清理所有正在使用的资源。 12 | /// 13 | /// 如果应释放托管资源,为 true;否则为 false。 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows 窗体设计器生成的代码 24 | 25 | /// 26 | /// 设计器支持所需的方法 - 不要 27 | /// 使用代码编辑器修改此方法的内容。 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.lblOldConnString = new System.Windows.Forms.Label(); 32 | this.txtOldConnString = new System.Windows.Forms.TextBox(); 33 | this.lblNewConnString = new System.Windows.Forms.Label(); 34 | this.txtNewConnString = new System.Windows.Forms.TextBox(); 35 | this.lblConfigPath = new System.Windows.Forms.Label(); 36 | this.txtConfigPath = new System.Windows.Forms.TextBox(); 37 | this.btnConfigPath = new System.Windows.Forms.Button(); 38 | this.btnCompare = new System.Windows.Forms.Button(); 39 | this.grpResult = new System.Windows.Forms.GroupBox(); 40 | this.btnSaveToFile = new System.Windows.Forms.Button(); 41 | this.btnCopyToClipboard = new System.Windows.Forms.Button(); 42 | this.chkIgnoreComment = new System.Windows.Forms.CheckBox(); 43 | this.rtxResult = new System.Windows.Forms.RichTextBox(); 44 | this.grpResult.SuspendLayout(); 45 | this.SuspendLayout(); 46 | // 47 | // lblOldConnString 48 | // 49 | this.lblOldConnString.AutoSize = true; 50 | this.lblOldConnString.Location = new System.Drawing.Point(22, 26); 51 | this.lblOldConnString.Name = "lblOldConnString"; 52 | this.lblOldConnString.Size = new System.Drawing.Size(137, 12); 53 | this.lblOldConnString.TabIndex = 0; 54 | this.lblOldConnString.Text = "旧版数据库连接字符串:"; 55 | // 56 | // txtOldConnString 57 | // 58 | this.txtOldConnString.Location = new System.Drawing.Point(165, 23); 59 | this.txtOldConnString.Name = "txtOldConnString"; 60 | this.txtOldConnString.Size = new System.Drawing.Size(541, 21); 61 | this.txtOldConnString.TabIndex = 1; 62 | // 63 | // lblNewConnString 64 | // 65 | this.lblNewConnString.AutoSize = true; 66 | this.lblNewConnString.Location = new System.Drawing.Point(22, 56); 67 | this.lblNewConnString.Name = "lblNewConnString"; 68 | this.lblNewConnString.Size = new System.Drawing.Size(137, 12); 69 | this.lblNewConnString.TabIndex = 2; 70 | this.lblNewConnString.Text = "新版数据库连接字符串:"; 71 | // 72 | // txtNewConnString 73 | // 74 | this.txtNewConnString.Location = new System.Drawing.Point(165, 53); 75 | this.txtNewConnString.Name = "txtNewConnString"; 76 | this.txtNewConnString.Size = new System.Drawing.Size(541, 21); 77 | this.txtNewConnString.TabIndex = 3; 78 | // 79 | // lblConfigPath 80 | // 81 | this.lblConfigPath.AutoSize = true; 82 | this.lblConfigPath.Location = new System.Drawing.Point(22, 86); 83 | this.lblConfigPath.Name = "lblConfigPath"; 84 | this.lblConfigPath.Size = new System.Drawing.Size(89, 12); 85 | this.lblConfigPath.TabIndex = 4; 86 | this.lblConfigPath.Text = "配置文件路径:"; 87 | // 88 | // txtConfigPath 89 | // 90 | this.txtConfigPath.Location = new System.Drawing.Point(165, 83); 91 | this.txtConfigPath.Name = "txtConfigPath"; 92 | this.txtConfigPath.Size = new System.Drawing.Size(456, 21); 93 | this.txtConfigPath.TabIndex = 5; 94 | // 95 | // btnConfigPath 96 | // 97 | this.btnConfigPath.Location = new System.Drawing.Point(631, 81); 98 | this.btnConfigPath.Name = "btnConfigPath"; 99 | this.btnConfigPath.Size = new System.Drawing.Size(75, 23); 100 | this.btnConfigPath.TabIndex = 6; 101 | this.btnConfigPath.Text = "浏览"; 102 | this.btnConfigPath.UseVisualStyleBackColor = true; 103 | this.btnConfigPath.Click += new System.EventHandler(this.btnConfigPath_Click); 104 | // 105 | // btnCompare 106 | // 107 | this.btnCompare.Location = new System.Drawing.Point(728, 41); 108 | this.btnCompare.Name = "btnCompare"; 109 | this.btnCompare.Size = new System.Drawing.Size(68, 43); 110 | this.btnCompare.TabIndex = 7; 111 | this.btnCompare.Text = "对比"; 112 | this.btnCompare.UseVisualStyleBackColor = true; 113 | this.btnCompare.Click += new System.EventHandler(this.btnCompare_Click); 114 | // 115 | // grpResult 116 | // 117 | this.grpResult.Controls.Add(this.btnSaveToFile); 118 | this.grpResult.Controls.Add(this.btnCopyToClipboard); 119 | this.grpResult.Controls.Add(this.chkIgnoreComment); 120 | this.grpResult.Controls.Add(this.rtxResult); 121 | this.grpResult.Location = new System.Drawing.Point(24, 138); 122 | this.grpResult.Name = "grpResult"; 123 | this.grpResult.Size = new System.Drawing.Size(772, 324); 124 | this.grpResult.TabIndex = 8; 125 | this.grpResult.TabStop = false; 126 | this.grpResult.Text = "对比结果及生成SQL"; 127 | // 128 | // btnSaveToFile 129 | // 130 | this.btnSaveToFile.Location = new System.Drawing.Point(698, 200); 131 | this.btnSaveToFile.Name = "btnSaveToFile"; 132 | this.btnSaveToFile.Size = new System.Drawing.Size(58, 44); 133 | this.btnSaveToFile.TabIndex = 3; 134 | this.btnSaveToFile.Text = "保存到文件"; 135 | this.btnSaveToFile.UseVisualStyleBackColor = true; 136 | this.btnSaveToFile.Click += new System.EventHandler(this.btnSaveToFile_Click); 137 | // 138 | // btnCopyToClipboard 139 | // 140 | this.btnCopyToClipboard.Location = new System.Drawing.Point(698, 128); 141 | this.btnCopyToClipboard.Name = "btnCopyToClipboard"; 142 | this.btnCopyToClipboard.Size = new System.Drawing.Size(58, 44); 143 | this.btnCopyToClipboard.TabIndex = 2; 144 | this.btnCopyToClipboard.Text = "复制到剪贴板"; 145 | this.btnCopyToClipboard.UseVisualStyleBackColor = true; 146 | this.btnCopyToClipboard.Click += new System.EventHandler(this.btnCopyToClipboard_Click); 147 | // 148 | // chkIgnoreComment 149 | // 150 | this.chkIgnoreComment.AutoSize = true; 151 | this.chkIgnoreComment.Location = new System.Drawing.Point(698, 87); 152 | this.chkIgnoreComment.Name = "chkIgnoreComment"; 153 | this.chkIgnoreComment.Size = new System.Drawing.Size(72, 16); 154 | this.chkIgnoreComment.TabIndex = 1; 155 | this.chkIgnoreComment.Text = "忽略注释"; 156 | this.chkIgnoreComment.UseVisualStyleBackColor = true; 157 | // 158 | // rtxResult 159 | // 160 | this.rtxResult.Location = new System.Drawing.Point(19, 32); 161 | this.rtxResult.Name = "rtxResult"; 162 | this.rtxResult.Size = new System.Drawing.Size(663, 268); 163 | this.rtxResult.TabIndex = 0; 164 | this.rtxResult.Text = ""; 165 | // 166 | // MainForm 167 | // 168 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); 169 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 170 | this.ClientSize = new System.Drawing.Size(819, 484); 171 | this.Controls.Add(this.grpResult); 172 | this.Controls.Add(this.btnCompare); 173 | this.Controls.Add(this.btnConfigPath); 174 | this.Controls.Add(this.txtConfigPath); 175 | this.Controls.Add(this.lblConfigPath); 176 | this.Controls.Add(this.txtNewConnString); 177 | this.Controls.Add(this.lblNewConnString); 178 | this.Controls.Add(this.txtOldConnString); 179 | this.Controls.Add(this.lblOldConnString); 180 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D; 181 | this.MaximizeBox = false; 182 | this.Name = "MainForm"; 183 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 184 | this.Text = "服务器数据库版本升级助手 1.0 by 张齐(https://github.com/zhangqi-ulua)"; 185 | this.grpResult.ResumeLayout(false); 186 | this.grpResult.PerformLayout(); 187 | this.ResumeLayout(false); 188 | this.PerformLayout(); 189 | 190 | } 191 | 192 | #endregion 193 | 194 | private System.Windows.Forms.Label lblOldConnString; 195 | private System.Windows.Forms.TextBox txtOldConnString; 196 | private System.Windows.Forms.Label lblNewConnString; 197 | private System.Windows.Forms.TextBox txtNewConnString; 198 | private System.Windows.Forms.Label lblConfigPath; 199 | private System.Windows.Forms.TextBox txtConfigPath; 200 | private System.Windows.Forms.Button btnConfigPath; 201 | private System.Windows.Forms.Button btnCompare; 202 | private System.Windows.Forms.GroupBox grpResult; 203 | private System.Windows.Forms.Button btnSaveToFile; 204 | private System.Windows.Forms.Button btnCopyToClipboard; 205 | private System.Windows.Forms.CheckBox chkIgnoreComment; 206 | private System.Windows.Forms.RichTextBox rtxResult; 207 | } 208 | } 209 | 210 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/MainForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.IO; 6 | using System.Text; 7 | using System.Windows.Forms; 8 | 9 | namespace ServerDatabaseVersionUpdateHelper 10 | { 11 | public partial class MainForm : Form 12 | { 13 | public MainForm() 14 | { 15 | InitializeComponent(); 16 | Utils.RtxResult = rtxResult; 17 | } 18 | 19 | private void btnCompare_Click(object sender, EventArgs e) 20 | { 21 | rtxResult.Text = string.Empty; 22 | string errorString = null; 23 | 24 | // 检查新旧数据库连接字符串是否输入 25 | AppValues.ConnectStringForOldDatabase = txtOldConnString.Text.Trim(); 26 | AppValues.ConnectStringForNewDatabase = txtNewConnString.Text.Trim(); 27 | if (string.IsNullOrEmpty(AppValues.ConnectStringForOldDatabase)) 28 | { 29 | MessageBox.Show("必须输入旧版数据库连接字符串", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 30 | return; 31 | } 32 | if (string.IsNullOrEmpty(AppValues.ConnectStringForNewDatabase)) 33 | { 34 | MessageBox.Show("必须输入新版数据库连接字符串", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 35 | return; 36 | } 37 | if (AppValues.ConnectStringForOldDatabase.Equals(AppValues.ConnectStringForNewDatabase)) 38 | { 39 | MessageBox.Show("输入的新旧两数据库连接字符串重复", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 40 | return; 41 | } 42 | // 检查配置文件 43 | AppValues.ConfigFilePath = txtConfigPath.Text.Trim(); 44 | if (string.IsNullOrEmpty(AppValues.ConfigFilePath)) 45 | { 46 | DialogResult dialogResult = MessageBox.Show("未指定配置文件,本工具将默认比较两数据库中所有表格的结构和数据,确定要这样做吗?\n\n点击“是”进行默认比较,点击“否”放弃", "警告", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); 47 | if (dialogResult == DialogResult.No) 48 | return; 49 | else 50 | AppValues.AllTableCompareRule = new Dictionary(); 51 | } 52 | else 53 | { 54 | Utils.AppendOutputText("读取配置文件:", OutputType.Comment); 55 | AppValues.AllTableCompareRule = ConfigReader.LoadConfig(AppValues.ConfigFilePath, out errorString); 56 | if (!string.IsNullOrEmpty(errorString)) 57 | { 58 | MessageBox.Show(string.Concat("配置文件中存在以下错误,请修正后重试:\n\n", errorString), "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 59 | return; 60 | } 61 | else 62 | Utils.AppendOutputText("成功\n", OutputType.None); 63 | } 64 | // 连接两数据库 65 | Utils.AppendOutputText("连接旧版数据库:", OutputType.Comment); 66 | MySQLOperateHelper.ConnectToDatabase(AppValues.ConnectStringForOldDatabase, out AppValues.OldConn, out AppValues.OldSchemaName, out AppValues.OldExistTableNames, out errorString); 67 | if (!string.IsNullOrEmpty(errorString)) 68 | { 69 | MessageBox.Show(string.Concat("连接旧版数据库失败,错误原因为:", errorString), "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 70 | return; 71 | } 72 | else 73 | Utils.AppendOutputText("成功\n", OutputType.None); 74 | 75 | Utils.AppendOutputText("连接新版数据库:", OutputType.Comment); 76 | MySQLOperateHelper.ConnectToDatabase(AppValues.ConnectStringForNewDatabase, out AppValues.NewConn, out AppValues.NewSchemaName, out AppValues.NewExistTableNames, out errorString); 77 | if (!string.IsNullOrEmpty(errorString)) 78 | { 79 | MessageBox.Show(string.Concat("连接新版数据库失败,错误原因为:", errorString), "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 80 | return; 81 | } 82 | else 83 | Utils.AppendOutputText("成功\n", OutputType.None); 84 | 85 | // 整理数据库结构信息 86 | Utils.AppendOutputText("分析旧版数据库表结构:", OutputType.Comment); 87 | AppValues.OldTableInfo = new Dictionary(); 88 | foreach (string tableName in AppValues.OldExistTableNames) 89 | { 90 | TableInfo tableInfo = MySQLOperateHelper.GetTableInfo(AppValues.OldSchemaName, tableName, AppValues.OldConn); 91 | AppValues.OldTableInfo.Add(tableName, tableInfo); 92 | } 93 | Utils.AppendOutputText("成功\n", OutputType.None); 94 | 95 | Utils.AppendOutputText("分析新版数据库表结构:", OutputType.Comment); 96 | AppValues.NewTableInfo = new Dictionary(); 97 | foreach (string tableName in AppValues.NewExistTableNames) 98 | { 99 | TableInfo tableInfo = MySQLOperateHelper.GetTableInfo(AppValues.NewSchemaName, tableName, AppValues.NewConn); 100 | AppValues.NewTableInfo.Add(tableName, tableInfo); 101 | } 102 | Utils.AppendOutputText("成功\n", OutputType.None); 103 | 104 | Utils.AppendOutputText("\n", OutputType.None); 105 | // 进行对比,并展示结果 106 | MySQLOperateHelper.CompareAndShowResult(out errorString); 107 | if (!string.IsNullOrEmpty(errorString)) 108 | { 109 | string tips = string.Concat("对比中发现以下问题,请修正后重新进行比较:\n\n", errorString); 110 | MessageBox.Show(tips, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 111 | } 112 | else 113 | { 114 | Utils.AppendOutputText("\n", OutputType.Comment); 115 | Utils.AppendOutputText("对比完毕", OutputType.Comment); 116 | } 117 | } 118 | 119 | private void btnConfigPath_Click(object sender, EventArgs e) 120 | { 121 | OpenFileDialog dialog = new OpenFileDialog(); 122 | dialog.Title = "请选择配置文件所在路径"; 123 | dialog.Multiselect = false; 124 | dialog.Filter = "文本文件 (*.txt)|*.txt"; 125 | if (dialog.ShowDialog() == DialogResult.OK) 126 | txtConfigPath.Text = dialog.FileName; 127 | } 128 | 129 | private string _GetTextIgnoreComment(string text) 130 | { 131 | StringBuilder stringBuilder = new StringBuilder(); 132 | using (StringReader reader = new StringReader(rtxResult.Text)) 133 | { 134 | string line = null; 135 | while ((line = reader.ReadLine()) != null) 136 | { 137 | if (!line.StartsWith("--")) 138 | stringBuilder.AppendLine(line); 139 | } 140 | } 141 | 142 | return stringBuilder.ToString(); 143 | } 144 | 145 | private void btnCopyToClipboard_Click(object sender, EventArgs e) 146 | { 147 | string text = null; 148 | if (chkIgnoreComment.Checked == true) 149 | text = _GetTextIgnoreComment(rtxResult.Text); 150 | else 151 | text = rtxResult.Text; 152 | 153 | Clipboard.SetData(DataFormats.Text, text); 154 | MessageBox.Show("已成功复制到剪贴板", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); 155 | } 156 | 157 | private void btnSaveToFile_Click(object sender, EventArgs e) 158 | { 159 | string text = null; 160 | if (chkIgnoreComment.Checked == true) 161 | text = _GetTextIgnoreComment(rtxResult.Text); 162 | else 163 | text = rtxResult.Text; 164 | 165 | SaveFileDialog dialog = new SaveFileDialog(); 166 | dialog.ValidateNames = true; 167 | dialog.Title = "请选择导出文件的存储路径"; 168 | dialog.Filter = "Sql files (*.sql)|*.sql|Text files (*.txt)|*.txt|All files (*.*)|*.*"; 169 | 170 | if (dialog.ShowDialog() == DialogResult.OK) 171 | { 172 | string savePath = dialog.FileName; 173 | try 174 | { 175 | StreamWriter writer = new StreamWriter(savePath, false, new UTF8Encoding(false)); 176 | writer.Write(text); 177 | writer.Flush(); 178 | writer.Close(); 179 | 180 | MessageBox.Show("保存成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); 181 | } 182 | catch (Exception exception) 183 | { 184 | MessageBox.Show(string.Concat("保存失败,错误原因为:", exception.Message), "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 185 | } 186 | } 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/MainForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/MySQLOperateHelper.cs: -------------------------------------------------------------------------------- 1 | using MySql.Data.MySqlClient; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.IO; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | 9 | public class MySQLOperateHelper 10 | { 11 | // MySQL支持的用于定义Schema名的参数名 12 | private static string[] _DEFINE_SCHEMA_NAME_PARAM = { "Database", "Initial Catalog" }; 13 | 14 | private const string _SELECT_DATA_SQL = "SELECT {0} FROM {1};"; 15 | private const string _SELECT_COLUMN_INFO_SQL = "SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '{0}' AND TABLE_NAME = '{1}' ORDER BY ORDINAL_POSITION ASC;"; 16 | private const string _SELECT_TABLE_INFO_SQL = "SELECT {0} FROM information_schema.TABLES WHERE TABLE_SCHEMA = '{1}' AND TABLE_NAME = '{2}';"; 17 | private const string _SHOW_CREATE_TABLE_SQL = "SHOW CREATE TABLE {0}"; 18 | private const string _SHOW_INDEX_SQL = "SHOW INDEX FROM {0} WHERE Key_name != 'PRIMARY' AND Index_type = 'BTREE';"; 19 | private const string _INSERT_DATA_SQL = "INSERT INTO {0} ({1}) VALUES {2};"; 20 | private const string _DROP_TABLE_SQL = "DROP TABLE {0};"; 21 | private const string _ALTER_TABLE_SQL = "ALTER TABLE {0}"; 22 | private const string _DROP_COLUMN_SQL = "DROP COLUMN `{0}`"; 23 | private const string _ADD_COLUMN_SQL = "ADD COLUMN `{0}` {1} {2}{3} COMMENT '{4}'"; 24 | private const string _CHANGE_COLUMN_SQL = "CHANGE COLUMN `{0}` `{0}` {1} {2}{3} COMMENT '{4}'"; 25 | private const string _DROP_PRIMARY_KEY_SQL = "DROP PRIMARY KEY"; 26 | private const string _ADD_PRIMARY_KEY_SQL = "ADD PRIMARY KEY ({0})"; 27 | private const string _DROP_INDEX_SQL = "DROP INDEX `{0}`"; 28 | private const string _ADD_UNIQUE_INDEX_SQL = "ADD UNIQUE INDEX `{0}` ({1})"; 29 | private const string _ALTER_TABLE_COMMENT_SQL = "COMMENT = '{0}'"; 30 | private const string _ALTER_TABLE_COLLATION_SQL = "COLLATE = {0}"; 31 | private const string _DROP_DATA_SQL = "DELETE FROM {0} WHERE {1};"; 32 | private const string _UPDATE_DATA_SQL = "UPDATE {0} SET {1} WHERE {2};"; 33 | 34 | /// 35 | /// 连接数据库,并返回连接对象、Schema名、所有表名 36 | /// 37 | public static bool ConnectToDatabase(string connectString, out MySqlConnection conn, out string schemaName, out List existTableNames, out string errorString) 38 | { 39 | conn = null; 40 | schemaName = null; 41 | existTableNames = new List(); 42 | 43 | // 提取MySQL连接字符串中的Schema名 44 | foreach (string legalSchemaNameParam in _DEFINE_SCHEMA_NAME_PARAM) 45 | { 46 | int defineStartIndex = connectString.IndexOf(legalSchemaNameParam, StringComparison.CurrentCultureIgnoreCase); 47 | if (defineStartIndex != -1) 48 | { 49 | // 查找后面的等号 50 | int equalSignIndex = -1; 51 | for (int i = defineStartIndex + legalSchemaNameParam.Length; i < connectString.Length; ++i) 52 | { 53 | if (connectString[i] == '=') 54 | { 55 | equalSignIndex = i; 56 | break; 57 | } 58 | } 59 | if (equalSignIndex == -1 || equalSignIndex + 1 == connectString.Length) 60 | { 61 | errorString = string.Format("MySQL数据库连接字符串(\"{0}\")中\"{1}\"后需要跟\"=\"进行Schema名声明", connectString, legalSchemaNameParam); 62 | return false; 63 | } 64 | else 65 | { 66 | // 查找定义的Schema名,在参数声明的=后面截止到下一个分号或字符串结束 67 | int semicolonIndex = -1; 68 | for (int i = equalSignIndex + 1; i < connectString.Length; ++i) 69 | { 70 | if (connectString[i] == ';') 71 | { 72 | semicolonIndex = i; 73 | break; 74 | } 75 | } 76 | if (semicolonIndex == -1) 77 | schemaName = connectString.Substring(equalSignIndex + 1).Trim(); 78 | else 79 | schemaName = connectString.Substring(equalSignIndex + 1, semicolonIndex - equalSignIndex - 1).Trim(); 80 | } 81 | 82 | break; 83 | } 84 | } 85 | if (schemaName == null) 86 | { 87 | errorString = string.Format("MySQL数据库连接字符串(\"{0}\")中不包含Schema名的声明,请在{1}中任选一个参数名进行声明", connectString, Utils.CombineString(_DEFINE_SCHEMA_NAME_PARAM, ",")); 88 | return false; 89 | } 90 | 91 | try 92 | { 93 | conn = new MySqlConnection(connectString); 94 | conn.Open(); 95 | if (conn.State == System.Data.ConnectionState.Open) 96 | { 97 | // 获取已存在的数据表名 98 | DataTable schemaInfo = conn.GetSchema(System.Data.SqlClient.SqlClientMetaDataCollectionNames.Tables); 99 | foreach (DataRow info in schemaInfo.Rows) 100 | existTableNames.Add(info.ItemArray[2].ToString()); 101 | 102 | errorString = null; 103 | return true; 104 | } 105 | else 106 | { 107 | errorString = "未知错误"; 108 | return true; 109 | } 110 | } 111 | catch (Exception exception) 112 | { 113 | errorString = exception.Message; 114 | return false; 115 | } 116 | } 117 | 118 | /// 119 | /// 获取某张表的某个属性 120 | /// 121 | private static string _GetTableProperty(string schemaName, string tableName, string propertyName, MySqlConnection conn) 122 | { 123 | MySqlCommand cmd = new MySqlCommand(string.Format(_SELECT_TABLE_INFO_SQL, propertyName, schemaName, tableName), conn); 124 | DataTable dt = _ExecuteSqlCommand(cmd); 125 | return dt.Rows.Count > 0 ? dt.Rows[0][0].ToString() : string.Empty; 126 | } 127 | 128 | /// 129 | /// 获取某张表的建表SQL 130 | /// 131 | private static string _GetCreateTableSql(string schemaName, string tableName, string targetSchemaName, MySqlConnection conn) 132 | { 133 | MySqlCommand cmd = new MySqlCommand(string.Format(_SHOW_CREATE_TABLE_SQL, _CombineDatabaseTableFullName(schemaName, tableName)), conn); 134 | DataTable dt = _ExecuteSqlCommand(cmd); 135 | string createTableSql = dt.Rows[0]["Create Table"].ToString(); 136 | // MySQL提供功能返回的建表SQL不含Schema,这里自己加上 137 | int firstBracketIndex = createTableSql.IndexOf("("); 138 | return string.Format("CREATE TABLE {0} {1};", _CombineDatabaseTableFullName(targetSchemaName, tableName), createTableSql.Substring(firstBracketIndex)); 139 | } 140 | 141 | /// 142 | /// 获取填充某表格所有数据的SQL 143 | /// 144 | private static string _GetFillDataSql(TableInfo tableInfo, DataTable data, string targetSchemaName) 145 | { 146 | // 处理空表的情况 147 | if (data.Rows.Count == 0) 148 | return null; 149 | 150 | // 生成所有列名组成的定义字符串 151 | List columnDefine = new List(); 152 | foreach (string columnName in tableInfo.AllColumnInfo.Keys) 153 | columnDefine.Add(string.Format("`{0}`", columnName)); 154 | 155 | string columnDefineString = Utils.CombineString(columnDefine, ", "); 156 | 157 | // 逐行生成插入数据的SQL语句中的value定义部分 158 | StringBuilder valueDefineStringBuilder = new StringBuilder(); 159 | int rowCount = data.Rows.Count; 160 | const string SPLIT_STRING = ",\n"; 161 | for (int row = 0; row < rowCount; ++row) 162 | { 163 | List values = new List(); 164 | foreach (string columnName in tableInfo.AllColumnInfo.Keys) 165 | { 166 | object value = data.Rows[row][columnName]; 167 | values.Add(_GetDatabaseValueString(value)); 168 | } 169 | 170 | valueDefineStringBuilder.AppendFormat("({0}){1}", Utils.CombineString(values, ","), SPLIT_STRING); 171 | } 172 | // 去掉末尾多余的逗号和换行 173 | string valueDefineString = valueDefineStringBuilder.ToString(); 174 | valueDefineString = valueDefineString.Substring(0, valueDefineString.Length - SPLIT_STRING.Length); 175 | 176 | return string.Format(_INSERT_DATA_SQL, _CombineDatabaseTableFullName(targetSchemaName, tableInfo.TableName), columnDefineString, string.Concat("\n", valueDefineString)); 177 | } 178 | 179 | /// 180 | /// 获取某表格的索引设置 181 | /// 182 | private static Dictionary> _GetIndexInfo(string schemaName, string tableName, MySqlConnection conn) 183 | { 184 | Dictionary> indexInfo = new Dictionary>(); 185 | 186 | // MySQL的SHOW INDEX语句中无法使用ORDER BY,而List中没有前面的元素就无法在后面指定下标处插入数据,故用下面的数据结构进行整理,其中内层Dictionary的key为序号,value为列名 187 | Dictionary> tempIndexInfo = new Dictionary>(); 188 | 189 | MySqlCommand cmd = new MySqlCommand(string.Format(_SHOW_INDEX_SQL, _CombineDatabaseTableFullName(schemaName, tableName)), conn); 190 | DataTable dt = _ExecuteSqlCommand(cmd); 191 | 192 | int count = dt.Rows.Count; 193 | for (int i = 0; i < count; ++i) 194 | { 195 | string name = dt.Rows[i]["Key_name"].ToString(); 196 | string columnName = dt.Rows[i]["Column_name"].ToString(); 197 | int seq = int.Parse(dt.Rows[i]["Seq_in_index"].ToString()); 198 | if (!tempIndexInfo.ContainsKey(name)) 199 | tempIndexInfo.Add(name, new Dictionary()); 200 | 201 | Dictionary tempColumnNames = tempIndexInfo[name]; 202 | tempColumnNames.Add(seq, columnName); 203 | } 204 | 205 | // 转为Dictionary>数据结构 206 | foreach (var pair in tempIndexInfo) 207 | { 208 | string name = pair.Key; 209 | indexInfo.Add(name, new List()); 210 | List columnNames = indexInfo[name]; 211 | int columnCount = pair.Value.Count; 212 | for (int seq = 1; seq <= columnCount; ++seq) 213 | columnNames.Add(pair.Value[seq]); 214 | } 215 | 216 | return indexInfo; 217 | } 218 | 219 | /// 220 | /// 获取删除某张表的SQL 221 | /// 222 | private static string _GetDropTableSql(string schemaName, string tableName) 223 | { 224 | return string.Format(_DROP_TABLE_SQL, _CombineDatabaseTableFullName(schemaName, tableName)); 225 | } 226 | 227 | /// 228 | /// 根据Select语句返回查询结果 229 | /// 230 | private static DataTable _SelectData(string schemaName, string tableName, string selectColumns, MySqlConnection conn) 231 | { 232 | MySqlCommand cmd = new MySqlCommand(string.Format(_SELECT_DATA_SQL, selectColumns, _CombineDatabaseTableFullName(schemaName, tableName)), conn); 233 | return _ExecuteSqlCommand(cmd); 234 | } 235 | 236 | /// 237 | /// 返回某张表格所有列属性 238 | /// 239 | private static DataTable _GetAllColumnInfo(string schemaName, string tableName, MySqlConnection conn) 240 | { 241 | MySqlCommand cmd = new MySqlCommand(string.Format(_SELECT_COLUMN_INFO_SQL, schemaName, tableName), conn); 242 | return _ExecuteSqlCommand(cmd); 243 | } 244 | 245 | /// 246 | /// 将某张表格的属性作为TableInfo类返回 247 | /// 248 | public static TableInfo GetTableInfo(string schemaName, string tableName, MySqlConnection conn) 249 | { 250 | TableInfo tableInfo = new TableInfo(); 251 | // Schema名 252 | tableInfo.SchemaName = schemaName; 253 | // 表名 254 | tableInfo.TableName = tableName; 255 | // 表注释(注意转义注释中的换行) 256 | tableInfo.Comment = _GetTableProperty(schemaName, tableName, "TABLE_COMMENT", conn).Replace(System.Environment.NewLine, "\\n").Replace("\n", "\\n"); 257 | // 表校对集 258 | tableInfo.Collation = _GetTableProperty(schemaName, tableName, "TABLE_COLLATION", conn); 259 | // 索引设置 260 | tableInfo.IndexInfo = _GetIndexInfo(schemaName, tableName, conn); 261 | // 列信息 262 | DataTable dtColumnInfo = _GetAllColumnInfo(schemaName, tableName, conn); 263 | if (dtColumnInfo != null) 264 | { 265 | int columnCount = dtColumnInfo.Rows.Count; 266 | for (int i = 0; i < columnCount; ++i) 267 | { 268 | ColumnInfo columnInfo = new ColumnInfo(); 269 | // 表名 270 | columnInfo.TableName = tableName; 271 | // 列名 272 | columnInfo.ColumnName = dtColumnInfo.Rows[i]["COLUMN_NAME"].ToString(); 273 | // 注释(注意转义注释中的换行) 274 | columnInfo.Comment = dtColumnInfo.Rows[i]["COLUMN_COMMENT"].ToString().Replace(System.Environment.NewLine, "\\n").Replace("\n", "\\n"); 275 | // 数据类型(包含长度) 276 | columnInfo.DataType = dtColumnInfo.Rows[i]["COLUMN_TYPE"].ToString(); 277 | // 属性 278 | string columnKey = dtColumnInfo.Rows[i]["COLUMN_KEY"].ToString(); 279 | if (!string.IsNullOrEmpty(columnKey)) 280 | { 281 | if (columnKey.IndexOf("PRI", StringComparison.CurrentCultureIgnoreCase) != -1) 282 | { 283 | columnInfo.IsPrimaryKey = true; 284 | tableInfo.PrimaryKeyColumnNames.Add(columnInfo.ColumnName); 285 | } 286 | if (columnKey.IndexOf("UNI", StringComparison.CurrentCultureIgnoreCase) != -1) 287 | columnInfo.IsUnique = true; 288 | if (columnKey.IndexOf("MUL", StringComparison.CurrentCultureIgnoreCase) != -1) 289 | columnInfo.IsMultiple = true; 290 | } 291 | // 额外属性 292 | string extra = dtColumnInfo.Rows[i]["EXTRA"].ToString(); 293 | if (!string.IsNullOrEmpty(extra)) 294 | { 295 | if (columnKey.IndexOf("auto_increment", StringComparison.CurrentCultureIgnoreCase) != -1) 296 | columnInfo.IsAutoIncrement = true; 297 | } 298 | // 是否非空 299 | columnInfo.IsNotEmpty = dtColumnInfo.Rows[i]["IS_NULLABLE"].ToString().Equals("NO", StringComparison.CurrentCultureIgnoreCase); 300 | // 默认值 301 | object defaultValue = dtColumnInfo.Rows[i]["COLUMN_DEFAULT"]; 302 | string defaultValueString = _GetDatabaseValueString(defaultValue); 303 | columnInfo.DefaultValue = defaultValueString; 304 | 305 | tableInfo.AllColumnInfo.Add(columnInfo.ColumnName, columnInfo); 306 | } 307 | } 308 | 309 | return tableInfo; 310 | } 311 | 312 | /// 313 | /// 执行指定SQL语句,返回执行结果 314 | /// 315 | private static DataTable _ExecuteSqlCommand(MySqlCommand cmd) 316 | { 317 | MySqlDataAdapter da = new MySqlDataAdapter(cmd); 318 | DataTable dt = new DataTable(); 319 | da.Fill(dt); 320 | return dt; 321 | } 322 | 323 | /// 324 | /// 对新旧两版本数据库进行对比,并展示结果以及生成的SQL 325 | /// 326 | public static void CompareAndShowResult(out string errorString) 327 | { 328 | StringBuilder errorStringBuilder = new StringBuilder(); 329 | 330 | // 找出新版本中删除的表 331 | List dropTableNames = new List(); 332 | foreach (string tableName in AppValues.OldExistTableNames) 333 | { 334 | if (!AppValues.NewExistTableNames.Contains(tableName)) 335 | dropTableNames.Add(tableName); 336 | } 337 | if (dropTableNames.Count > 0) 338 | { 339 | Utils.AppendOutputText(string.Format("新版本数据库中删除以下表格:{0}\n", Utils.CombineString(dropTableNames, ",")), OutputType.Comment); 340 | foreach (string tableName in dropTableNames) 341 | { 342 | Utils.AppendOutputText(string.Format("生成删除{0}表的SQL\n", tableName), OutputType.Comment); 343 | if (AppValues.AllTableCompareRule.ContainsKey(tableName) && AppValues.AllTableCompareRule[tableName].CompareWay == TableCompareWays.Ignore) 344 | { 345 | Utils.AppendOutputText("该表格配置为忽略比较,故不进行删除\n", OutputType.Warning); 346 | continue; 347 | } 348 | string dropTableSql = _GetDropTableSql(AppValues.OldSchemaName, tableName); 349 | Utils.AppendOutputText(dropTableSql, OutputType.Sql); 350 | Utils.AppendOutputText("\n", OutputType.None); 351 | } 352 | } 353 | // 找出新版本中新增的表 354 | List addTableNames = new List(); 355 | foreach (string tableName in AppValues.NewExistTableNames) 356 | { 357 | if (!AppValues.OldExistTableNames.Contains(tableName)) 358 | addTableNames.Add(tableName); 359 | } 360 | if (addTableNames.Count > 0) 361 | { 362 | Utils.AppendOutputText(string.Format("新版本数据库中新增以下表格:{0}\n", Utils.CombineString(addTableNames, ",")), OutputType.Comment); 363 | foreach (string tableName in addTableNames) 364 | { 365 | Utils.AppendOutputText(string.Format("生成创建{0}表及填充数据的SQL\n", tableName), OutputType.Comment); 366 | if (AppValues.AllTableCompareRule.ContainsKey(tableName) && AppValues.AllTableCompareRule[tableName].CompareWay == TableCompareWays.Ignore) 367 | { 368 | Utils.AppendOutputText("该表格配置为忽略比较,故不进行新建\n", OutputType.Warning); 369 | continue; 370 | } 371 | // 通过MySQL提供的功能得到建表SQL 372 | string createTableSql = _GetCreateTableSql(AppValues.NewSchemaName, tableName, AppValues.OldSchemaName, AppValues.NewConn); 373 | Utils.AppendOutputText(createTableSql, OutputType.Sql); 374 | Utils.AppendOutputText("\n", OutputType.None); 375 | // 得到填充数据的SQL 376 | DataTable data = _SelectData(AppValues.NewSchemaName, tableName, "*", AppValues.NewConn); 377 | string fillDataSql = _GetFillDataSql(AppValues.NewTableInfo[tableName], data, AppValues.OldSchemaName); 378 | if (!string.IsNullOrEmpty(fillDataSql)) 379 | { 380 | Utils.AppendOutputText(fillDataSql, OutputType.Sql); 381 | Utils.AppendOutputText("\n", OutputType.None); 382 | } 383 | } 384 | } 385 | // 对两版本中均存在的表格进行对比 386 | foreach (string tableName in AppValues.NewExistTableNames) 387 | { 388 | if (AppValues.OldExistTableNames.Contains(tableName)) 389 | { 390 | Utils.AppendOutputText(string.Format("开始对比{0}表\n", tableName), OutputType.Comment); 391 | TableInfo newTableInfo = AppValues.NewTableInfo[tableName]; 392 | TableInfo oldTableInfo = AppValues.OldTableInfo[tableName]; 393 | 394 | TableCompareRule compareRule = null; 395 | if (AppValues.AllTableCompareRule.ContainsKey(tableName)) 396 | { 397 | compareRule = AppValues.AllTableCompareRule[tableName]; 398 | using (StringReader reader = new StringReader(compareRule.GetCompareRuleComment())) 399 | { 400 | string line = null; 401 | while ((line = reader.ReadLine()) != null) 402 | { 403 | Utils.AppendOutputText(line, OutputType.Comment); 404 | Utils.AppendOutputText("\n", OutputType.None); 405 | } 406 | } 407 | } 408 | else 409 | { 410 | compareRule = new TableCompareRule(); 411 | compareRule.CompareWay = TableCompareWays.ColumnInfoAndData; 412 | Utils.AppendOutputText("未设置对该表格进行对比的方式,将默认对比表结构及数据\n", OutputType.Warning); 413 | } 414 | 415 | // 进行表结构比较 416 | const string SPLIT_STRING = ",\n"; 417 | bool isPrimaryKeySame = true; 418 | if (compareRule.CompareWay != TableCompareWays.Ignore) 419 | { 420 | Utils.AppendOutputText("开始进行结构对比\n", OutputType.Comment); 421 | // 修改表结构的SQL开头 422 | string alterTableSqlPrefix = string.Format(_ALTER_TABLE_SQL, _CombineDatabaseTableFullName(AppValues.OldSchemaName, tableName)); 423 | // 标识是否输出过修改表结构的SQL开头 424 | bool hasOutputPrefix = false; 425 | // 标识是否输出过该对比部分中的第一条SQL,非第一条输出前先加逗号并换行 426 | bool hasOutputPartFirstSql = false; 427 | 428 | // 找出删除列 429 | List dropColumnNames = new List(); 430 | foreach (string columnName in oldTableInfo.AllColumnInfo.Keys) 431 | { 432 | if (!newTableInfo.AllColumnInfo.ContainsKey(columnName)) 433 | dropColumnNames.Add(columnName); 434 | } 435 | if (dropColumnNames.Count > 0) 436 | { 437 | if (hasOutputPrefix == false) 438 | { 439 | Utils.AppendOutputText(alterTableSqlPrefix, OutputType.Sql); 440 | Utils.AppendOutputText("\n", OutputType.None); 441 | hasOutputPrefix = true; 442 | } 443 | // 如果之前对比出差异并进行了输出,需要先为上一条语句添加逗号结尾 444 | if (hasOutputPartFirstSql == true) 445 | { 446 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 447 | hasOutputPartFirstSql = false; 448 | } 449 | Utils.AppendOutputText(string.Format("新版本中删除以下列:{0}\n", Utils.CombineString(dropColumnNames, ",")), OutputType.Comment); 450 | foreach (string columnName in dropColumnNames) 451 | { 452 | if (hasOutputPartFirstSql == false) 453 | hasOutputPartFirstSql = true; 454 | else 455 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 456 | 457 | string dropColumnSql = string.Format(_DROP_COLUMN_SQL, columnName); 458 | Utils.AppendOutputText(dropColumnSql, OutputType.Sql); 459 | } 460 | } 461 | 462 | // 找出新增列 463 | List addColumnNames = new List(); 464 | foreach (string columnName in newTableInfo.AllColumnInfo.Keys) 465 | { 466 | if (!oldTableInfo.AllColumnInfo.ContainsKey(columnName)) 467 | addColumnNames.Add(columnName); 468 | } 469 | string addColumnNameString = Utils.CombineString(addColumnNames, ","); 470 | if (addColumnNames.Count > 0) 471 | { 472 | if (hasOutputPrefix == false) 473 | { 474 | Utils.AppendOutputText(alterTableSqlPrefix, OutputType.Sql); 475 | Utils.AppendOutputText("\n", OutputType.None); 476 | hasOutputPrefix = true; 477 | } 478 | if (hasOutputPartFirstSql == true) 479 | { 480 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 481 | hasOutputPartFirstSql = false; 482 | } 483 | Utils.AppendOutputText(string.Format("新版本中新增以下列:{0}\n", Utils.CombineString(addColumnNames, ",")), OutputType.Comment); 484 | foreach (string columnName in addColumnNames) 485 | { 486 | if (hasOutputPartFirstSql == false) 487 | hasOutputPartFirstSql = true; 488 | else 489 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 490 | 491 | // 根据新增列属性生成添加新列的SQL 492 | ColumnInfo columnInfo = newTableInfo.AllColumnInfo[columnName]; 493 | string notEmptyString = columnInfo.IsNotEmpty == true ? "NOT NULL" : "NULL"; 494 | // 注意如果列设为NOT NULL,就不允许设置默认值为NULL 495 | string defaultValue = columnInfo.DefaultValue.Equals("NULL") ? string.Empty : string.Concat(" DEFAULT ", columnInfo.DefaultValue); 496 | string addColumnSql = string.Format(_ADD_COLUMN_SQL, columnName, columnInfo.DataType, notEmptyString, defaultValue, columnInfo.Comment); 497 | Utils.AppendOutputText(addColumnSql, OutputType.Sql); 498 | } 499 | } 500 | 501 | // 在改变列属性前需先同步索引设置,因为自增属性仅可用于设置了索引的列 502 | // 找出主键修改 503 | isPrimaryKeySame = true; 504 | if (newTableInfo.PrimaryKeyColumnNames.Count != oldTableInfo.PrimaryKeyColumnNames.Count) 505 | isPrimaryKeySame = false; 506 | else 507 | { 508 | foreach (string primaryKey in newTableInfo.PrimaryKeyColumnNames) 509 | { 510 | if (!oldTableInfo.PrimaryKeyColumnNames.Contains(primaryKey)) 511 | { 512 | isPrimaryKeySame = false; 513 | break; 514 | } 515 | } 516 | } 517 | if (isPrimaryKeySame == false) 518 | { 519 | if (hasOutputPrefix == false) 520 | { 521 | Utils.AppendOutputText(alterTableSqlPrefix, OutputType.Sql); 522 | Utils.AppendOutputText("\n", OutputType.None); 523 | hasOutputPrefix = true; 524 | } 525 | if (hasOutputPartFirstSql == true) 526 | { 527 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 528 | hasOutputPartFirstSql = false; 529 | } 530 | string newPrimaryKeyString = newTableInfo.PrimaryKeyColumnNames.Count > 0 ? Utils.CombineString(newTableInfo.PrimaryKeyColumnNames, ",") : "无"; 531 | string oldPrimaryKeyString = oldTableInfo.PrimaryKeyColumnNames.Count > 0 ? Utils.CombineString(oldTableInfo.PrimaryKeyColumnNames, ",") : "无"; 532 | Utils.AppendOutputText(string.Format("新版本中主键为:{0},而旧版本中为:{1}\n", newPrimaryKeyString, oldPrimaryKeyString), OutputType.Comment); 533 | // 先删除原有的主键设置 534 | Utils.AppendOutputText(_DROP_PRIMARY_KEY_SQL, OutputType.Sql); 535 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 536 | // 再重新设置 537 | List primaryKeyDefine = new List(); 538 | foreach (string primaryKey in newTableInfo.PrimaryKeyColumnNames) 539 | primaryKeyDefine.Add(string.Format("`{0}`", primaryKey)); 540 | 541 | string addPrimaryKeySql = string.Format(_ADD_PRIMARY_KEY_SQL, Utils.CombineString(primaryKeyDefine, ",")); 542 | Utils.AppendOutputText(addPrimaryKeySql, OutputType.Sql); 543 | hasOutputPartFirstSql = true; 544 | } 545 | 546 | // 找出唯一索引修改 547 | // 找出新版本中删除的索引 548 | List dropIndexNames = new List(); 549 | foreach (string name in oldTableInfo.IndexInfo.Keys) 550 | { 551 | if (!newTableInfo.IndexInfo.ContainsKey(name)) 552 | dropIndexNames.Add(name); 553 | } 554 | if (dropIndexNames.Count > 0) 555 | { 556 | if (hasOutputPrefix == false) 557 | { 558 | Utils.AppendOutputText(alterTableSqlPrefix, OutputType.Sql); 559 | Utils.AppendOutputText("\n", OutputType.None); 560 | hasOutputPrefix = true; 561 | } 562 | if (hasOutputPartFirstSql == true) 563 | { 564 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 565 | hasOutputPartFirstSql = false; 566 | } 567 | Utils.AppendOutputText(string.Format("新版本中删除以下索引:{0}\n", Utils.CombineString(dropIndexNames, ",")), OutputType.Comment); 568 | foreach (string name in dropIndexNames) 569 | { 570 | if (hasOutputPartFirstSql == false) 571 | hasOutputPartFirstSql = true; 572 | else 573 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 574 | 575 | string dropIndexSql = string.Format(_DROP_INDEX_SQL, name); 576 | Utils.AppendOutputText(dropIndexSql, OutputType.Sql); 577 | } 578 | } 579 | // 找出新版本中新增索引 580 | List addIndexNames = new List(); 581 | foreach (string name in newTableInfo.IndexInfo.Keys) 582 | { 583 | if (!oldTableInfo.IndexInfo.ContainsKey(name)) 584 | addIndexNames.Add(name); 585 | } 586 | if (addIndexNames.Count > 0) 587 | { 588 | if (hasOutputPrefix == false) 589 | { 590 | Utils.AppendOutputText(alterTableSqlPrefix, OutputType.Sql); 591 | Utils.AppendOutputText("\n", OutputType.None); 592 | hasOutputPrefix = true; 593 | } 594 | if (hasOutputPartFirstSql == true) 595 | { 596 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 597 | hasOutputPartFirstSql = false; 598 | } 599 | Utils.AppendOutputText(string.Format("新版本中新增以下索引:{0}\n", Utils.CombineString(addIndexNames, ",")), OutputType.Comment); 600 | foreach (string name in addIndexNames) 601 | { 602 | if (hasOutputPartFirstSql == false) 603 | hasOutputPartFirstSql = true; 604 | else 605 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 606 | 607 | // 根据新增索引属性生成添加新索引的SQL 608 | // 注意列名后必须声明排序方式,MySQL只支持索引的升序排列 609 | List columnDefine = new List(); 610 | foreach (string columnName in newTableInfo.IndexInfo[name]) 611 | columnDefine.Add(string.Format("`{0}` ASC", columnName)); 612 | 613 | string addIndexSql = string.Format(_ADD_UNIQUE_INDEX_SQL, name, Utils.CombineString(columnDefine, ",")); 614 | Utils.AppendOutputText(addIndexSql, OutputType.Sql); 615 | } 616 | } 617 | // 找出同名索引的变动 618 | foreach (var pair in newTableInfo.IndexInfo) 619 | { 620 | string name = pair.Key; 621 | if (oldTableInfo.IndexInfo.ContainsKey(name)) 622 | { 623 | List newIndexColumnInfo = pair.Value; 624 | List oldIndexColumnInfo = oldTableInfo.IndexInfo[name]; 625 | bool isIndexColumnSame = true; 626 | if (newIndexColumnInfo.Count != oldIndexColumnInfo.Count) 627 | isIndexColumnSame = false; 628 | else 629 | { 630 | int count = newIndexColumnInfo.Count; 631 | for (int i = 0; i < count; ++i) 632 | { 633 | if (!newIndexColumnInfo[i].Equals(oldIndexColumnInfo[i])) 634 | { 635 | isIndexColumnSame = false; 636 | break; 637 | } 638 | } 639 | } 640 | 641 | if (isIndexColumnSame == false) 642 | { 643 | if (hasOutputPrefix == false) 644 | { 645 | Utils.AppendOutputText(alterTableSqlPrefix, OutputType.Sql); 646 | Utils.AppendOutputText("\n", OutputType.None); 647 | hasOutputPrefix = true; 648 | } 649 | if (hasOutputPartFirstSql == true) 650 | { 651 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 652 | hasOutputPartFirstSql = false; 653 | } 654 | Utils.AppendOutputText(string.Format("新版本中名为{0}的索引,涉及的列名为{1},而旧版本中为{2}\n", name, Utils.CombineString(newIndexColumnInfo, ","), Utils.CombineString(oldIndexColumnInfo, ",")), OutputType.Comment); 655 | // 先删除 656 | string dropIndexSql = string.Format(_DROP_INDEX_SQL, name); 657 | Utils.AppendOutputText(dropIndexSql, OutputType.Sql); 658 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 659 | // 再重新创建 660 | List columnDefine = new List(); 661 | foreach (string columnName in newIndexColumnInfo) 662 | columnDefine.Add(string.Format("`{0}` ASC", columnName)); 663 | 664 | string addIndexSql = string.Format(_ADD_UNIQUE_INDEX_SQL, name, Utils.CombineString(columnDefine, ",")); 665 | Utils.AppendOutputText(addIndexSql, OutputType.Sql); 666 | hasOutputPartFirstSql = true; 667 | } 668 | } 669 | } 670 | 671 | // 找出列属性修改 672 | foreach (string columnName in newTableInfo.AllColumnInfo.Keys) 673 | { 674 | if (oldTableInfo.AllColumnInfo.ContainsKey(columnName)) 675 | { 676 | ColumnInfo newColumnInfo = newTableInfo.AllColumnInfo[columnName]; 677 | ColumnInfo oldColumnInfo = oldTableInfo.AllColumnInfo[columnName]; 678 | // 比较各个属性 679 | bool isDataTypeSame = newColumnInfo.DataType.Equals(oldColumnInfo.DataType); 680 | bool isCommentSame = newColumnInfo.Comment.Equals(oldColumnInfo.Comment); 681 | bool isNotEmptySame = newColumnInfo.IsNotEmpty == oldColumnInfo.IsNotEmpty; 682 | bool isAutoIncrementSame = newColumnInfo.IsAutoIncrement == oldColumnInfo.IsAutoIncrement; 683 | bool isDefaultValueSame = newColumnInfo.DefaultValue.Equals(oldColumnInfo.DefaultValue); 684 | if (isDataTypeSame == false || isCommentSame == false || isNotEmptySame == false || isAutoIncrementSame == false || isDefaultValueSame == false) 685 | { 686 | if (hasOutputPrefix == false) 687 | { 688 | Utils.AppendOutputText(alterTableSqlPrefix, OutputType.Sql); 689 | Utils.AppendOutputText("\n", OutputType.None); 690 | hasOutputPrefix = true; 691 | } 692 | if (hasOutputPartFirstSql == true) 693 | { 694 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 695 | hasOutputPartFirstSql = false; 696 | } 697 | Utils.AppendOutputText(string.Format("列{0}中存在以下属性不同:\n", columnName), OutputType.Comment); 698 | if (isDataTypeSame == false) 699 | Utils.AppendOutputText(string.Format("新版本中数据类型为{0},而旧版本中为{1}\n", newColumnInfo.DataType, oldColumnInfo.DataType), OutputType.Comment); 700 | if (isCommentSame == false) 701 | Utils.AppendOutputText(string.Format("新版本中列注释为\"{0}\",而旧版本中为\"{1}\"\n", newColumnInfo.Comment, oldColumnInfo.Comment), OutputType.Comment); 702 | if (isNotEmptySame == false) 703 | Utils.AppendOutputText(string.Format("新版本中数据{0}为空,而旧版本中{1}\n", newColumnInfo.IsNotEmpty == true ? "不允许" : "允许", oldColumnInfo.IsNotEmpty == true ? "不允许" : "允许"), OutputType.Comment); 704 | if (isAutoIncrementSame == false) 705 | Utils.AppendOutputText(string.Format("新版本中列设为{0},而旧版本中为{1}\n", newColumnInfo.IsAutoIncrement == true ? "自增" : "不自增", oldColumnInfo.IsAutoIncrement == true ? "自增" : "不自增"), OutputType.Comment); 706 | if (isDefaultValueSame == false) 707 | Utils.AppendOutputText(string.Format("新版本中默认值为{0},而旧版本中为{1}\n", newColumnInfo.DefaultValue, oldColumnInfo.DefaultValue), OutputType.Comment); 708 | 709 | // 根据新的列属性进行修改 710 | string notEmptyString = newColumnInfo.IsNotEmpty == true ? "NOT NULL" : "NULL"; 711 | string defaultValue = newColumnInfo.DefaultValue.Equals("NULL") ? string.Empty : string.Concat(" DEFAULT ", newColumnInfo.DefaultValue); 712 | string changeColumnSql = string.Format(_CHANGE_COLUMN_SQL, columnName, newColumnInfo.DataType, notEmptyString, defaultValue, newColumnInfo.Comment); 713 | Utils.AppendOutputText(changeColumnSql, OutputType.Sql); 714 | hasOutputPartFirstSql = true; 715 | } 716 | } 717 | } 718 | 719 | // 对比表校对集 720 | if (!newTableInfo.Collation.Equals(oldTableInfo.Collation)) 721 | { 722 | if (hasOutputPrefix == false) 723 | { 724 | Utils.AppendOutputText(alterTableSqlPrefix, OutputType.Sql); 725 | Utils.AppendOutputText("\n", OutputType.None); 726 | hasOutputPrefix = true; 727 | } 728 | if (hasOutputPartFirstSql == true) 729 | { 730 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 731 | hasOutputPartFirstSql = false; 732 | } 733 | Utils.AppendOutputText(string.Format("新版本中该表格校对集为:\"{0}\",而旧版本中为\"{1}\"\n", newTableInfo.Collation, oldTableInfo.Collation), OutputType.Comment); 734 | string alterTableComment = string.Format(_ALTER_TABLE_COLLATION_SQL, newTableInfo.Collation); 735 | Utils.AppendOutputText(alterTableComment, OutputType.Sql); 736 | hasOutputPartFirstSql = true; 737 | } 738 | 739 | // 对比表注释 740 | if (!newTableInfo.Comment.Equals(oldTableInfo.Comment)) 741 | { 742 | if (hasOutputPrefix == false) 743 | { 744 | Utils.AppendOutputText(alterTableSqlPrefix, OutputType.Sql); 745 | Utils.AppendOutputText("\n", OutputType.None); 746 | hasOutputPrefix = true; 747 | } 748 | if (hasOutputPartFirstSql == true) 749 | { 750 | Utils.AppendOutputText(SPLIT_STRING, OutputType.Sql); 751 | hasOutputPartFirstSql = false; 752 | } 753 | Utils.AppendOutputText(string.Format("新版本中该表格注释为:\"{0}\",而旧版本中为\"{1}\"\n", newTableInfo.Comment, oldTableInfo.Comment), OutputType.Comment); 754 | string alterTableComment = string.Format(_ALTER_TABLE_COMMENT_SQL, newTableInfo.Comment); 755 | Utils.AppendOutputText(alterTableComment, OutputType.Sql); 756 | hasOutputPartFirstSql = true; 757 | } 758 | 759 | // 最后添加分号结束 760 | if (hasOutputPartFirstSql == true) 761 | { 762 | Utils.AppendOutputText(";\n", OutputType.Sql); 763 | hasOutputPrefix = false; 764 | hasOutputPartFirstSql = false; 765 | } 766 | 767 | // 进行表数据比较 768 | if (compareRule.CompareWay == TableCompareWays.ColumnInfoAndData) 769 | { 770 | Utils.AppendOutputText("开始进行数据对比\n", OutputType.Comment); 771 | 772 | // 检查表格是否设置了主键,本工具生成的同步数据的SQL需要通过主键确定数据行 773 | if (newTableInfo.PrimaryKeyColumnNames.Count == 0) 774 | { 775 | string tips = string.Format("错误:表格\"{0}\"未设置主键,本工具无法通过主键生成定位并更新数据的SQL,请设置主键后重试\n本次操作被迫中止\n", tableName); 776 | Utils.AppendOutputText(tips, OutputType.Error); 777 | errorStringBuilder.Append(tips); 778 | errorString = errorStringBuilder.ToString(); 779 | return; 780 | } 781 | // 检查用户设置的对比配置,不允许将主键列设为数据比较时忽略的列 782 | if (compareRule.CompareIgnoreColumn.Count > 0) 783 | { 784 | foreach (string primaryKeyColumnName in newTableInfo.PrimaryKeyColumnNames) 785 | { 786 | if (compareRule.CompareIgnoreColumn.Contains(primaryKeyColumnName)) 787 | { 788 | string tips = string.Format("\n错误:对比数据时不允许将表格主键列设为忽略,而您的配置声明对表格\"{0}\"的主键列\"{1}\"进行忽略,请修正配置后重试\n本次操作被迫中止\n", tableName, primaryKeyColumnName); 789 | Utils.AppendOutputText(tips, OutputType.Error); 790 | errorStringBuilder.Append(tips); 791 | errorString = errorStringBuilder.ToString(); 792 | return; 793 | } 794 | } 795 | } 796 | // 如果新旧版本中的主键设置不同,无法进行数据对比 797 | if (isPrimaryKeySame == false) 798 | { 799 | string tips = string.Format("新旧两版本表格\"{0}\"的主键设置不同,本工具目前无法在此情况下进行数据比较并生成同步SQL,请先通过执行上面生成的同步数据库表结构SQL,使得旧版表格和新版为相同的主键设置后,再次运行本工具进行数据比较及同步\n", tableName); 800 | Utils.AppendOutputText(tips, OutputType.Error); 801 | errorStringBuilder.Append(tips); 802 | continue; 803 | } 804 | 805 | DataTable newData = _SelectData(AppValues.NewSchemaName, tableName, "*", AppValues.NewConn); 806 | DataTable oldData = _SelectData(AppValues.OldSchemaName, tableName, "*", AppValues.OldConn); 807 | Dictionary newDataInfo = _GetDataInfoByPrimaryKey(newData, newTableInfo.PrimaryKeyColumnNames); 808 | Dictionary oldDataInfo = _GetDataInfoByPrimaryKey(oldData, newTableInfo.PrimaryKeyColumnNames); 809 | 810 | // 找出删除的数据 811 | foreach (var pair in oldDataInfo) 812 | { 813 | string primaryKeyValueString = pair.Key; 814 | int index = pair.Value; 815 | if (!newDataInfo.ContainsKey(primaryKeyValueString)) 816 | { 817 | DataRow dataRow = oldData.Rows[index]; 818 | string primaryKeyColumnNameAndValueString = _GetColumnNameAndValueString(dataRow, newTableInfo.PrimaryKeyColumnNames, " AND "); 819 | Utils.AppendOutputText(string.Concat("新版本中删除了主键列为以下值的一行:", primaryKeyColumnNameAndValueString, "\n"), OutputType.Comment); 820 | // 判断该行数据是否被设为忽略 821 | //if (_IsIgnoreData(compareRule.CompareIgnoreData, dataRow) == true) 822 | // Utils.AppendOutputText("该行符合配置的需忽略的数据行,故不进行删除\n", OutputType.Warning); 823 | //else 824 | //{ 825 | string dropDataSql = string.Format(_DROP_DATA_SQL, _CombineDatabaseTableFullName(AppValues.OldSchemaName, tableName), _GetColumnNameAndValueString(dataRow, newTableInfo.PrimaryKeyColumnNames, " AND ")); 826 | Utils.AppendOutputText(dropDataSql, OutputType.Sql); 827 | Utils.AppendOutputText("\n", OutputType.None); 828 | //} 829 | } 830 | } 831 | 832 | // 找出需要对比数据的列(以新版表中所有列为基准,排除用户设置的忽略列以及旧版表中不存在的列,因为主键列的值在找新旧两表对应行时已经对比,也无需比较) 833 | List compareColumnNames = new List(); 834 | foreach (string columnName in newTableInfo.AllColumnInfo.Keys) 835 | { 836 | if (oldTableInfo.AllColumnInfo.ContainsKey(columnName) && !newTableInfo.PrimaryKeyColumnNames.Contains(columnName) && !compareRule.CompareIgnoreColumn.Contains(columnName)) 837 | compareColumnNames.Add(columnName); 838 | } 839 | 840 | // 生成新增数据时所有列名组成的定义字符串 841 | List columnDefine = new List(); 842 | foreach (string columnName in newTableInfo.AllColumnInfo.Keys) 843 | columnDefine.Add(string.Format("`{0}`", columnName)); 844 | 845 | string columnDefineString = Utils.CombineString(columnDefine, ", "); 846 | 847 | foreach (var pair in newDataInfo) 848 | { 849 | string primaryKeyValueString = pair.Key; 850 | int newTableIndex = pair.Value; 851 | // 新增数据 852 | if (!oldDataInfo.ContainsKey(primaryKeyValueString)) 853 | { 854 | DataRow dataRow = newData.Rows[newTableIndex]; 855 | string primaryKeyColumnNameAndValueString = _GetColumnNameAndValueString(dataRow, newTableInfo.PrimaryKeyColumnNames, " AND "); 856 | Utils.AppendOutputText(string.Concat("新版本中新增主键列为以下值的一行:", primaryKeyColumnNameAndValueString, "\n"), OutputType.Comment); 857 | //// 判断该行数据是否被设为忽略 858 | //if (_IsIgnoreData(compareRule.CompareIgnoreData, dataRow) == true) 859 | // Utils.AppendOutputText("该行符合配置的需忽略的数据行,故不进行新增\n", OutputType.Warning); 860 | //else 861 | //{ 862 | List values = new List(); 863 | foreach (string columnName in newTableInfo.AllColumnInfo.Keys) 864 | { 865 | object value = dataRow[columnName]; 866 | values.Add(_GetDatabaseValueString(value)); 867 | } 868 | 869 | string valueString = string.Format("({0})", Utils.CombineString(values, ",")); 870 | string insertDataSql = string.Format(_INSERT_DATA_SQL, _CombineDatabaseTableFullName(AppValues.OldSchemaName, tableName), columnDefineString, valueString); 871 | Utils.AppendOutputText(insertDataSql, OutputType.Sql); 872 | Utils.AppendOutputText("\n", OutputType.None); 873 | //} 874 | } 875 | // 判断未被忽略的列中的数据是否相同 876 | else 877 | { 878 | int oldTableIndex = oldDataInfo[primaryKeyValueString]; 879 | DataRow newDataRow = newData.Rows[newTableIndex]; 880 | DataRow oldDataRow = oldData.Rows[oldTableIndex]; 881 | 882 | List dataDiffColumnNames = new List(); 883 | foreach (string columnName in compareColumnNames) 884 | { 885 | string newDataValue = _GetDatabaseValueString(newDataRow[columnName]); 886 | string oldDataValue = _GetDatabaseValueString(oldDataRow[columnName]); 887 | if (!newDataValue.Equals(oldDataValue)) 888 | dataDiffColumnNames.Add(columnName); 889 | } 890 | string primaryKeyColumnNameAndValueString = _GetColumnNameAndValueString(newDataRow, newTableInfo.PrimaryKeyColumnNames, " AND "); 891 | if (dataDiffColumnNames.Count > 0) 892 | { 893 | string newColumnNameAndValueString = _GetColumnNameAndValueString(newDataRow, dataDiffColumnNames, ", "); 894 | string oldColumnNameAndValueString = _GetColumnNameAndValueString(oldDataRow, dataDiffColumnNames, ", "); 895 | Utils.AppendOutputText(string.Format("主键为{0}的行中,新版本中以下数据为{1},而旧版本中为{2}\n", primaryKeyColumnNameAndValueString, newColumnNameAndValueString, oldColumnNameAndValueString), OutputType.Comment); 896 | // 判断该行数据是否被设为忽略 897 | if (_IsIgnoreData(compareRule.CompareIgnoreData, newDataRow) == true) 898 | Utils.AppendOutputText("该行符合配置的需忽略的数据行,故不进行修改\n", OutputType.Warning); 899 | else 900 | { 901 | List values = new List(); 902 | foreach (string columnName in newTableInfo.AllColumnInfo.Keys) 903 | { 904 | object value = newDataRow[columnName]; 905 | values.Add(_GetDatabaseValueString(value)); 906 | } 907 | string valueString = string.Format("({0})", Utils.CombineString(values, ",")); 908 | string updateDataSql = string.Format(_UPDATE_DATA_SQL, _CombineDatabaseTableFullName(AppValues.OldSchemaName, tableName), newColumnNameAndValueString, primaryKeyColumnNameAndValueString); 909 | Utils.AppendOutputText(updateDataSql, OutputType.Sql); 910 | Utils.AppendOutputText("\n", OutputType.None); 911 | } 912 | } 913 | 914 | // 新版表格中新增列的值需要同步到旧表,无视用户是否设置为忽略列 915 | if (addColumnNames.Count > 0) 916 | { 917 | string addColumnNameAndValueString = _GetColumnNameAndValueString(newDataRow, addColumnNames, ", "); 918 | Utils.AppendOutputText(string.Format("为新版本中新增的{0}列填充数据\n", addColumnNameString), OutputType.Comment); 919 | string updateDataSql = string.Format(_UPDATE_DATA_SQL, _CombineDatabaseTableFullName(AppValues.OldSchemaName, tableName), addColumnNameAndValueString, primaryKeyColumnNameAndValueString); 920 | Utils.AppendOutputText(updateDataSql, OutputType.Sql); 921 | Utils.AppendOutputText("\n", OutputType.None); 922 | } 923 | } 924 | } 925 | } 926 | } 927 | } 928 | } 929 | 930 | errorString = errorStringBuilder.ToString(); 931 | } 932 | 933 | /// 934 | /// 将数据整理为以各个主键值拼成key的Dictionary,value为数据在DataTable中的下标 935 | /// 936 | private static Dictionary _GetDataInfoByPrimaryKey(DataTable data, List primaryKeyColumnNames) 937 | { 938 | const string SPLIT_STRING = "_"; 939 | Dictionary info = new Dictionary(); 940 | List tempColumnKeys = new List(); 941 | int count = data.Rows.Count; 942 | for (int i = 0; i < count; ++i) 943 | { 944 | string primaryKeyValueString = _GetPrimaryKeyValueString(data.Rows[i], primaryKeyColumnNames, SPLIT_STRING); 945 | info.Add(primaryKeyValueString, i); 946 | } 947 | 948 | return info; 949 | } 950 | 951 | /// 952 | /// 将某表格一行数据中的主键列值通过指定字符连接 953 | /// 954 | private static string _GetPrimaryKeyValueString(DataRow dataRow, List primaryKeyColumnNames, string splitString) 955 | { 956 | List tempValues = new List(); 957 | foreach (string columnName in primaryKeyColumnNames) 958 | tempValues.Add(dataRow[columnName].ToString()); 959 | 960 | return Utils.CombineString(tempValues, splitString); 961 | } 962 | 963 | /// 964 | /// 返回某表格一行数据中的指定列名及取值,符合WHERE语句要求 965 | /// 966 | private static string _GetColumnNameAndValueString(DataRow dataRow, List columnNames, string splitString) 967 | { 968 | List tempValues = new List(); 969 | foreach (string columnName in columnNames) 970 | { 971 | object value = dataRow[columnName]; 972 | string valueString = _GetDatabaseValueString(value); 973 | 974 | tempValues.Add(string.Format("`{0}`={1}", columnName, valueString)); 975 | } 976 | 977 | return Utils.CombineString(tempValues, splitString); 978 | } 979 | 980 | /// 981 | /// 判断某表格中的一行是否为配置的需要忽略比较的数据 982 | /// 983 | private static bool _IsIgnoreData(List> compareIgnoreData, DataRow dataRow) 984 | { 985 | DataColumnCollection dataColumnCollection = dataRow.Table.Columns; 986 | int count = compareIgnoreData.Count; 987 | for (int i = 0; i < count; ++i) 988 | { 989 | Dictionary oneIgnoreData = compareIgnoreData[i]; 990 | bool isMatch = true; 991 | foreach (string columnName in oneIgnoreData.Keys) 992 | { 993 | if (!dataColumnCollection.Contains(columnName)) 994 | { 995 | isMatch = false; 996 | break; 997 | } 998 | 999 | Regex regex = oneIgnoreData[columnName]; 1000 | object value = dataRow[columnName]; 1001 | string valueString = null; 1002 | if (value.GetType() == typeof(System.Boolean)) 1003 | { 1004 | if ((bool)value == true) 1005 | valueString = "1"; 1006 | else 1007 | valueString = "0"; 1008 | } 1009 | else 1010 | valueString = value.ToString(); 1011 | 1012 | if (!regex.IsMatch(valueString)) 1013 | { 1014 | isMatch = false; 1015 | break; 1016 | } 1017 | } 1018 | 1019 | if (isMatch == true) 1020 | return true; 1021 | } 1022 | 1023 | return false; 1024 | } 1025 | 1026 | /// 1027 | /// 获取数据库中一个数据在SQL语句中的表示形式 1028 | /// 1029 | private static string _GetDatabaseValueString(object value) 1030 | { 1031 | if (value.GetType() == typeof(System.DBNull)) 1032 | return "NULL"; 1033 | else if (value.GetType() == typeof(System.Boolean)) 1034 | { 1035 | if ((bool)value == true) 1036 | return "\"1\""; 1037 | else 1038 | return "\"0\""; 1039 | } 1040 | // MySQL中string类型的空字符串,若用单引号包裹则认为是NULL,用双引号包裹才认为是空字符串。还要注意转义数据中的引号 1041 | else 1042 | return string.Concat("\"", value.ToString().Replace("\"", "\\\""), "\""); 1043 | } 1044 | 1045 | /// 1046 | /// 将数据库的表名连同Schema名组成形如'schemaName'.'tableName'的字符串 1047 | /// 1048 | private static string _CombineDatabaseTableFullName(string schemaName, string tableName) 1049 | { 1050 | return string.Format("`{0}`.`{1}`", schemaName, tableName); 1051 | } 1052 | } 1053 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/MySql.Data.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-ulua/ServerDatabaseVersionUpdateHelper/e295eef1874782cdac8ac39261d857409959e28f/ServerDatabaseVersionUpdateHelper/MySql.Data.dll -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows.Forms; 4 | 5 | namespace ServerDatabaseVersionUpdateHelper 6 | { 7 | static class Program 8 | { 9 | /// 10 | /// 应用程序的主入口点。 11 | /// 12 | [STAThread] 13 | static void Main() 14 | { 15 | Application.EnableVisualStyles(); 16 | Application.SetCompatibleTextRenderingDefault(false); 17 | Application.Run(new MainForm()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的常规信息通过以下 6 | // 特性集控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("ServerDatabaseVersionUpdateHelper")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("张齐")] 12 | [assembly: AssemblyProduct("")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // 将 ComVisible 设置为 false 使此程序集中的类型 18 | // 对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | // 则将该类型上的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("7a47e936-bd54-4e26-915f-9290c5178b53")] 24 | 25 | // 程序集的版本信息由下面四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | // 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本: 4.0.30319.18408 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ServerDatabaseVersionUpdateHelper.Properties 12 | { 13 | 14 | 15 | /// 16 | /// 一个强类型的资源类,用于查找本地化的字符串等。 17 | /// 18 | // 此类是由 StronglyTypedResourceBuilder 19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen 21 | // (以 /str 作为命令选项),或重新生成 VS 项目。 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// 返回此类使用的、缓存的 ResourceManager 实例。 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ServerDatabaseVersionUpdateHelper.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// 为所有资源查找重写当前线程的 CurrentUICulture 属性, 56 | /// 方法是使用此强类型资源类。 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18408 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ServerDatabaseVersionUpdateHelper.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/ServerDatabaseVersionUpdateHelper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AE451B08-7834-4008-9A7B-2F3F0A1774BE} 8 | WinExe 9 | Properties 10 | ServerDatabaseVersionUpdateHelper 11 | ServerDatabaseVersionUpdateHelper 12 | v2.0 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | False 37 | .\MySql.Data.dll 38 | True 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Form 52 | 53 | 54 | MainForm.cs 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | MainForm.cs 64 | Designer 65 | 66 | 67 | ResXFileCodeGenerator 68 | Resources.Designer.cs 69 | Designer 70 | 71 | 72 | True 73 | Resources.resx 74 | 75 | 76 | SettingsSingleFileGenerator 77 | Settings.Designer.cs 78 | 79 | 80 | True 81 | Settings.settings 82 | True 83 | 84 | 85 | 86 | 93 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/ServerDatabaseVersionUpdateHelper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.21005.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerDatabaseVersionUpdateHelper", "ServerDatabaseVersionUpdateHelper.csproj", "{AE451B08-7834-4008-9A7B-2F3F0A1774BE}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {AE451B08-7834-4008-9A7B-2F3F0A1774BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {AE451B08-7834-4008-9A7B-2F3F0A1774BE}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {AE451B08-7834-4008-9A7B-2F3F0A1774BE}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {AE451B08-7834-4008-9A7B-2F3F0A1774BE}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/ServerDatabaseVersionUpdateHelper.v12.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-ulua/ServerDatabaseVersionUpdateHelper/e295eef1874782cdac8ac39261d857409959e28f/ServerDatabaseVersionUpdateHelper/ServerDatabaseVersionUpdateHelper.v12.suo -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/TableCompareRule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using System.Text.RegularExpressions; 4 | 5 | /// 6 | /// 对数据库中一张表格进行对比的规则 7 | /// 8 | public class TableCompareRule 9 | { 10 | // 表名 11 | public string TableName { get; set; } 12 | // 比较方式 13 | public TableCompareWays CompareWay { get; set; } 14 | // 如果进行数据对比,忽略对哪些列下的数据比较(存储列名) 15 | public List CompareIgnoreColumn { get; set; } 16 | // 如果进行数据对比,忽略对特定列中特定取值的行进行比较(每个Dictionary配置一条忽略项,key为列名,value为需满足的正则表达式,每个Dictionary中所有条件要求同时满足) 17 | public List> CompareIgnoreData { get; set; } 18 | 19 | public TableCompareRule() 20 | { 21 | // 注意在C#中未对枚举显式赋值时默认值为0 22 | CompareWay = TableCompareWays.UnDefine; 23 | CompareIgnoreColumn = new List(); 24 | CompareIgnoreData = new List>(); 25 | } 26 | 27 | // 返回该表格对比规则的说明 28 | public string GetCompareRuleComment() 29 | { 30 | StringBuilder stringBuilder = new StringBuilder(); 31 | string compareWayString = null; 32 | if (CompareWay == TableCompareWays.Ignore) 33 | compareWayString = "忽略比较"; 34 | else if (CompareWay == TableCompareWays.OnlyColumnInfo) 35 | compareWayString = "仅比较表结构"; 36 | else if (CompareWay == TableCompareWays.ColumnInfoAndData) 37 | compareWayString = "比较表结构和数据"; 38 | 39 | stringBuilder.Append("该表格比较方式为:").Append(compareWayString).AppendLine(); 40 | 41 | // 比较数据时,配置需要忽略的列和行 42 | if (CompareWay == TableCompareWays.ColumnInfoAndData) 43 | { 44 | // 忽略的列 45 | if (CompareIgnoreColumn.Count > 0) 46 | stringBuilder.AppendFormat("比较数据时忽略以下列:{0}\n", Utils.CombineString(CompareIgnoreColumn, ",")); 47 | 48 | // 忽略的特定行 49 | if (CompareIgnoreData.Count > 0) 50 | { 51 | stringBuilder.AppendLine("忽略满足下列条件的特定行:"); 52 | const string SPLIT_STRING = " && "; 53 | for (int i = 0; i < CompareIgnoreData.Count; ++i) 54 | { 55 | Dictionary oneIgnoreData = CompareIgnoreData[i]; 56 | stringBuilder.Append(i + 1).Append("."); 57 | foreach (var pair in oneIgnoreData) 58 | { 59 | stringBuilder.AppendFormat("列\"{0}\"的值满足\"{1}\"", pair.Key, pair.Value.ToString()); 60 | stringBuilder.Append(SPLIT_STRING); 61 | } 62 | // 去掉最后多余的连接字符串 63 | stringBuilder.Remove(stringBuilder.Length - SPLIT_STRING.Length, SPLIT_STRING.Length); 64 | stringBuilder.AppendLine(); 65 | } 66 | } 67 | } 68 | 69 | return stringBuilder.ToString(); 70 | } 71 | } 72 | 73 | /// 74 | /// 对数据库中一张表格进行对比的方式 75 | /// 76 | public enum TableCompareWays 77 | { 78 | // 未声明 79 | UnDefine = -1, 80 | // 忽略,不进行比较 81 | Ignore, 82 | // 仅比较表结构 83 | OnlyColumnInfo, 84 | // 比较结构及数据 85 | ColumnInfoAndData, 86 | } 87 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/TableInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | /// 4 | /// 一张数据库表格的信息 5 | /// 6 | public class TableInfo 7 | { 8 | // Schema名 9 | public string SchemaName { get; set; } 10 | // 表名 11 | public string TableName { get; set; } 12 | // 注释 13 | public string Comment { get; set; } 14 | // 校对集 15 | public string Collation { get; set; } 16 | // 所有列信息(key:列名, value:列信息) 17 | public Dictionary AllColumnInfo { get; set; } 18 | // 主键列的列名 19 | public List PrimaryKeyColumnNames { get; set; } 20 | // 索引设置(key:索引名, value:按顺序排列的列名) 21 | public Dictionary> IndexInfo { get; set; } 22 | 23 | public TableInfo() 24 | { 25 | AllColumnInfo = new Dictionary(); 26 | PrimaryKeyColumnNames = new List(); 27 | IndexInfo = new Dictionary>(); 28 | } 29 | } 30 | 31 | /// 32 | /// 数据库表格中一列的信息 33 | /// 34 | public class ColumnInfo 35 | { 36 | // 表名 37 | public string TableName { get; set; } 38 | // 列名 39 | public string ColumnName { get; set; } 40 | // 数据类型(包含长度) 41 | public string DataType { get; set; } 42 | // 注释 43 | public string Comment { get; set; } 44 | // 是否是主键 45 | public bool IsPrimaryKey { get; set; } 46 | // 是否唯一 47 | public bool IsUnique { get; set; } 48 | // 是否为索引列但允许重复值 49 | public bool IsMultiple { get; set; } 50 | // 是否非空 51 | public bool IsNotEmpty { get; set; } 52 | // 是否自增 53 | public bool IsAutoIncrement { get; set; } 54 | // 默认值 55 | public string DefaultValue { get; set; } 56 | } 57 | -------------------------------------------------------------------------------- /ServerDatabaseVersionUpdateHelper/Utils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Drawing; 3 | using System.Text; 4 | using System.Windows.Forms; 5 | 6 | public class Utils 7 | { 8 | // 用于输出结果的富文本框 9 | public static RichTextBox RtxResult = null; 10 | // 上一次在富文本框中输出的文字颜色 11 | private static Color _lastTextColor = Color.Black; 12 | 13 | public static void AppendOutputText(string text, OutputType type) 14 | { 15 | Color color; 16 | if (type == OutputType.Comment) 17 | color = Color.DarkGray; 18 | else if (type == OutputType.Warning) 19 | color = Color.Orange; 20 | else if (type == OutputType.Error) 21 | color = Color.Red; 22 | else if (type == OutputType.Sql) 23 | color = Color.Black; 24 | else 25 | color = _lastTextColor; 26 | 27 | RtxResult.SelectionColor = color; 28 | _lastTextColor = color; 29 | 30 | if (type != OutputType.Sql && type != OutputType.None) 31 | RtxResult.AppendText("-- "); 32 | 33 | RtxResult.AppendText(text); 34 | RtxResult.Focus(); 35 | } 36 | 37 | /// 38 | /// 将List中的所有数据用指定分隔符连接为一个新字符串 39 | /// 40 | public static string CombineString(IList list, string separateString) 41 | { 42 | if (list == null || list.Count < 1) 43 | return null; 44 | else 45 | { 46 | StringBuilder builder = new StringBuilder(); 47 | for (int i = 0; i < list.Count; ++i) 48 | builder.Append(list[i]).Append(separateString); 49 | 50 | string result = builder.ToString(); 51 | // 去掉最后多加的一次分隔符 52 | if (separateString != null) 53 | return result.Substring(0, result.Length - separateString.Length); 54 | else 55 | return result; 56 | } 57 | } 58 | } 59 | 60 | public enum OutputType 61 | { 62 | None, 63 | Comment, 64 | Warning, 65 | Error, 66 | Sql, 67 | } 68 | -------------------------------------------------------------------------------- /使用说明.txt: -------------------------------------------------------------------------------- 1 | 本工具可以对比新旧两个版本的服务器软件所用MySQL数据库,并根据配置(可设置对某表格忽略对比、仅比较表结构、比较结构和数据),展示两库差异并生成将旧版本数据库的表结构和数据升级为新版本的SQL 2 | 3 | 配置文件的配置方法,请参看样例配置文件config.txt中的说明 4 | 5 | 使用本工具时需满足以下要求: 6 | 1、每张表格都需设置主键 7 | 2、配置文件中禁止将表格的主键列设为比较数据时忽略的列 8 | 3、目前只能对新旧两表格在主键设置完全相同的情况下进行数据对比,如果不同,只能先生成同步表结构的SQL,由用户执行SQL使得主键设置相同后,再用本工具对比数据 9 | 10 | 使用本工具时可能出现的异常情况: 11 | 因为本工具目前进行表格对比的规则是先进行表结构对比,再进行表数据对比。有可能出现下面的异常情况:比如旧版本某表格的一列原本是允许NULL的,并且已经有很多NULL数据,而新版本中设为不允许NULL,并且将原有的NULL值进行赋值。这种情况下,本工具先生成的同步结构的SQL因为将列直接设为NOT NULL,由于原先列中NULL值的存在,该SQL无法执行 12 | 该问题倒是有解决方案,只需改为在生成同步表结构SQL时,先不要将列设为NULL,等到完成数据同步后,再设置列为NOT NULL。但这样有点破坏原本较清晰的先比结构再比数据的步骤,而且若用户在配置文件中配置忽略数据对比或忽略掉涉及行的对比,仍旧会出现此问题。故暂时不进行此修改,根据大家的反馈情况决定是否修改 13 | 14 | 我的联系方式: 15 | QQ:2246549866 16 | QQ交流群:132108644(XlsxToLua交流群) 17 | GitHub:https://github.com/zhangqi-ulua -------------------------------------------------------------------------------- /软件运行截图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-ulua/ServerDatabaseVersionUpdateHelper/e295eef1874782cdac8ac39261d857409959e28f/软件运行截图.png --------------------------------------------------------------------------------