常用的表格检测识别方法 - 表格区域检测方法(下) - 合合技术团队 - 博客园

mikel阅读(149)

来源: 常用的表格检测识别方法 – 表格区域检测方法(下) – 合合技术团队 – 博客园

Training

半监督网络的训练分两步进行:a)对标记数据独立训练学生模块,由教师模块生成伪标签;b)结合两个模块的训练,得到最终的预测结果。

 

伪标签框架

 

 

实验

 

数据集:

TableBank是文档分析领域中用于表识别问题的第二大数据集。该数据集有417,000个通过arXiv数据库爬虫过程注释。该数据集具有来自三类文档图像的表格:LaTeX图像(253,817)、Word图像(163,417),以及两者的组合(417,234)。它还包括一个用于识别表格的结构的数据集。在论文的实验中,只使用进行表检测的数据。

PubLayNet是一个大型公共数据集,训练集中有335,703张图像,验证集中有11,240张图像,测试集中有11,405张图像。它包括注释,如多边形分割和图形的边界框,列出标题、表格和来自研究论文和文章的图像文本。使用coco分析技术对该数据集进行了评估。在实验中,作者只使用了86,460个表注释中的102,514个。

DocBank是一个包含5000多个带注释的文档图像的大型数据集,旨在训练和评估诸如文本分类、实体识别和关系提取等任务。它包括标题、作者姓名、隶属关系、摘要、正文等方面的注释。

ICDAR-19:表检测和识别(cTDaR)竞赛于2019年由ICDAR组织。对于表格检测任务(TRACKA),在比赛中引入了两个新的数据集(现代和历史数据集)。为了与之前的最先进的方法进行直接比较,实验提供了在IoU阈值范围为0.5-0.9的现代数据集上的结果。

 

实验设置细节:

 

实验使用在ImageNet数据集上预先训练的ResNet-50为主干的可变形DETR作为检测框架,以评估半监督方法的有效性。在PubLayNet、ICDAR-19、DocBank和TableBank的三类数据集上进行训练。实验使用10%、30%和50%的标记数据,其余的作为未标记数据。伪标记的阈值设置为0.7。将所有实验的训练周期设置为150,在第120期的学习率降低了0.1倍。应用强增强作为水平翻转,调整大小,去除斑块,裁剪,灰度和高斯模糊。实验使用水平翻转来应用弱增强。可变形DETR解码器输入的query数的值N被设置为30,因为它能给出最好的结果。除非另有说明,实验都使用mAP(AP50:95)度量来评估结果。

实验结果讨论:

 

TableBank:

实验提供了对不同比例的标签数据的表库数据集的所有分割的实验结果。还比较了基于transformer的半监督方法与以前的基于深度学习的监督和半监督方法。此外,实验给出了10%标记数据的TableBank-both数据集在所有IoU阈值下的结果。表1提供了半监督方法在TableBank-latex, TableBank-word, 和TableBank-both数据集,分别10%、30%和50%标记数据时的实验结果。它表明,在10%标记数据时,TableBank-both数据集的AP50值最高,为95.8%,TableBank-latex为93.5%,TableBank-word有92.5%。

 

 

表格的半监督学习的定性分析如图5所示。图5的(b)部分有一个与行和列结构相似的矩阵,网络将该矩阵检测为一个表格,给出false positive检测结果。在这里,不正确的检测结果表明网络不能提供正确的表格区域检测。表2给出了这种半监督方法对10%标签数据上的所有数据集的不同IoU阈值的结果。在TableBank10%标记数据集上使用不同的ResNet-50骨干的半监督网络的准确率、召回率和f1-score的可视化比较如图6所示。

 

 

 

 

 

与以前的监督方法和半监督方法的比较

 

表3比较了ResNet-50主干上基于深度学习的监督网络和半监督网络。还将在10%、30%和50%TableBank-both数据集标签数据上训练的监督可变形DETR与使用可变形transformer的半监督方法进行了比较。结果表明,基于attention机制的半监督方法使用候选生成过程和后处理步骤,如非最大抑制(NMS),取得了可观的结果。

 

 

 

PubLayNet:

实验讨论了在PubLayNet表类数据集上对不同标记数据百分比的实验结果。还比较了基于transformer的半监督方法与以前的基于深度学习的监督和半监督方法。此外,实验给出了10%标记数据的PubLayNet数据集上的所有IoU阈值的结果。表4提供了半监督方法的结果,该方法对PubLayNet表类数据使用可变形transformer来处理标记数据的不同百分比。在这里,10%、30%和50%的标记数据的AP50值分别为98.5%、98.8%和98.8%

 

 

 

此外,半监督网络在10%的标记的PubLayNet数据集上,在不同的IoU阈值上进行训练。表5给出了半监督方法对10%标记数据上的PubLayNet表类的不同IoU阈值的结果。在PubLayNet表类的10%标记数据集上,在不同的IoU阈值上使用具有ResNet-50主干的可变形transformer网络的半监督网络的准确率、召回率和f1-score的可视化比较如图6(b)所示。这里,蓝色表示不同IoU阈值的准确率结果,红色表示不同IoU阈值的召回结果,绿色表示对不同IoU阈值的f1-score结果。

 

 

 

与以前的监督方法和半监督方法的比较

 

表6比较了使用ResNet-50骨干网的PubLayNet表类上基于深度学习的监督网络和半监督网络。还比较了在10%、30%和50%的PubLayNet表类标签数据上训练的有监督的可变形detr与使用可变形transformer的半监督方法。它表明,半监督方法不使用候选和后处理步骤,如非最大抑制(NMS),提供了有竞争力的结果。

 

 

 

 

DocBank:

 

实验讨论了在DocBank数据集上的不同标签百分比数据的实验结果。在表7中比较了基于transformer的半监督方法与以前的基于cnn的半监督方法。

 

 

 

此外,还比较了表8中对不同比例的标记数据的半监督方法与之前针对不同数据集的表格检测和文档分析方法。虽然不能直接比较作者的半监督方法与以前的监督文档分析方法。然而,可以观察到,即使有50%的标签数据,作者也获得了与以前的监督方法类似的结果。

 

ICDAR-19:

实验还评估了在Modern Track A数据集上的表格检测方法。作者总结了该方法在不同百分比的标签数据下的定量结果,并将其与表9中以前的监督表格检测方法进行了比较。在更高的IoU阈值0.8和0.9下评估结果。为了与以前的表格检测方法进行直接比较,作者还在100%的标签数据上评估了论文的方法。论文方法在100%标签数据的IoU阈值上获得了92.6%的准确率和91.3%的召回率。

 

 

 

 

消融实验:

 

伪标记置信阈值

 

阈值(称为置信阈值)在决定生成的伪标签的准确性和数量之间的平衡方面起着重要的作用。随着这个阈值的增加,通过过滤器的样本将会更少,但它们的质量将会更高。相反,较小的阈值将导致更多的样本通过,但false positive的可能性更高。从0.5到0.9的各种阈值的影响如表10所示。根据计算结果,确定最优阈值为0.7。

 

 

可学习query数量的影响

 

在分析中,作者研究了改变作为可变形DETR解码器中输入的query数量的影响。图7通过改变作为可变形DETR解码器中输入的对象query的数量来比较预测结果。当query数N设置为30时,达到最佳性能;偏离此值会导致性能下降。表11显示并分析了不同对象query数量的结果。为N选择一个较小的值可能会导致模型无法识别特定的对象,从而对其性能产生负面影响。另一方面,选择一个较大的N值可能会导致模型由于过拟合而表现不佳,因为它会错误地将某些区域分类为对象。此外,在师生模块中,该半监督自注意机制的训练复杂度依赖于对象query的数量,并通过最小化对象query的数量来降低复杂度而得到提高。

 

 

 

结论

 

本文介绍了一种利用可变形transformer对文档图像进行表格检测的半监督方法。该方法通过将伪标签生成框架集成到一个简化的机制中,减轻了对大规模注释数据的需要,并简化了该过程。同时生成伪标签产生了一个被称为“飞轮效应”的动态过程,随着训练的进行,一个模型不断改进另一个模型产生的伪边框。在该框架中,使用两个不同的模块学生和教师,对伪类标签和伪边界框进行了改进。这些模块通过EMA功能相互更新,以提供精确的分类和边界框预测。结果表明,当应用于TableBank和PubLayNet训练数据的10%、30%和50%时,该方法的性能超过了监督模型的性能。此外,当对PubLayNet的10%标记数据进行训练时,该模型的性能与当前基于cnn的半监督基线相比较。在未来,作者的目标是研究标记数据的比例对最终性能的影响,并开发出以最小数量的标记数据有效运行的模型。此外,作者还打算采用基于transformer的半监督学习机制来进行表结构识别任务。

 

 

 

 

参考文献:

Gao L C, Li Y B, Du L, Zhang X P, Zhu Z Y, Lu N, Jin L W, Huang Y S, Tang Z . 2022.A survey on table recognition technology. Journal of Image and Graphics, 27(6): 1898-1917.

M Kasem , A Abdallah, A Berendeyev,E Elkady , M Abdalla, M Mahmouda, M Hamada, D Nurseitovd, I Taj-Eddin.Deep learning for table detection and structure recognition: A survey.arXiv:2211.08469v1 [cs.CV] 15 Nov 2022

S A Siddiqui , M I Malik,S Agne , A Dengel and S Ahmed. DeCNT: Deep Deformable CNN for Table Detection. in IEEE Access, vol.6, pp.74151-74161, [DOI: 10.1109/ACCESS.2018.2880211]

T Shehzadi, K A Hashmi, D Stricker, M Liwicki , and M Z Afzal.Towards End-to-End Semi-Supervised Table Detection with Deformable Transformer.arXiv:2305.02769v2 [cs.CV] 7 May 2023

常用的表格检测识别方法——表格结构识别方法(上) - 合合技术团队 - 博客园

mikel阅读(160)

来源: 常用的表格检测识别方法——表格结构识别方法(上) – 合合技术团队 – 博客园

3.2表格结构识别方法

 

表格结构识别是表格区域检测之后的任务,其目标是识别出表格的布局结构、层次结构等,将表格视觉信息转换成可重建表格的结构描述信息。这些表格结构描述信息包括:单元格的具体位置、单元格之间的关系、单元格的行列位置等。

 

在当前的研究中,表格结构信息主要包括以下两类描述形式:1)单元格的列表(包含每个单元格的位置、单元格 的行列信息、单元格的内容);2)HTML代码或Latex代码(包含单元格的位置信息,有些也会包含单元格的内容)。

 

与表格区域检测任务类似,在早期的表格结构识别方法中,研究者们通常会根据数据集特点,设计启发式算法或者使用机器学习方法来完成表格结构识别任务。

 

Itonori(1993)根据表格中单元格的二维布局的 规律性,使用连通体分析抽取其中的文本块,然后 对每个文本块进行扩展对齐形成单元格,从而得到 每个单元格的物理坐标和行列位置。

 

Rahgozar等人 (1994)则根据行列来进行表格结构的识别,其先 识别出图片中的文本块,然后按照文本块的位置以及两个单元格中间的空白区域做行的聚类和列的聚类,之后通过行和列的交叉得到每个单元格的位 置和表格的结构。

 

Hirayama等人(1995)则从表格线出发,通过平行、垂直等几何分析得到表格的行和列,并使用动态规划匹配的方法对各个内容块进 行逻辑关系识别,来恢复表格的结构。

 

Zuyev(1997) 使用视觉特征进行表格的识别,使用行线和列线以及空白区域进行单元格分割。该算法已经应用到FineReader OCR产品之中。

 

Kieninger等人(1998) 提出了T-Recs(Table RECognition System)系统,以 词语区域的框作为输入,并通过聚类和列分解等启 发式方法,输出各个文本框对应的信息,恢复表格 的结构。随后,其又在此基础上提出了T-Recs++系 统(Kieninger等,2001),进一步提升了识别效果。

 

Amano等人(2001)创新性地引入了文本的语义信息,首先将文档分解为一组框,并将它们半自动地 分为四种类型:空白、插入、指示和解释。然后根据 文档结构语法中定义的语义和几何知识,分析表示 框与其关联条目之间的框关系。

 

Wang等人(2004) 将表格结构定义为一棵树,提出了一种基于优化方 法设计的表结构理解算法。该算法通过对训练集中 的几何分布进行学习来优化参数,得到表格的结构。 同样使用树结构定义表格结构的还有Ishitani等人 (2005),其使用了DOM(Document Object Model) 树来表示表格,从表格的输入图像中提取单元格特 征。然后对每个单元格进行分类,识别出不规则的 表格,并对其进行修改以形成规则的单元格排布。

 

Hassan(2007)、Shigarov(2016)等人则以PDF文档为表格识别的载体,从PDF文档中反解出表格视 觉信息。后者还提出了一种可配置的启发式方法框架。

 

国内的表格结构识别研究起步较晚,因此传统的启发式方法和机器学习方法较少。

 

在早期,Liu等 人(1995)提出了表格框线模板方法,使用表格的 框架线构成框架模板,可以从拓扑上或几何上反映 表格的结构。然后提出相应的项遍历算法来定位和 标记表格中的项。之后Li等人(2012)使用OCR引擎抽取表单中的文本内容和文本位置,使用关键词 来定位表头,然后将表头信息和表的投影信息结合 起来,得到列分隔符和行分隔符来得到表格结构。

 

总体来说,表格结构识别的传统方法可以归纳为以下四种:基于行和列的分割与后处理,基于文本的检测、扩展与后处理,基于文本块的分类和后处理,以及几类方法的融合。

 

随着神经网络的兴起,研究人员开始将它们应用于文档布局分析任务中。后来,随着更复杂的架构的发展,更多的工作被放到表列和整体结构识别中。

 

A Zucker提出了一种有效的方法CluSTi,是一种用于识别发票扫描图像中的表格结构的聚类方法。CluSTi有三个贡献。首先,它使用了一种聚类方法来消除表格图片中的高噪声。其次,它使用最先进的文本识别技术来提取所有的文本框。最后,CluSTi使用具有最优参数的水平和垂直聚类技术将文本框组织成正确的行和列。Z Zhang提出的分割、嵌入和合并(SEM)是一个准确的表结构识别器。M Namysl提出了一种通用的、模块化的表提取方法。

 

E Koci 提出了一种新的方法来识别电子表格中的表格,并在确定每个单元格的布局角色后构建布局区域。他们使用图形模型表示这些区域之间的空间相互关系。在此基础上,他们提出了删除和填充算法(RAC),这是一种基于一组精心选择的标准的表识别算法。

 

SA Siddiqui利用可变形卷积网络的潜力,提出了一种独特的方法来分析文档图片中的表格模式。P Riba提出了一种基于图的识别文档图片中的表格结构的技术。该方法也使用位置、上下文和内容类型,而不是原始内容(可识别的文本),因此它只是一种结构性感知技术,不依赖于语言或文本阅读的质量。E Koci使用基于遗传的技术进行图划分,以识别与电子表中的表格匹配的图的部分。

 

SA Siddiqui将结构识别问题描述为语义分割问题。为了分割行和列,作者采用了完全卷积网络。假设表结构的一致性的情况下,该方法引入了预测拼接方法,降低了表格结构识别的复杂性。作者从ImageNet导入预先训练的模型,并使用FCN编码器和解码器的结构模型。当给定图像时,模型创建与原始输入图像大小相同的特征。

 

SA Khan提出了一个鲁棒的基于深度学习的解决方案,用于从文档图片中已识别的表格中提取行和列。表格图片经过预处理,然后使用门控递归单元(GRU)和具有softmax激活的全连接层发送到双向递归神经网络。SF Rashid提供了一种新的基于学习的方法来识别不同文档图片中的表格内容。SR Qasim提出了一种基于图网络的表识别架构,作为典型神经网络的替代方案。S Raja提出了一种识别表格结构的方法,该方法结合了单元格检测和交互模块来定位单元格,并根据行和列预测它们与其他检测到的单元格的关系。此外,增加了结构限制的损失功能的单元格识别作为额外的差异组件。Y Deng 测试了现有的端到端表识别的问题,他还强调了在这一领域需要一个更大的数据集。

 

Y Zou的另一项研究呼吁开发一种利用全卷积网络的基于图像的表格结构识别技术。所示的工作将表格的行、列和单元格划分。所有表格组件的估计边界都通过连接组件分析进行了增强。根据行和列分隔符的位置,然后为每个单元格分配行和列号。此外,还利用特殊的算法优化单元格边界。

 

为了识别表中的行和列,KA Hashmi [118]提出了一种表结构识别的引导技术。根据本研究,通过使用锚点优化方法,可以更好地实现行和列的定位。在他们提出的工作中,使用掩模R-CNN和优化的锚点来检测行和列的边界。

 

另一项分割表格结构的努力是由W Xue撰写的ReS2TIM论文,它提出了从表格中对句法结构的重建。回归每个单元格的坐标是这个模型的主要目标。最初使用该新技术构建了一个可以识别表格中每个单元格的邻居的网络。本研究给出了一个基于距离的加权系统,这将有助于网络克服与训练相关的类不平衡问题。

 

C Tensmeyer提出了SPLERGE(Split and Merge),另一种使用扩展卷积的方法。他们的策略需要使用两种不同的深度学习模型,第一个模型建立了表的网格状布局,第二个模型决定了是否可能在许多行或列上进行进一步的单元格跨度。

 

Nassar为表格结构提供了一个新的识别模型。在两个重要方面增强了PubTabNet端到端深度学习模型中最新的encoder-dual-decoder。首先,作者提供了一种全新的表格单元目标检测解码器。这使得它们可以轻松地访问编程pdf中的表格单元格的内容,而不必训练任何专有的OCR解码器。作者称,这种体系结构的改进使表格内容的提取更加精确,并使它们能够使用非英语表。第二,基于transformer的解码器取代了LSTM解码器。

 

S Raja提出了一种新的基于目标检测的深度模型,它被定制用于快速优化并捕获表格内单元格的自然对齐。即使使用精确的单元格检测,密集的表格识别也可能仍然存在问题,因为多行/列跨越单元格使得捕获远程行/列关系变得困难。因此,作者也寻求通过确定一个独特的直线的基于图的公式来增强结构识别。作者从语义的角度强调了表格中空单元格的相关性。作者建议修改一个很受欢迎的评估标准,以考虑到这些单元格。为了促进这个问题的新观点,然后提供一个中等大的进行了人类认知注释后的评估数据集。

X Shen提出了两个模块,分别称为行聚合(RA)和列聚合(CA)。首先,作者应用了特征切片和平铺,对行和列进行粗略的预测,并解决高容错性的问题。其次,计算信道的attention map,进一步获得行和列信息。为了完成行分割和列分割,作者利用RA和CA构建了一个语义分割网络,称为行和列聚合网络(RCANet)。

 

C Ma提出了一种识别表格的结构并从各种不同的文档图片中检测其边界的新方法。作者建议使用CornerNet作为一种新的区域候选网络,为fasterR-CNN生成更高质量的候选表格,这大大提高了更快的R-CNN对表格识别的定位精度。该方法只利用最小的ResNet-18骨干网络。此外,作者提出了一种全新的split-and-merge方法来识别表格结构。该方法利用一种新的spatial CNN分离线预测模块将每个检测表格划分为一个单元网格,然后使用一个GridCNN单元合并模块来恢复生成单元格。它们的表格结构识别器可以准确地识别具有显著空白区域的表格和几何变形(甚至是弯曲的)表格,因为spatial CNN模块可以有效地向整个表图片传输上下文信息。B Xiao假设一个复杂的表格结构可以用一个图来表示,其中顶点和边代表单个单元格以及它们之间的连接。然后,作者设计了一个conditional attention网络,并将表格结构识别问题描述为一个单元格关联分类问题(CATT-Net)。

 

Jain建议训练一个深度网络来识别表格图片中包含的各种字符对之间的空间关系,以破译表格的结构。作者提供了一个名为TSR-DSAW的端到端pipeline:TSR,通过深度空间的字符联系,它以像HTML这样的结构化格式生成表格图片的数字表示。该技术首先利用文本检测网络,如CRAFT,来识别输入表图片中的每个字符。接下来,使用动态规划,创建字符配对。这些字符配对在每个单独的图像中加下划线,然后交给DenseNet-121分类器,该分类器被训练来识别同行、同列、同单元格或无单元格等空间相关性。最后,作者将后处理应用于分类器的输出,以生成HTML表格结构。

 

H Li将这个问题表述为一个单元格关系提取的挑战,并提供了T2,一种前沿的两阶段方法,成功地从数字保存的文本中提取表格结构。T2提供了一个广泛的概念,即基本连接,准确地代表了单元格之间的直接关系。为了找到复杂的表格结构,它还构建了一个对齐图,并使用了一个消息传递网络。

实际场景应用中的表格结构识别,不仅要同时完成表格检测和结构识别,还要对每个单元格的文本进行识别和信息抽取,其流程比以上的研究领域都更为复杂。

 

 

 

 

 

 

参考文献:

Gao L C, Li Y B, Du L, Zhang X P, Zhu Z Y, Lu N, Jin L W, Huang Y S, Tang Z . 2022.A survey on table recognition technology. Journal of Image and Graphics, 27(6): 1898-1917.

M Kasem , A Abdallah, A Berendeyev,E Elkady , M Abdalla, M Mahmouda, M Hamada, D Nurseitovd, I Taj-Eddin.Deep learning for table detection and structure recognition: A survey.arXiv:2211.08469v1 [cs.CV] 15 Nov 2022

S A Siddiqui , M I Malik,S Agne , A Dengel and S Ahmed. DeCNT: Deep Deformable CNN for Table Detection. in IEEE Access, vol.6, pp.74151-74161, [DOI: 10.1109/ACCESS.2018.2880211]

T Shehzadi, K A Hashmi, D Stricker, M Liwicki , and M Z Afzal.Towards End-to-End Semi-Supervised Table Detection with Deformable Transformer.arXiv:2305.02769v2 [cs.CV] 7 May 2023

架构与思维:秒杀和竞拍的业务架构,永不过时的话题 - Hello-Brand - 博客园

mikel阅读(143)

来源: 架构与思维:秒杀和竞拍的业务架构,永不过时的话题 – Hello-Brand – 博客园

1 互联网架构越来越复杂?

为啥感觉互联网架构越来越复杂了,早期我们的系统,可能也就那么少部分人使用,大都是一些后台管理系统。
所以不用考虑很多东西,比如:

  • 流量少,无需考虑并发问题
  • 数据少,不用考虑什么索引优化、分库分表
  • 访问不集中,不用考虑缓存、过载保护
  • 如果数据不重要,不用考虑安全策略,甚至不用考虑容灾备份
  • 可重复提交,所以不用关系幂等性
  • 允许短暂宕机和定期关停维护,所以不用考虑多活架构

但是随着互联网的普及和用户的激增,为了应对流量增量带来的各种问题,我们的架构体系衍生出很多强大的技术方案。

2 什么是秒杀/竞拍业务

秒杀业务也是随着互联网电商的发展而不断普及的,我们来看看普通业务和秒杀业务的区别

2.1 普通的业务

  1. 微信的个人信息:个人的注册信息,公众号、视频号的基础信息,微信好友列表,微信群列表。这种是 1:1 的,一般也不会被别人看到。
  2. 微信朋友圈:你盆友圈公开的内容是可以被多个好友看到的,你也可以对应看到你多个好友的盆友圈。这种是 1:n 的,多读的一种场景。

2.2 秒杀/竞拍业务

只有少量的数据,却会在集中的时间段被一批人看到和抢购,集中式的高频读写。
业内也称为 群蜂请求 ,你可以想象下你捅了马蜂窝的场景。哈哈哈

典型秒杀/竞拍业务案例:

  1. 春运前的火车票开售那一刻,可能瞬间有千万级请求涌入
  2. 将来某个遥遥领先开售,可能是一秒售罄

这些业务场景有如下技术难点:

  1. 瞬时流量特别大,你的接入层、应用层、数据层等能否扛得住
  2. 大量流量涌入 对一个数据进行操作,怎么保证数据原子增减、顺序公平性,怎么保证数据不超卖
  3. 如何 保证数据安全,如防攻击、防刷数、保持幂等
  4. 如果使用 并发控制,如何保证不产生死锁

所以,一个优秀的秒杀业务架构,在现在的互联网业务中,是一个永不过时的话题

3 如何优化

这边只针对几个对秒杀业务有效改进的点做展开,什么集群动态扩容、流量控制、弹性伸缩、智能限流啊,可以参考我的这篇文章《千万级流量冲击下,如何保证极致性能》。

3.1 清除无效请求

尽量在前面就把一些无效请求给清理掉,所以这些操作Web前端 或者 App Client端做就行了,越前端越好,尽量不要伤害到服务端,比如:

  • 未登录拦截
  • 重复提交拦截(未响应则按钮置灰,直至响应或者5S超时才恢复,幂等保证)
  • 频繁提交拦截(单用户一分钟不超过100次,避免AI刷机)
  • 验证码拦截(避免AI刷数据、黑客攻击等)
  • 参与条件拦截(可提前加载名单):如用户等级不够、注册未满3个月、用户进入黑名单等

image

3.2 服务端+缓存层做高效原子操作

公共数据做缓存
缓存是提升系统性能的重要手段。通过缓存热点数据,缓存还可以提高数据的访问速度,见很少对数据库的访问速度,提升用户体验。Redis单机每秒10w没什么问题,再加上多集群多副本模式。

原子操作保证秒杀的计数
在Redis中,高效地进行原子计数通常使用INCRINCRBYDECRDECRBY等命令。这些命令都是原子操作,意味着在执行时不会被其他Redis命令打断,从而保证了计数的准确性和一致性。

# 计算已售卖1000台库里南
> INCRBY cullinan_counter 1000

# 获取当前售卖数量
> GET cullinan_counter
> 1000

# 超过1000,返回秒杀失败

队列保证请求有序进入
使用Redis的 Stream 队列功能。Stream 实际上是一个 key,你可以使用 XADD 命令向其中添加消息。

XADD mystream * field1 value1 field2 value2

这里 mystream 是 Stream 的名称,* 表示让 Redis 自动生成一个唯一的消息 ID。field1 value1 和 field2 value2 是消息的内容,你可以根据需要添加任意数量的字段。
如果你只有1000台库里南供抢购,那么第1001就不要进入队列了。

扩展阅读
缓存可以扩展阅读作者的这个系列的文章:★ Redis24篇集合

image

3.3 数据层做终兜底

经过上面的保证之后,到数据层的量就很少了,大概率就是你定额的商品数量同等的数量。
比如1000,数据库绝对的扛得住的。
唯一可以做的就是检查数量是否符合预期,这个可以创建约束或者触发器来实现。

image

3.4 全球式业务,单元化处理

有些人可能会说,我的商品全球售卖,那我的缓存中心、数据中心放哪里,如果放中国,那跨地域跨机房访问,在0.1微妙都能决定我是不是买得到,欧洲的客户铁定抢不到库里南了。
现在的做法一般是单元化隔离,比如:

image

A/B中心都有这样的缓存或者数据结构,配置中心统一下发配置。然后在各自的单元里面玩耍,互不干预。 秒杀业务千万不要想着跨地域+跨机房,用户存在不公平性。

4 写在最后

  1. 无效请求拦截,尽量在前端完成,避免走入后端,造成服务端压力
  2. 缓存支持高性能检索、原子计算和有序队列
  3. 数据层做存储兜底
  4. 分治原理:单元化隔离,避免集中处理

C# 开发技巧 轻松监控方法执行耗时 - 小码编匠 - 博客园

mikel阅读(164)

来源: C# 开发技巧 轻松监控方法执行耗时 – 小码编匠 – 博客园

MethodTimer.Fody 是一个功能强大的库,可以用于测量 .NET 应用程序中的方法的执行时间。允许你在不修改代码的情况下,自动地测量和记录方法的执行时间。

这个工具是基于.NET的 weaving 技术,通过修改IL(Intermediate Language,中间语言)代码来插入计时逻辑,从而在方法调用前后记录时间戳,进而计算出方法的执行时间。

它使用 Fody 插件框架可以无缝集成到项目中,所以向代码中添加性能测量功能变得非常容易。

使用方法

1、安装NuGet包

在Visual Studio中,打开NuGet包管理器,搜索并安装MethodTimer.Fody或者使用命令方式

PM> Install-Package Fody
PM> Install-Package MethodTimer.Fody

具体操作如下图所示:

2、使用 Time 特性

复制代码
using MethodTimer;

namespace DemoConsole
{
    internal class Program
    {
        /// <summary>
        /// 程序入口
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // 调用示例方法
            new Program().DoSomething();

            Console.WriteLine("测试方法执行结束!!!");

            Console.ReadKey();
        }

        /// <summary>
        /// 示例方法
        /// </summary>
        [Time]
        public void DoSomething()
        {
            Console.WriteLine("测试方法执行时间!!!");
        }
    }
}
复制代码

Fody是一个.NET的weaving框架,需要确保项目已经启用了Fody,并且在项目属性的”Fody”标签页中添加了MethodTimer模块。

3、执行效果

启动运行程序,可以在输出窗口查看方法的执行耗时,具体如下图所示:

4、其他说明

Time 特性不仅可以加在方法上还可以直接添加到 Class 上,具体如下代码所示:

复制代码
using MethodTimer;

namespace ConsoleApp3
{
    [Time]
    internal class Program
    {
        /// <summary>
        /// 程序入口
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // 调用示例方法
            new Program().DoSomething();

            new Program().ToDoSomething();

            Console.WriteLine("方法执行结束!!!");

            Console.ReadKey();
        }

        /// <summary>
        /// 示例方法1
        /// </summary>
      
        public void DoSomething()
        {
            Console.WriteLine("001——测试执行时间方法!!!");
           
        }
        /// <summary>
        /// 示例方法2
        /// </summary>

        public void ToDoSomething()
        {
            Console.WriteLine("002——测试执行时间方法!!!");

        }
    }
}
复制代码

运行程序后,可以输出类中每个方法的执行时间。

实际上,在代码中添加了 Time 特性以后,Fody 会自动生成下面的代码

复制代码
 public class MyClass
 {
        [Time]
        public void DoSomething()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            // 原始方法体
            System.Threading.Thread.Sleep(1000); // 模拟工作

            stopwatch.Stop();

            // 输出或记录执行时间
            Console.WriteLine($"执行时间:{stopwatch.Elapsed.TotalMilliseconds} ms");
        }
 }
复制代码

5、拦截记录

如果想手动处理日志记录,可以定义一个静态类来拦截日志记录,方法的示例,具体如下代码所示

复制代码
public static class MethodTimeLogger
{
    public static void Log(MethodBase methodBase, TimeSpan elapsed, string message)
    {
        //Do some logging here
    }
}
复制代码

生成后的代码

复制代码
public class MyClass
{
    public void MyMethod()
    {
        var stopwatch = Stopwatch.StartNew();
        try
        {
            Console.WriteLine("Hello");
        }
        finally
        {
            stopwatch.Stop();
            MethodTimeLogger.Log(methodof(MyClass.MyMethod), stopwatch.Elapsed);
        }
    }
}
复制代码

MethodTimer.Fody是一个非常有用的工具,尤其在性能调优阶段,可以帮助你快速识别出哪些方法是性能瓶颈,从而针对性地进行优化。

主要特点

1、非侵入式

MethodTimer.Fody不需要在源代码中添加额外的计时代码,只需要在项目中添加相应的NuGet包,并在项目属性中做一些配置,就可以自动地为方法添加计时功能。

2、灵活的配置

你可以选择性地对某些方法进行计时,或者排除不想被计时的方法。这通常通过方法的特性或者类的命名空间来进行配置。

3、输出结果多样化

MethodTimer.Fody可以将计时结果输出到不同的地方,如控制台、日志文件或者通过事件追踪(ETW)等方式,这取决于你的配置。

4、性能影响小

尽管MethodTimer.Fody在方法中插入了计时逻辑,但它被设计得尽可能地对性能影响最小,通过精心优化的IL代码插入策略来实现这一点。

总结

MethodTimer.Fody 是一个强大的工具,提供了简便的方式来监控 C# 方法的执行时间,特别适用于需要快速诊断性能问题的场合。

通过其灵活的配置和非侵入性的特性,它可以无缝地融入现有的开发流程中,帮助我们团队提高应用的性能和响应速度。

这个工具特别适合在开发和测试阶段快速识别性能瓶颈,而无需在代码中显式地添加计时代码,可以保持源代码的整齐性和可维护性。

开源地址

https://github.com/Fody/MethodTimer

Asp .Net Core 系列:基于 T4 模板生成代码 - Code技术分享 - 博客园

mikel阅读(133)

来源: Asp .Net Core 系列:基于 T4 模板生成代码 – Code技术分享 – 博客园

T4模板,即Text Template Transformation Toolkit,是微软官方在Visual Studio中引入的一种代码生成引擎。自Visual Studio 2008开始,T4模板就被广泛应用于生成各种类型的文本文件,包括网页、资源文件以及各种编程语言的源代码等。

T4模板是一种由文本块和控制逻辑组成的混合模板,它可以根据预设的规则和输入数据生成目标文本文件。

官网:https://learn.microsoft.com/zh-cn/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2022

组成部分

T4模板主要由以下几部分组成:

  1. 指令块:向文本模板化引擎提供关于如何生成转换代码和输出文件的一般指令。常见的指令包括<#@ template #><#@ parameter #><#@ assembly #><#@ import #><#@ include #><#@ output #>等。
    • 模板指令<#@ template #>):定义模板的基本属性,如使用的编程语言、是否开启调试模式等。
    • 参数指令<#@ parameter #>):声明模板代码中从外部上下文传入的值初始化的属性。
    • 程序集指令<#@ assembly #>):引用外部程序集,以便在模板中使用其中的类型和方法。
    • 导入指令<#@ import #>):允许在模板中引用其他命名空间中的类型,类似于C#中的using指令或Visual Basic中的Imports指令。
    • 包含指令<#@ include #>):在模板中包含另一个文件的内容,通常用于共享常用的代码片段或模板设置。
    • 输出指令<#@ output #>):定义输出文件的扩展名和编码方式。
  2. 文本块:直接复制到输出文件的内容,不会进行任何处理或转换。
  3. 代码语句块(Statement Block)

    代码语句块通过<#Statement#>的形式表示,中间是一段通过相应编程语言编写的程序调用,我们可以通过代码语句快控制文本转化的流程。在上面的代码中,我们通过代码语句块实现对一个数组进行遍历,输出重复的Console.WriteLine("Hello {0},Welcome to T4 World!","<#= p.Name #>");语句。

  4. 表达式块(Expression Block)

    表达式块以<#=Expression#>的形式表示,通过它之际上动态的解析的字符串表达内嵌到输出的文本中。比如在上面的foreach循环中,每次迭代输出的人名就是通过表达式块的形式定义的(<#= p.Name #>

  5. 类特性块(Class Feature Block)

    如果文本转化需要一些比较复杂的逻辑,我们需要写在一个单独的辅助方法中,甚至是定义一些单独的类,我们就是将它们定义在类特性块中。类特性块的表现形式为<#+ FeatureCode #>

分类

  1. 设计时模板(文本模版)

    在 Visual Studio 中执行设计时 T4 文本模板,以便定义应用程序的部分源代码和其他资源。通常,您可以使用读取单个输入文件或数据库中的数据的多个模板,并生成一些 .cs、.vb 或其他源文件。每个模板都生成一个文件。 在 Visual Studio 或 MSBuild 内执行它们。若要创建设计时模板,请向您的项目中添加“文本模板”文件。 另外,您还可以添加纯文本文件并将其“自定义工具”属性设置为“TextTemplatingFileGenerator”。

  2. 运行时模板(预处理模板)

    可在应用程序中执行运行时 T4 文本模板(“预处理过的”模板)以便生成文本字符串(通常作为其输出的一部分)。若要创建运行时模板,请向您的项目中添加“已预处理的文本模板”文件。另外,您还可以添加纯文本文件并将其“自定义工具”属性设置为“TextTemplatingFilePreprocessor”。

Visual Studio 中使用T4模板

1.创建T4模板文件

  1. 新建文件:在Visual Studio中,你可以通过右键点击项目,选择“添加” -> “新建项…”,然后在搜索框中输入“T4”或“Text Template”来找到T4模板文件模板(通常称为“文本模板”)。选择它并命名你的模板文件(例如:MyTemplate.tt)。

image

  1. 编辑模板:双击新创建的.tt文件以在Visual Studio中打开它。此时,你可以看到模板的初始内容,包括一些基本的指令和控制块。

2. 编写T4模板

在T4模板中,你可以使用C#或VB.NET代码(取决于你的项目设置)来编写控制逻辑,并使用特定的语法来定义输出文本的格式。

  • 指令块:如前所述,使用指令块来定义模板的行为和引入必要的资源。
  • 控制块:使用<# ... #>来包围代码块,这些代码块在模板转换时执行。
  • 表达式块:使用<#= ... #>来输出表达式的值到生成的文本中。
  • 类特征块:使用<#+ ... #>来定义辅助方法、属性或类,这些方法可以在模板的其他部分中被调用。
<#@ template debug="false" hostspecific="false" language="C#" #>  
<#@ output extension=".cs" #>
using System;  
  
namespace MyNamespace  
{  
    public class MyClass  
    {  
        public string MyProperty { get; set; }  
  
        public void MyMethod()  
        {  
            Console.WriteLine("Hello from T4 Template!");  
        }  
    }  
}

3. 转换模板

  • 自动转换:在Visual Studio中,通常当你保存T4模板文件时,Visual Studio会自动执行模板转换并生成输出文件。
  • 手动转换:你也可以通过右键点击模板文件并选择“运行自定义工具”来手动触发模板的转换。

中心控制Manager

上面T4模板的简单内容。可以生成模板,但是只能保存在t4模板的目录下方,无法进行更多操作。假如是项目集,还需要手动赋值粘贴很麻烦,基于Manage类进行块控制和保存文件到指定位置

<#@ assembly name="System.Core"#>
<#@ assembly name="EnvDTE"#>
<#@ import namespace="System.Collections.Generic"#>
<#@ import namespace="System.IO"#>
<#@ import namespace="System.Text"#>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating"#>
<#@ output extension=".cs" #>
<#+
class Manager
{
    public struct Block {
        public int Start, Length;
		public String Name,OutputPath;
    }

    public List<Block> blocks = new List<Block>();
    public Block currentBlock;
    public Block footerBlock = new Block();
    public Block headerBlock = new Block();
    public ITextTemplatingEngineHost host;
    public ManagementStrategy strategy;
    public StringBuilder template;
    public Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader) {
        this.host = host;
        this.template = template;
        strategy = ManagementStrategy.Create(host);
    }
    public void StartBlock(String name,String outputPath) {
        currentBlock = new Block { Name = name, Start = template.Length ,OutputPath=outputPath};
    }

    public void StartFooter() {
        footerBlock.Start = template.Length;
    }

    public void EndFooter() {
        footerBlock.Length = template.Length - footerBlock.Start;
    }

    public void StartHeader() {
        headerBlock.Start = template.Length;
    }

    public void EndHeader() {
        headerBlock.Length = template.Length - headerBlock.Start;
    }    

    public void EndBlock() {
        currentBlock.Length = template.Length - currentBlock.Start;
        blocks.Add(currentBlock);
    }
    public void Process(bool split) {
        String header = template.ToString(headerBlock.Start, headerBlock.Length);
        String footer = template.ToString(footerBlock.Start, footerBlock.Length);
        blocks.Reverse();
        foreach(Block block in blocks) {
            String fileName = Path.Combine(block.OutputPath, block.Name);
            if (split) {
                String content = header + template.ToString(block.Start, block.Length) + footer;
                strategy.CreateFile(fileName, content);
                template.Remove(block.Start, block.Length);
            } else {
                strategy.DeleteFile(fileName);
            }
        }
    }
}
class ManagementStrategy
{
    internal static ManagementStrategy Create(ITextTemplatingEngineHost host) {
        return (host is IServiceProvider) ? new VSManagementStrategy(host) : new ManagementStrategy(host);
    }

    internal ManagementStrategy(ITextTemplatingEngineHost host) { }

    internal virtual void CreateFile(String fileName, String content) {
        File.WriteAllText(fileName, content);
    }

    internal virtual void DeleteFile(String fileName) {
        if (File.Exists(fileName))
            File.Delete(fileName);
    }
}

class VSManagementStrategy : ManagementStrategy
{
    private EnvDTE.ProjectItem templateProjectItem;

    internal VSManagementStrategy(ITextTemplatingEngineHost host) : base(host) {
        IServiceProvider hostServiceProvider = (IServiceProvider)host;
        if (hostServiceProvider == null)
            throw new ArgumentNullException("Could not obtain hostServiceProvider");

        EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
        if (dte == null)
            throw new ArgumentNullException("Could not obtain DTE from host");

        templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile);
    }
    internal override void CreateFile(String fileName, String content) {
        base.CreateFile(fileName, content);
        //((EventHandler)delegate { templateProjectItem.ProjectItems.AddFromFile(fileName); }).BeginInvoke(null, null, null, null);
    }
    internal override void DeleteFile(String fileName) {
        ((EventHandler)delegate { FindAndDeleteFile(fileName); }).BeginInvoke(null, null, null, null);
    }
    private void FindAndDeleteFile(String fileName) {
        foreach(EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems) {
            if (projectItem.get_FileNames(0) == fileName) {
                projectItem.Delete();
                return;
            }
        }
    }
}#>

每一个文件就要进行一次block的开关,即manager.StartBlock(文件名)manager.EndBlock(),在文件都结束后,执行manager.Process(true),进行文件的写操作。

注意:Manager类实现了文件块的开关和保存位置的设定。
这里需要设置template指令 :hostspecific=“true”

如果提示错误:T4 模板 错误 当前上下文中不存在名称“Host” ,请按照设置hostspecific=“true”

根据 MySQL 数据库生成实体

MySQLHelper.tt

<#@ assembly name="C:\Users\xxxx\.nuget\packages\mysql.data\9.0.0\lib\net48\MySql.Data.dll" #>
<#@ assembly name="System.Core.dll" #>
<#@ assembly name="System.Data.dll" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="MySql.Data.MySqlClient" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>

<#+  
    public class EntityHelper
    {
        public static List<Entity> GetEntities(string connectionString, List<string> databases)
        {
            var list = new List<Entity>();
            var conn = new MySqlConnection(connectionString);
            try
            {
                conn.Open();
                var dbs = string.Join("','", databases.ToArray());
                var cmd = string.Format(@"SELECT `information_schema`.`COLUMNS`.`TABLE_SCHEMA`
                                                    ,`information_schema`.`COLUMNS`.`TABLE_NAME`
                                                    ,`information_schema`.`COLUMNS`.`COLUMN_NAME`
                                                    ,`information_schema`.`COLUMNS`.`DATA_TYPE`
                                                    ,`information_schema`.`COLUMNS`.`COLUMN_COMMENT`
                                                FROM `information_schema`.`COLUMNS`
                                                WHERE `information_schema`.`COLUMNS`.`TABLE_SCHEMA` IN ('{0}') ", dbs);
                using (var reader = MySqlHelper.ExecuteReader(conn, cmd))
                {
                    while (reader.Read())
                    {
                        var db = reader["TABLE_SCHEMA"].ToString();
                        var table = reader["TABLE_NAME"].ToString();
                        var column = reader["COLUMN_NAME"].ToString();
                        var type = reader["DATA_TYPE"].ToString();
                        var comment = reader["COLUMN_COMMENT"].ToString();
                        var entity = list.FirstOrDefault(x => x.EntityName == table);
                        if (entity == null)
                        {
                            entity = new Entity(table);
                            entity.Fields.Add(new Field
                            {
                                Name = column,
                                Type = GetCLRType(type),
                                Comment = comment
                            });

                            list.Add(entity);
                        }
                        else
                        {
                            entity.Fields.Add(new Field
                            {
                                Name = column,
                                Type = GetCLRType(type),
                                Comment = comment
                            });
                        }
                    }
                }
            }
            finally
            {
                conn.Close();
            }

            return list;
        }

        public static string GetCLRType(string dbType)
        {
            switch (dbType)
            {
                case "tinyint":
                case "smallint":
                case "mediumint":
                case "int":
                case "integer":
                    return "int";
                case "double":
                    return "double";
                case "float":
                    return "float";
                case "decimal":
                    return "decimal";
                case "numeric":
                case "real":
                    return "decimal";
                case "bit":
                    return "bool";
                case "date":
                case "time":
                case "year":
                case "datetime":
                case "timestamp":
                    return "DateTime";
                case "tinyblob":
                case "blob":
                case "mediumblob":
                case "longblog":
                case "binary":
                case "varbinary":
                    return "byte[]";
                case "char":
                case "varchar":
                case "tinytext":
                case "text":
                case "mediumtext":
                case "longtext":
                    return "string";
                case "point":
                case "linestring":
                case "polygon":
                case "geometry":
                case "multipoint":
                case "multilinestring":
                case "multipolygon":
                case "geometrycollection":
                case "enum":
                case "set":
                default:
                    return dbType;
            }
        }
    }

    public class Entity
    {
        public Entity()
        {
            this.Fields = new List<Field>();
        }

        public Entity(string name)
            : this()
        {
            this.EntityName = name;
        }

        public string EntityName { get; set; }
        public List<Field> Fields { get; set; }
        public string PascalEntityName
        {
            get
            {
                return CommonConver.ToPascalCase(this.EntityName);
            }
        }
        public string CamelEntityName
        {
            get
            {
                return CommonConver.ToCamelCase(this.EntityName);
            }
        }
    }

    public class Field
    {
        public string Name { get; set; }
        public string Type { get; set; }
        public string Comment { get; set; }
    }
    public class CommonConver
    {
        public static string ToPascalCase(string tableName)
        {
            string upperTableName = tableName.Substring(0, 1).ToUpper() + tableName.Substring(1, tableName.Length - 1);
            return upperTableName;
        }
        public static string ToCamelCase(string tableName)
        {
            string lowerTableName = tableName.Substring(0, 1).ToLower() + tableName.Substring(1, tableName.Length - 1);
            return lowerTableName;
        }
    }

    class config
    {

        public static readonly string ConnectionString = "Database=test;Data Source=127.0.0.1;User Id=root;Password=123456;pooling=false;CharSet=utf8;port=3306";
        public static readonly string ModelNameSpace = "App.Entities";
    }
#>

AutoCreateModel.tt

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Data.Common.dll" #>
<#@ assembly name="System.Core.dll" #>
<#@ assembly name="System.Data.dll" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ include file="$(ProjectDir)Manage.tt"  #>
<#@ include file="$(ProjectDir)MySqlHelper.tt"  #>
<#@ output extension=".cs" #>
<# var manager = new Manager(Host, GenerationEnvironment, true); #>
<# 
    
      var OutputPath1 ="D:\\test"; //设置文件存储逇位置
      var entities =EntityHelper.GetEntities(config.ConnectionString,new List<string> { "test"});
      foreach(Entity entity in entities)
     {
	 manager.StartBlock(entity.EntityName+".cs",OutputPath1);
#>
using System;

namespace <#=config.ModelNameSpace#>
{
    /// <summary>
    /// <#= entity.EntityName #> Entity Model
    /// </summary>   

    public class <#= entity.EntityName #>
    {
<#
        for(int i = 0; i < entity.Fields.Count; i++)
        {
            if(i ==0)
            {
#>      
        /// <summary>
        /// <#= entity.Fields[i].Comment #>
        /// </summary>
        public <#= entity.Fields[i].Type #> <#= entity.Fields[i].Name #> { get; set; }
<#
            }
            else
            {
#>   
        /// <summary>
        /// <#= entity.Fields[i].Comment #>
        /// </summary>

        public <#= entity.Fields[i].Type #> <#= entity.Fields[i].Name #> { get; set; }
<#            
            }
        }
#>
    }
}
<#       
        manager.EndBlock();
    }
    manager.Process(true);
#>

介绍几个常用的$(variableName) 变量:

  • $(SolutionDir):当前项目所在解决方案目录
  • $(ProjectDir):当前项目所在目录
  • $(TargetPath):当前项目编译输出文件绝对路径
  • $(TargetDir):当前项目编译输出目录,即web项目的Bin目录,控制台、类库项目bin目录下的Debug或release目录(取决于当前的编译模式)

举个例子:比如我们在D盘根目录建立了一个控制台项目TestConsole,解决方案目录为D:\LzrabbitRabbit,项目目录为
D:\LzrabbitRabbit\TestConsole,那么此时在Debug编译模式下

  • $(SolutionDir)的值为D:\LzrabbitRabbit
  • $(ProjectDir)的值为D:\LzrabbitRabbit\TestConsole
  • $(TargetPath)值为D:\LzrabbitRabbit\TestConsole\bin\Debug\TestConsole.exe
  • $(TargetDir)值为D:\LzrabbitRabbit\TestConsole\bin\Debug\

Windows Server2012 R2 无法安装.NET Framework 3.5的解决方法 - __小白菜 - 博客园

mikel阅读(235)

来源: Windows Server2012 R2 无法安装.NET Framework 3.5的解决方法 – __小白菜 – 博客园

Windows server 2012R2,自带的是.NET Framework 4.5,如果想装SQL server2008或者SQL server2012就需要安装 .ENT Framework 3.5或者2.0的版本,建议安装  .NET3.5 版本,我本人亲测过,成功了!

安装不成功错误分析:

如果直接装SQL server2008或者2012,就会报:无法安装一下功能 .NET Framework 3.5。

如果找一个.NET Framework 3.5的来安装,系统会报安装了一个或者多个角色服务或功能失败,找不到原文件等错误。

按照提示从控制面板-程序-启动或关闭Windows功能里看看

这和Win7,win10 ,xp操作不一样,但是原理是一样;

 

 

 

我们看到系统默认安装了.NET Framework 4.5于是隐隐有种不祥的预感,但我们还是要硬着头皮勾选3.5

显示需要指定备用路径,但我没有指定

到这里就是一个失败的安装;

解决方法:

 

从网上参考了很多:https://blog.csdn.net/sunny_lv/article/details/73603360

这篇文章里说了很多方法,大家可以尝试。

从网上找了安装盘路径下的 C:\sources\sxs简包放入指定位置后,输入备用源路径也没起作用,只能乖乖下载整个镜像文件

(WindowsServer2012R2镜像文件迅雷链接:ed2k://|file|cn_windows_server_2012_r2_vl_with_update_x64_dvd_4051059.iso|4683122688|BD0B95997679F83A4EE2D062865D8E64|/ )

下载的镜像文件里有sxs这个文件

于是灵光乍现(投机取巧),让我们来试一试这个简包,于是单独复制sxs文件到服务器的C:/下。

填写备用源路径为C:/sxs

 

其实回想一下,备用源路径只要能指向到正确的安装盘下的sxs文件即可。之前下载的安装简包可能不是对应Windows servers 2012R2版本里切取出来的,所以使用本文方法的同学一定要注意选取对应版本的简包(我用的简包链接在文中),然后指定备用源路径即可。

为什么反射慢? - 北冥有鱼要继续奋斗 - 博客园

mikel阅读(169)

来源: 为什么反射慢? – 北冥有鱼要继续奋斗 – 博客园

反射机制就是通过字节码文件对象获取成员变量、成员方法和构造方法,然后进一步获取它们的具体信息,如名字、修饰符、类型等。

反射机制的性能较低有很多原因,这里详细总结以下4点原因:

(1)JIT优化受限:
JIT 编译器的优化是基于静态分析和预测的。反射是一种在运行时动态解析类型信息的机制,在编译时无法确定反射调用的具体方法,因此编译器无法对这些代码进行静态分析,从而无法进行一些JIT优化,比如:

内联优化受限:JIT 编译器通常会对频繁调用的方法进行内联优化,将方法调用替换为直接的代码。但是,由于反射调用的方法在运行时才能确定,因此 JIT 编译器无法进行有效的内联优化。

无法进行即时编译:因为反射调用的方法在运行时才能确定,因此在解释执行阶段,我们无法确定反射调用的方法会被执行多少次,会不会成为热点代码,也就无法对其进行即时编译优化。

(2)反射中频繁的自动拆装箱操作会导致应用性能下降:
在反射中,当你调用一个方法时,由于在编译时不知道具体要调用的方法参数类型,因此需要用最通用的引用类型来处理所有的参数,即Object。例如,通过Method对象调用方法时,使用的invoke方法签名大致如下:

public Object invoke(Object obj, Object... args)

对于基本数据类型的参数,它们必须被装箱成对应的包装类(如IntegerDouble等),以便它们可以作为对象被传递。在方法实际执行时,如果方法的参数是基本类型,JVM需要基本类型的值,而不是它们的包装类对象。因此,JVM会自动进行拆箱。例如,如果你通过反射调用的方法期望得到一个int类型的参数,但你传入的是Integer,在调用过程中JVM会自动将Integer对象拆箱为int类型。装箱和拆箱操作涉及到额外的对象创建(装箱时)和对象值的提取(拆箱时),在高性能要求的场景下,过度的装箱和拆箱可能会导致性能瓶颈。此外,由于装箱操作导致创建了许多短生命周期的对象,这些对象在成为垃圾后,需要通过垃圾回收过程来回收内存资源,当有大量对象需要回收时,GC会占用更多的CPU资源,可能导致应用性能暂时下降。

(3)遍历操作
反射在调用方法时会从方法数组中遍历查找,这对普通的方法调用来说是不需要的。

(4)方法访问检查
每次使用反射调用方法时,JVM都要检查是否允许访问该方法,例如是否为私有方法等。这些访问检查对普通的方法调用来说是不需要的,因为这些检查都是在编译时完成的。

C# 使用模式匹配的好处,因为好用所以推荐~ - 万雅虎 - 博客园

mikel阅读(140)

来源: C# 使用模式匹配的好处,因为好用所以推荐~ – 万雅虎 – 博客园

  1. 类型检查和转换:当你需要检查对象是否为特定类型,并且希望在同一时间内将其转换为那个类型时,模式匹配提供了一种更简洁的方式来完成这一任务,避免了使用传统的as和is操作符后还需要进行额外的null检查。
  2. 复杂条件逻辑:在处理复杂的条件逻辑时,特别是涉及到多个条件和类型的情况下,使用模式匹配可以使代码更加清晰易读。通过模式匹配,可以将复杂的if-else链或switch语句简化,使逻辑更直观。
  3. 解构复合类型:当你需要从复合类型(如元组、自定义类等)中提取值时,模式匹配允许你直接在条件检查中进行解构,这样可以避免编写额外的解构代码,使得代码更加简洁。
  4. 范围检查:对于需要进行范围检查的场景,如检查一个数是否落在某个区间内,使用C# 9.0引入的关系模式可以极大简化代码,使得范围检查逻辑一目了然。
  5. 逻辑组合:在需要对多个条件进行逻辑组合的情况下,如需要检查一个值是否满足多个条件之一或全部条件,使用逻辑模式可以直接在模式匹配表达式中使用and、or和not运算符,避免了复杂的逻辑嵌套。
  6. 数据验证:模式匹配可以用于数据验证场景,特别是当验证逻辑涉及到类型检查、值范围检查或特定属性值检查时。通过模式匹配,可以在单个表达式中完成所有这些检查,使得验证逻辑更加紧凑和易于维护。
  7. 多态行为:在处理需要根据对象类型执行不同操作的多态行为时,模式匹配提供了一种更灵活的方式来替代传统的虚方法或接口实现。这使得在不修改原有类层次结构的情况下,能够更容易地扩展或修改行为。
  8. 替代访问者模式:在实现访问者设计模式时,模式匹配可以作为一种更简洁的替代方案,特别是在处理复杂的对象结构时。通过模式匹配,可以直接在一个地方处理所有类型的情况,而不需要为每种类型创建单独的访问者方法。

模式匹配的这些用途展示了它在简化代码、提高可读性和灵活处理不同类型和条件的强大能力。随着C#语言的发展,模式匹配的功能和应用场景将会进一步扩展和深化。

下面我们看下一些经典的模式匹配编码风格:

is断言 变量str已被安全地转换为string类型

object obj = "Hello, World!";
if (obj is string str) {
    Console.WriteLine(str);
}

is对可空类型的断言

public record Person(int Id, string? Name, bool? IsActived);
var person = new Person(1, "vipwan", null);
if (person?.IsActived is true)
{
    Console.WriteLine($"Id {person.Id} 已激活");
}

switch 允许使用多种模式,包括类型模式、常量模式和var模式 ,无需我们提前做转换以节省编码量

switch (obj) {
    case 0:
        Console.WriteLine("Zero");
        break;
    case var value:
        Console.WriteLine($"Value: {value}");
        break;
}

switch 中使用弃元_代替变量

public static string CronEveryNHours(this int n) => n switch
{
	(>= 1 and < 24) => $"0 0/{n} * * *",
	_ => throw new ArgumentException("n must be between 1 and 24", nameof(n))
};

C# 8.0引入了属性模式,允许基于对象的属性进行模式匹配

public record Person(string Name,int Age);
var person = new Person("vipwan", 30);
//通俗易懂:如果person不为null,且name==vipwan 并且age>=18的时候
if (person is { Name: "vipwan", Age: >= 18 }) {
    Console.WriteLine("vipwan is an adult.");
}

C# 9.0引入的逻辑模式,它允许使用逻辑运算符andornot来组合模式。

if (number is > 0 and < 10 or 100) {
    Console.WriteLine("Number is between 0 and 10 or equals 100.");
}

元组模式允许你对元组的元素进行模式匹配,这在处理元组返回值或多值情况时非常有用

var numbers = (1, "one", 18);
if (numbers is (1, string name, int age)) {
    Console.WriteLine($"The name of 1 is {name}, age {age}!");
}

列表模式允许对数组、列表等集合进行模式匹配,可以匹配集合的长度、元素等属性。这对于处理集合数据时进行模式匹配提供了极大的便利。

int[] numbers = { 1, 2, 3 };
if (numbers is [1, 2, 3]) {
    Console.WriteLine("The array contains the numbers 1, 2, and 3 in that order.");
}

切片模式允许你匹配集合的一部分,而不是整个集合。这在你只关心集合的某个特定部分时特别有用。

int[] numbers = { 0, 1, 2, 3, 4 };
if (numbers is [0, .., 4]) {
    Console.WriteLine("The array starts with 0 and ends with 4.");
}

这里只是介绍了部分好用常见的模式匹配,随着C#语言的逐代增强,可能会有更多的新特性和改进被引入。

Layui select配合input实现可输入,可选择,可搜索_layui selectinput-CSDN博客

mikel阅读(166)

来源: Layui select配合input实现可输入,可选择,可搜索_layui selectinput-CSDN博客

1.前言
今天遇到一个问题,客户要求select框可以自己输入,就是在下拉列表里找不到自己想要的选项就可以自己输入,同时还要支持模糊匹配功能,百度了一下,都是讲select配合input,input覆盖在select上面,同时又不完全盖住select,就可以实现,输入,和选择并存了,但是模糊匹配是一个问题。好了先看一张效果图。

 

 

Video_2018-10-09_150251.gif

 

其实我这里下拉框中有五个选项,111~555.666是没有的这个是自己输入的。
先看html代码

<div class=”layui-col-md4″>
<label class=”layui-form-label”>移交单位<span style=”color:red”>*</span></label>
<div class=”layui-input-block”>
<input type=”text” name=”HandoverCompany” id=”HandoverCompany” class=”layui-input” style=”position:absolute;z-index:2;width:80%;” lay-verify=”required” value=”111″ onkeyup=”search()” autocomplete=”off”>
<select type=”text” id=”hc_select” lay-filter=”hc_select” autocomplete=”off” placeholder=”移交单位全称” lay-verify=”required” class=”layui-select” lay-search>
<option value=”111″>111</option>
<option value=”222″>222</option>
<option value=”333″>333</option>
<option value=”444″>444</option>
<option value=”555″>555</option>
</select>
</div>
</div>
这里代码我就只粘贴部分,其中input的几个style样式简单说一下。

position:absolute 在这里是让input和select在同一位置。
z-index:2 是为了让input在select上面。
width:80% 是为了不盖住select后面的小三角符号,select还可以点击。
autocomplete=”off” 为了不自动填充input框,免得压盖select选项

然后是JS代码。

layui.use([‘form’, ‘layedit’,’upload’], function () {
var form = layui.form
form.on(‘select(hc_select)’, function (data) { //选择移交单位 赋值给input框
$(“#HandoverCompany”).val(data.value);
$(“#hc_select”).next().find(“dl”).css({ “display”: “none” });
form.render();
});

window.search = function () {
var value = $(“#HandoverCompany”).val();
$(“#hc_select”).val(value);
form.render();
$(“#hc_select”).next().find(“dl”).css({ “display”: “block” });
var dl = $(“#hc_select”).next().find(“dl”).children();
var j = -1;
for (var i = 0; i < dl.length; i++) {
if (dl[i].innerHTML.indexOf(value) <= -1) {
dl[i].style.display = “none”;
j++;
}
if (j == dl.length-1) {
$(“#hc_select”).next().find(“dl”).css({ “display”: “none” });
}
}

}
});
简单说一下我的思路,首先select选择的值要能赋值给input框,可以就需要form.on(‘select(hc_select)’来监听select值的变化,选择了之后要把下拉列表给隐藏掉。同时重新渲染一下这个表单,只重新渲染当前的select也是可以的。
然后就是输入到input框里的文字如何去select中去搜索。首先我们通过检查select的dom结构可以发现,他里面的选项都是在dl下的dd标签中,如图。

 

image.png

然后我们获取到dl标签,然后通过循环的方式挨个去匹配dd中的选项与我们输入的文本是否存在关系。通过indexOf就行。如果不相似,则直接隐藏掉,然后这里为什么要定义一个j呢?是因为如果都不匹配的话,下面还是会出来一个空的dl标签,页面显示就是一个空的小列表,有点影响美观,所以如果你输入的文本和下拉列表中的选项都没关系的话,直接把dl给隐藏了。这里我是判断不相似的个数如果和dl.length 相等的话,就说明你输入的文本和select的选项没一个相似的,然后就可以把dl隐藏了。
study hard and make progress every day。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/u012869793/article/details/84453150

Visual Studio 必备插件集合:AI 助力开发 - 小码编匠 - 博客园

mikel阅读(172)

来源: Visual Studio 必备插件集合:AI 助力开发 – 小码编匠 – 博客园

一、前言

2024年AI浪潮席卷全球,编程界迎来全新的挑战与机遇。智能编程、自动化测试、代码审查,这一切都得益于AI技术的迅猛发展,它正在重塑开发者的日常,让编写代码变得更加高效、智能。

精选出最受好评、最具实用价值的Visual Studio插件,涵盖代码编辑、调试、测试、版本控制等多个方面,帮助开发者选择出正确的工具,提升开发效率和质量。无论是初出茅庐的编码新手,还是久经沙场的资深码农,总有一款插件能够满足你的需求,激发你的编程灵感。通过本文的介绍,将能够发现那些隐藏在Visual Studio插件市场中的瑰宝,让它们成为我们日常开发工作中的得力助手。

二、Visual Studio 插件安装指南

Visual Studio 通过安装插件,可以极大地扩展其功能,提高开发效率。无论是初学者还是经验丰富的开发者,都能轻松上手。

步骤一:启动Visual Studio

1、打开 Visual Studio开发工具。

2、如果尚未创建或打开项目,可以选择”创建新项目”或”打开现有项目”来启动Visual Studio,具体如下图所示:

步骤二:访问扩展和更新
在Visual Studio 顶部菜单中,点击”扩展” > “管理扩展”,具体如下图所示:

步骤三:搜索和选择插件

1、点击“管理扩展”标签页,具体如下图所示:2、在搜索框输入你想要安装的插件名称或关键词,例如”Resharper”、”CodeMaid”等。3、浏览搜索结果,查看插件的详细信息,包括描述、评分、评论和版本信息。

步骤四:安装插件

1、找到你需要的插件后,点击右侧的”下载”按钮。

2、下载完成后,按钮会变成”安装”,点击以开始安装过程。3、插件的大小和你的网络速度,安装过程可能需要几分钟时间。4、安装完毕后,可能需要重启Visual Studio才能使插件生效。

步骤五:启用插件

1、重启Visual Studio后,插件应该已经自动启用。

2、如果需要手动启用,可以在”工具”> “扩展和更新”中找到已安装的插件,确保其状态为”已启用”。

3、对于一些插件,可能还需要额外的配置,如设置快捷键或调整选项,这些通常可以在”工具” > “选项”中找到。

三、Visual Studio 如何高效搜索插件

Visual Studio庞大的插件库中提供了一些高效的搜索工具和技巧,可以帮助你快速定位到所需的插件。

1、插件市场

https://marketplace.visualstudio.com

2、使用关键词搜索是最直接的查找方式。在插件市场页面的搜索框中输入关键词,如”TONGYI Lingma”、”Fitten Code”。

3、利用筛选和排序功能,可以根据插件的类型、评级、更新日期等条件进行筛选。

4、点击搜索结果中的插件,可以查看插件的详细信息页面,包括插件的描述、截图、用户评价和版本历史等。

四、Visual Studio AI插件推荐

在Visual Studio中,有几个AI的插件可以帮助提升开发效率和代码质量,推荐实用的AI插件。

1、Fitten Code(免费)

Fitten Code 是由非十大模型驱动的AI编程助手,支持多种编程语言,支持主流几乎所有的IDE开发工具。包括VS Code、Visual Studio、JetBrains系列IDE(包括IntelliJ IDEA、PyCharm等)等,还适配了上古神器VIM。

  • 自动生成代码,提升开发效率。
  • 调试Bug,节省时间。
  • 对话聊天,解决编程问题。

官网文档:https://code.fittentech.com/tutor_vs_zh

2、CodeGeeX (免费)

CodeGeeX 是一款基于大模型的智能编程助手,它可以实现代码的生成与补全、自动为代码添加注释、自动解释代码、自动编写单元测试、实现代码审查Code Review、自动修复代码fixbug、自动生成commit message完成git提交,以及在不同编程语言的代码间实现互译、针对技术和代码问题的智能问答等丰富的功能。

帮助开发者显著提高工作效率,CodeGeeX支持100+种编程语言,适配多种主流IDE平台,包括Visual Studio Code,JetBrains IDEs,Visual Studio,HBuilderX,DeepIn-IDE等。

  • 代码生成与补全。
  • 自动为代码添加注释和解释。
  • 编写单元测试、代码审查(Code Review)。
  • 自动修复代码(fix bug)。
  • 自动生成commit message完成git提交。
  • 跨语言代码互译和智能问答。

官网文档:https://codegeex.cn/

适用场景:适用于需要快速生成代码原型或进行代码重构的场景。

3、通义灵码(个人版免费)

工具提供了AI代码建议和优化,可以帮助开发者更快地理解和编写代码。它已经在Visual Studio Code、和其他IDE中上线,现在也加入了Visual Studio的插件市场。

  • 智能代码补全
  • 代码风格优化
  • 码审查与错误检测
  • 智能问答与文档生成
  • 跨语言支持

官方文档:https://help.aliyun.com/document_detail/2590613.html

4、GitHub Copilot(付费)

GitHub Copilot是一款AI辅助工具,能够帮助开发者更快速、智能地编写高质量代码。

它支持多种编程语言,如Python、JavaScript、TypeScript、Ruby、Go、C#和C++等。

  • GitHub Copilot 会在你编码时提供建议:有时是当前行的补全,有时是全新的代码块。可以接受全部或部分建议,也可以忽略建议并继续键入。
  • 实时提供代码建议,从单行代码到完整函数。
  • 自动为代码添加注释,提高代码可读性。
  • 支持代码补全、解释和审查等功能。

五、Visual Studio 必备插件大全

1、ILSpy-2022(免费)

ILSpy 对于.NET开发者来说是一个宝贵的资源,无论是用于学习、逆向工程还是作为开发过程中的辅助工具。由于其开源性质,ILSpy持续得到社区的支持和更新,保持着与最新.NET版本的兼容性。可以集成在Visual Studio 开发工具中,能够十分快捷方便的查看源代码内容。

2、Visual-Studio-Translator (免费)

Visual-Studio-Translator 是一款专为 Visual Studio 开发者设计的强大翻译工具,在提升开发过程中的语言处理效率。

这款工具通过集成在 Visual Studio 编辑器中,使得开发者能够轻松实现代码、注释以及其他文本内容的即时翻译,极大地促进了跨语言编程和文档处理的便利性。

安装完成后,通过简单的配置即可开始使用。在需要翻译的内容上右键点击并选择”Translate”选项,或使用快捷键触发翻译功能,即可获得翻译结果。

快捷键

  • Google 翻译:(Ctrl + Shift + D, Ctrl + Shift + D)
  • 必应翻译:(Ctrl + Shift + D,ctrl + Shift + F)
  • 百度翻译:(Ctrl + Shift + D,ctrl + Shift + C)
  • 有道翻译:(Ctrl + Shift + D,ctrl + Shift + V)

3、CodeMaid(免费)

CodeMaid是一个功能强大的Visual Studio扩展插件,通过提供自动代码清理、格式化、重构等功能,提高代码质量、提升开发效率并减少错误率。

支持Visual Studio版本:从VS 2005至VS 2022(最新版本主要支持 VS 2019和 VS 2022)

  • 代码整理与格式化:自动整理白色空间,包括缩进、空格、空行等,提供多种格式化方式,支持批量格式化,可选择”保存时自动清理”,实现每次保存代码时自动格式化。
  • 注释整理:自动整理注释内容,去除不必要的换行和空格。调整注释样式,使其更加规范和整洁。
  • 代码重构:按照Microsoft的Style Copy规范或自定义规则重新排列成员。自动创建匹配的区域,优化代码结构。
  • 代码审查:在代码审查过程中,帮助识别潜在的问题和不规范的代码。
  • 其他辅助功能:支持多行代码合并为一行,按字母排序代码,删除已选的#region。一键折叠解决方案和快速定义文档在解决方案的位置。

4、CSharpier(免费)

CSharpier 是一个开源、免费且轻量级的 C# 代码格式化工具,它基于 Roslyn 编译器框架来解析和重新格式化 C# 代码。

这个工具的设计目的是使代码风格保持一致,从而提高代码的可读性和可维护性。

  • 智能格式化:使用 Roslyn 编译器框架深入理解 C# 语法结构,从而做出更加智能的格式决策。
  • 高度定制化:虽然设计哲学倾向于提供有限的选项,但 CSharpier 仍允许一定程度的定制,以满足不同团队的需求。
  • 无缝集成:支持多种集成开发环境(IDE),如 Visual Studio(2019和2022版本)、Visual Studio Code 等,可以在这些环境中实现保存时自动格式化代码。

提供有限但关键的选项,确保代码格式的一致性,减少开发者在代码风格上的争论。

5、XAML Styler for Visual Studio 2022(免费)

XAML Styler for Visual Studio 2022 是一款专为Visual Studio 2022设计的免费扩展插件,帮助开发者格式化XAML代码,提升代码的可读性和一致性。

  • 自动化格式化:支持多种格式化选项,如属性排序、换行规则、空格处理等。
  • 提高可读性:通过格式化,XAML代码变得更加清晰、易于阅读和理解。
  • 灵活配置:用户可以根据自己的偏好和需求配置XAML Styler的格式化选项。
  • 集成到Visual Studio:XAML Styler无缝集成到开发环境中。
  • 支持多种XAML相关文件:不仅限于标准的XAML文件,还可能支持其他与XAML相关的文件类型,如XAML资源字典等。

6、Indent Guides for VS 2022(免费)

Indent Guides 是一个流行的 Visual Studio 插件,用于改善代码阅读性和可维护性。这个插件的主要功能是在代码编辑器中添加垂直引导线,这些引导线帮助开发者视觉上对齐代码块,尤其是那些与大括号 {} 相关的代码块。

以下是 Indent Guides 插件的一些关键特性和优势:

  • 垂直引导线:插件在每个代码块的左侧添加细线,这些线与大括号 {} 对齐,帮助开发者直观地看到哪些代码属于同一代码块。这在处理嵌套结构时尤其有用。
  • 增强代码结构的可见性:引导线提高了代码结构的清晰度,使开发者能够迅速识别出代码块的开始和结束位置,即使是在长文件中。
  • 提升代码可读性:通过视觉辅助,Indent Guides 可以帮助减少代码阅读时的认知负荷,使得代码更容易理解和维护。
  • 适用于多种语言:插件支持多种编程语言,包括 C#, VB.NET, C++, JavaScript, TypeScript 等,只要这些语言使用大括号来界定代码块。
  • 自定义选项:开发者可以根据个人偏好调整引导线的颜色和可见性。例如,可以选择在代码编辑器中总是显示引导线,或者仅在代码折叠时显示。
  • 安装和使用:Indent Guides 可以直接从 Visual Studio 的扩展管理器中搜索并安装。一旦安装,它会自动应用于所有支持的语言项目中,但在某些情况下可能需要重启 Visual Studio 才能使更改生效。
  • 开源性质:Indent Guides 是一个开源项目,这意味着它可以免费使用,并且开发者社区可以贡献代码来改进插件。

7、Viasfora(免费)

Viasfora是一个功能强大的工具,既可以作为Visual Studio的免费扩展来改善文本编辑体验,也可以作为Python中的可视化编程库来创建交互式图形和可视化。根据不同的应用场景和需求,Viasfora提供了丰富的功能和自定义选项,以满足开发者的不同需求。

  • 彩虹括号:这是Viasfora最为著名的功能之一,它通过使用不同的颜色来区分嵌套的大括号、小括号和方括号,从而帮助开发者更清晰地跟踪代码结构。
  • 关键字高亮:Viasfora能够高亮显示编程语言中的关键字,如流程控制关键字(if、for、while等)和LINQ查询关键字,提高代码的可读性。

  • 其他功能:包括自动展开折叠的区域、演示模式、转义序列和格式说明符的高亮等。

开发者可以通过Visual Studio的“选项 – 环境 – 字体和颜色”或Viasfora自身的设置界面来自定义关键字的颜色、彩虹括号的颜色深度等。

8、SQL Search(免费)

SQL Search 是由 Redgate 软件公司开发的一款 SQL Server Management Studio (SSMS) 和 Visual Studio 的插件,其主要功能是在 SQL Server 数据库中快速搜索 SQL 代码和文本。

它极大地提高了数据库开发和管理的效率,尤其是在大型项目中,其中可能包含数千个数据库对象和数百万行的 SQL 代码。

以下是 SQL Search 插件的一些关键特点:

  • 快速搜索:SQL Search 提供了快速搜索 SQL 代码的能力,能够在数据库、存储过程、函数、视图、触发器以及注释中查找特定的文本或模式。
  • 跨数据库搜索:它允许你同时在多个数据库中进行搜索,这在需要跨多个数据库环境查找相似代码或模式时特别有用。
  • 智能搜索:插件支持正则表达式和通配符,可以进行更复杂的搜索模式匹配。
  • 即时反馈:在输入搜索条件的同时,搜索结果会立即显示,无需等待完整的搜索过程完成。
  • 结果预览:搜索结果中可以直接预览匹配的代码片段,无需打开每一个对象来查看。
  • 结果导航:你可以从搜索结果中直接跳转到具体的数据库对象,便于修改和审查代码。
  • 历史记录与书签:SQL Search 记录搜索历史,并允许保存常用的搜索项作为书签,以便将来快速重复使用。
  • 轻量级与高性能:插件设计得十分轻巧,不会显著影响 SSMS 或 Visual Studio 的性能。
  • 无缝集成:它紧密地与 SSMS 和 Visual Studio 集成,提供一致的用户界面和流畅的工作流。
  • 版本控制友好:对于版本控制系统中的代码更改,SQL Search 也能提供有效的搜索和比较功能。

9、EFCore.Visualizer(免费)

EFCore.Visualizer 是一个专为 Microsoft Visual Studio 设计的免费插件,用于增强 Entity Framework Core (EF Core) 的开发体验。它的主要功能是提供一个可视化工具,让开发者能够直观地查看和理解 EF Core 查询计划,这对于调试和优化数据库查询性能至关重要,支持SQL Server和PostgreSQL两个数据库。

  • 查询计划可视化:这个插件使得在 Visual Studio 中直接查看和分析 EF Core 的 LINQ 查询转换成的 SQL 查询成为可能。通过图形化界面展示查询树,开发者可以更容易地识别查询中的瓶颈或低效部分。
  • 调试支持:当你在代码中设置断点并运行调试时,可以在 Watch 窗口中使用此插件来实时观察查询计划的变化。这对于理解运行时的查询行为非常有帮助。
  • 数据库兼容性:EFCore.Visualizer 支持多种数据库系统,包括 SQL Server 和 PostgreSQL,这意味着无论你使用哪种数据库,都可以利用该插件进行查询优化。
  • 易于使用:插件的安装和使用相对简单,可以通过 Visual Studio 的扩展管理器或者插件市场下载并安装。
  • 性能优化:通过识别和优化查询计划,EFCore.Visualizer 可以帮助开发者减少数据库查询的时间,从而提高应用程序的整体性能。

   SQL Server

     PostgreSQL

10、EF Core Power Tools(免费)

EF Core Power Tools 是一个非常有用的 Visual Studio 扩展,专为 Entity Framework Core (EF Core) 开发者设计。它提供了丰富的功能来帮助我们在开发过程中更高效地处理数据库和实体模型。

以下是一些主要的功能:

  • 反向工程(Reverse Engineering):EF Core Power Tools 允许你从现有的数据库中生成 EF Core 的数据访问层代码,包括 DbContext 类和实体模型类。这通常被称为“数据库优先”(Database-First)方法。
  • 数据库迁移管理:它提供了一个图形界面来管理数据库迁移,使你可以轻松创建、应用和回滚迁移。
  • 模型可视化:插件能够生成实体模型的图形表示,这有助于理解模型结构和关系。
  • 代码生成:可以生成 CRUD 方法和其他常用操作的代码模板。
  • CLI 工具:EF Core Power Tools CLI 是一个命令行工具,允许你在不打开 IDE 的情况下执行上述任务,这对于自动化工作流程和持续集成/持续部署 (CI/CD) 流程非常有用。
  • SQL Server DACPAC 支持:如果你使用 SQL Server 并且有 DACPAC 文件,那么你可以直接从 DACPAC 文件生成 EF Core 代码。
  • 连接字符串管理:插件简化了设置和管理连接字符串的过程。
  • 兼容性:支持 EF Core 的多个版本,包括但不限于 6.x 和 8.x 版本。

六、总结

本文介绍了Visual Studio插件的安装与搜索技巧,强调其对提升开发效率的价值。通过精确关键词搜索与用户评价筛选,快速锁定优质插件。

特别推荐AI增强型插件,如Fitten Code,智能代码建议加速开发。精选涵盖代码管理至设计优化的必备工具,全方位升级编程体验,打造高效开发环境。