فعالسازی قدم به قدم Roslyn refactoring افزونهی Roslynator
الف) فایل VSIX آنرا از اینجا دریافت کنید و سپس پسوند آنرا به zip تغییر دهید.
ب) این فایل zip را در پوشهای مشخص باز کنید.
ج) پس از باز کردن این فایل zip، دو فایل Roslynator.VisualStudio.Core.dll و Roslynator.VisualStudio.dll آنرا حذف کنید. این فایلها مخصوص نگارش کامل ویژوال استودیو هستند و در صورت وجود، با سیستم بارگذاری افزونههای OmniSharp تداخل میکنند.
د) در آخر مسیر زیر را گشوده:
%USERPROFILE%/.omnisharp
{ "RoslynExtensionsOptions": { "LocationPaths": [ "C:\\lib\\roslynator" ] } }
اکنون اگر VSCode را اجرا کنید، شاهد افزوده شدن امکانات Refactoring مخصوص افزونهی Roslynator به لیست Refactoring پیشفرض OmniSharp خواهید بود:
خودکار سازی دریافت، نصب و فعالسازی Roslyn refactoring افزونهی Roslynator
مراحل فوق را میتوان تبدیل به یک اسکریپت پاورشل کرد که با هر بار اجرای آن، به صورت خودکار کار دریافت و نصب این افزونه صورت گیرد:
Write-Host "Download, unzip and enable Roslynator for Visual Studio Code" $name = "josefpihrt.Roslynator2017" $url = "https://marketplace.visualstudio.com/items?itemName=$name" $currentDir = $PSScriptRoot $file = "$currentDir\Roslynator.zip" $pattern = "<script class=`"vss-extension`" defer=`"defer`" type=`"application\/json`">(.*?)<\/script>" $regex = [regex]"(?m)$pattern" Write-Host "Grab the home page of the $name." $dom = (New-Object Net.WebClient).DownloadString($url); if($dom -and $dom -match $pattern) { $matches = $regex.Match($dom) $jsonText = $matches[0].Groups[1] $json = ConvertFrom-Json $jsonText $version = $Json.versions[0].version # Parse the json in the page for the latest version number $parts = $name.Split(".") $publisher = $parts[0] $package = $parts[1] # Assemble the url for the vsix package $packageUrl = "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/$publisher/vsextensions/$package/$version/vspackage" Write-Host "Download the vsix package: $packageUrl" (New-Object Net.WebClient).DownloadFile($packageUrl, $file) Write-Host "Using $currentDir as the current dir." Write-Host "Unzip $file." $shellApp = new-object -com shell.application $zipFile = $shellApp.namespace($file) $destination = $shellApp.namespace($currentDir) $destination.Copyhere($zipFile.items(), 0x14)# overwrite and be silent Write-Host "Delete VS specific files. Otherwise they will interfere with the MEF services inside OmniSharp." Remove-Item "$currentDir\Roslynator.VisualStudio.Core.dll","$currentDir\Roslynator.VisualStudio.dll", "$currentDir\Roslynator.VisualStudio.pkgdef" $omnisharpJsonFilePath = "$env:USERPROFILE\.omnisharp\omnisharp.json"; Write-Host "Create $omnisharpJsonFilePath file." $omnisharpJson = @" {{ "RoslynExtensionsOptions": {{ "LocationPaths": [ "{0}" ] }} }} "@ -f $currentDir -Replace "\\","\\" $omnisharpJson | Out-File "$omnisharpJsonFilePath" -Confirm Write-Host "Done!" } else { Write-Host "Failed to find the packageUrl!" }
PS D:\Prog\1396\RoslynatorVSCode> .\update.ps1 Download, unzip and enable Roslynator for Visual Studio Code Grab the home page of the josefpihrt.Roslynator2017. Download the vsix package: https://marketplace.visualstudio.com/_apis/public/gallery/publishers/josefpihrt/vsextensions/Roslynator2017/1.7.0/vspackage Using D:\Prog\1396\RoslynatorVSCode as the current dir. Unzip D:\Prog\1396\RoslynatorVSCode\Roslynator.zip. Delete VS specific files. Otherwise they will interfere with the MEF services inside OmniSharp. Create C:\Users\Vahid\.omnisharp\omnisharp.json file. Confirm Are you sure you want to perform this action? Performing the operation "Output to File" on target "C:\Users\Vahid\.omnisharp\omnisharp.json". [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): Done!
در ادامه این افزونه دریافت شده و در پوشهی جاری باز خواهد شد. سپس فایل omnisharp.json نیز به صورت خودکار تشکیل و مقدار دهی میشود.
اکنون اگر VSCode را اجرا کنید، همه چیز آماده بوده و امکانات این افزونه در دسترس خواهند بود.
داشتن یک کتابخانهی مدیریت حالت برای برنامههای React بسیار مفید است؛ خصوصا اگر این برنامه پیچیده باشد و برای مثال در آن نیاز به اشتراک گذاری دادهها، بین دو کامپوننت یا بیشتر که در یک رده سلسه مراتبی قرار نمیگیرند، وجود داشته باشد. اما حتی اگر از یک کتابخانهی مدیریت حالت استفاده شود، شاید راه حلی را که ارائه میکند آنچنان تمیز و قابل انتظار نباشد. با MobX میتوان از ساختارهای پیچیدهی شیءگرا به سادگی استفاده کرد (mutation مستقیم اشیاء در آن مجاز است) و همچنین برای کار با آن به همراه React، نیاز به کدهای کمتری است نسبت به Redux. در اینجا از مفاهیم Reactive programming استفاده میشود؛ اما سعی میکند پیچیدگیهای آنرا مخفی کند. در نام MobX، حرف X به Reactive بودن آن اشاره میکند (مانند RxJS) و ob آن از observable گرفته شدهاست. M هم به حرف ابتدای نام شرکتی اشاره میکند که این کتابخانه را ایجاد کردهاست.
خواص محاسبه شده در جاوا اسکریپت
برای کار با MobX، نیاز است تا ابتدا با یکسری از مفاهیم آن آشنا شد؛ مانند خواص محاسبه شده (computed properties). برای مثال در اینجا یک کلاس متداول جاوا اسکریپتی را داریم:
class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } fullName() { return `${this.firstName} ${this.lastName}`; } }
const person = new Person('Vahid', 'N'); person.firstName; // 'Vahid' person.lastName; // 'N' person.fullName; // function fullName() {…}
در ES6 برای اینکه تنها با ذکر person.fullName بدون هیچ پرانتزی در مقابل آن بتوان به مقدار کامل fullName رسید، میتوان از روش زیر و با ذکر واژهی کلیدی get، در پیش از نام متد، استفاده کرد:
class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } get fullName() { return `${this.firstName} ${this.lastName}`; } }
اگر شبیه به همین قطعه کد را بخواهیم در ES5 پیاده سازی کنیم، روش آن به صورت زیر است:
function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } Object.defineProperty(Person.prototype, 'fullName', { get: function () { return this.firstName + ' ' + this.lastName; } });
اکنون فرض کنید قسمتی از state برنامهی React، قرار است خاصیت ویژهی fullName را نمایش دهد. برای اینکه UI برنامه با تغییرات نام و نام خانوادگی، متوجه تغییرات fullName که یک خاصیت محاسباتی است، شود و آنرا رندر مجدد کند، باید در طی یک حلقهی بینهایت، مدام آنرا فراخوانی کند و نتیجهی جدید را با نتیجهی قبلی محاسبه کرده و تغییرات را نمایش دهد. اینجا است که MobX یک چنین پیاده سازیهایی را به کمک مفهوم decorators، ساده میکند.
Decorators در جاوا اسکریپت
تزئین کنندهها یا decorators در سایر زبانهای برنامه نویسی نیز وجود دارند؛ اما پیاده سازی آنها در جاوا اسکریپت هنوز در مرحلهی آزمایشی است. Decorators در جاوا اسکریپت چیزی نیستند بجز بیان زیبای higher-order functions.
higher-order functions، توابعی هستند که توابع دیگر را با ارائهی قابلیتهای بیشتری، محصور میکنند. به همین جهت هر کاری را که بتوان با تزئین کنندهها انجام داد، همان را با توابع معمولی جاوا اسکریپتی نیز میتوان انجام داد. یک نمونه از این higher-order functions را در سری جاری تحت عنوان higher-order components با متد connect کتابخانهی react-redux مشاهده کردهایم. متد connect، متدی است که متدهای نگاشت state به props و نگاشت dispatch به props را دریافت کرده و سپس یک کامپوننت را نیز دریافت میکند و آنرا به صورت محصور شدهای ارائه میدهد تا بجای کامپوننت اصلی مورد استفاده قرار گیرد؛ به یک چنین کامپوننتهایی، higher-order components گفته میشود.
برای تعریف تزئین کنندهها، به نحوهی پیاده سازی Object.defineProperty در مثال فوق دقت کنید:
Object.defineProperty(Person.prototype, 'fullName', { enumerable: false, writable: false, get: function () { return this.firstName + ' ' + this.lastName; } });
در ذیل روش تعریف یک تزئین کننده را مشاهده میکنید که دقیقا از یک چنین الگویی پیروی میکند:
function decoratorName(target, key, descriptor) { // … }
function readonly(target, key, descriptor) { descriptor.writable = false; return descriptor; }
class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } @readonly get fullName() { return `${this.firstName} ${this.lastName}`; } }
مثالهایی از تزئین کنندهها
برای نمونه میتوان تزئین کنندهی bindThis@ را طراحی کرد تا کار bind شیء this را به متدهای کامپوننتهای React انجام دهد و یا کتابخانهای به نام core-decorators وجود دارد که به صورت زیر نصب میشود:
> npm install core-decorators
@autobind @deprecate @readonly @memoize @debounce @profile
نمونهی دیگری از این کتابخانهها lodash-decorators است که تعدادی دیگر از تزئین کنندهها را ارائه میکند.
MobX چگونه کار میکند؟
انجام یکسری از کارها با Redux مشکل است؛ برای مثال تغییر دادن یک شیء تو در توی پیچیده که شامل تهیهی یک کپی از آن، اعمال تغییرات و غیرهاست. اما با MobX میتوان با اشیاء جاوا اسکریپتی به همان صورتی که هستند کار کرد. برای مثال آرایهای را با متدهای push و pop تغییر داد (mutation اشیاء مجاز است) و یا خواص اشیاء را به صورت مستقیم ویرایش کرد، در این حالت MobX اعلام میکند که ... من میدانم که چه تغییری صورت گرفتهاست. بنابراین سبب رندر مجدد UI خواهم شد.
ایجاد یک برنامهی خالی React برای آزمایش MobX
در اینجا برای بررسی MobX، یک پروژهی جدید React را ایجاد میکنیم:
> create-react-app state-management-with-mobx-part1 > cd state-management-with-mobx-part1 > npm start
> npm install --save mobx
مثالی از MobX، مستقل از React
در اینجا نیز همانند روشی که در بررسی Redux در پیش گرفتیم، ابتدا MobX را به صورت کاملا مستقل از React، با یک مثال بررسی میکنیم و سپس در قسمتهای بعد آنرا به React متصل میکنیم. برای این منظور ابتدا فایل src\index.js را به صورت زیر تغییر میدهیم:
import { autorun, observable } from "mobx"; import React from "react"; import ReactDOM from "react-dom"; ReactDOM.render( <div> <input type="text" id="text-input" /> <div id="text-display"></div> <div id="text-display-uppercase"></div> </div>, document.getElementById("root") ); const input = document.getElementById("text-input"); const textDisplay = document.getElementById("text-display"); const loudDisplay = document.getElementById("text-display-uppercase"); console.log({ observable, autorun, input, textDisplay, loudDisplay });
- با استفاده از observable میخواهیم تغییرات یک شیء جاوا اسکریپتی را تحت نظر قرار داده و هر زمانیکه تغییری در شیء رخ داد، از آن مطلع شویم.
برای مثال شیء سادهی جاوا اسکریپتی زیر را در نظر بگیرید:
{ value: "Hello world!", get uppercase() { return this.value.toUpperCase(); } }
به همین جهت اینبار شیء فوق را توسط یک observable ارائه میدهیم، تا بتوانیم به تغییرات خواص آن گوش فرا دهیم:
const text = observable({ value: "Hello world!", get uppercase() { return this.value.toUpperCase(); } });
input.addEventListener("keyup", event => { text.value = event.target.value; });
autorun(() => { textDisplay.textContent = text.value; loudDisplay.textContent = text.uppercase; });
برای آزمایش آن، برنامه را اجرا کرده و متنی را داخل textbox وارد کنید:
نکتهی جالب اینجا است که هرچند فقط خاصیت value را تغییر دادهایم (تغییر مستقیم خواص یک شیء؛ بدون نیاز به ساخت یک clone از آن)، اما خاصیت محاسباتی uppercase نیز به روز رسانی شدهاست.
زمانیکه mobx را به یک برنامهی React متصل میکنیم، قسمت autorun، از دید ما مخفی خواهد بود. در این حالت فقط یک شیء معمولی جاوا اسکریپتی را مستقیما تغییر میدهیم و ... در نتیجهی آن رندر مجدد UI صورت خواهد گرفت.
یک observable چگونه کار میکند؟
در اینجا یک شبهکد را که بیانگر نحوهی عملکرد یک observable است، مشاهده میکنید:
const onChange = (oldValue, newValue) => { // Tell MobX that this value has changed. } const observable = (value) => { return { get() { return value; }, set(newValue) { onChange(this.get(), newValue); value = newValue; } } }
کدهای کامل این قسمت را میتوانید از اینجا دریافت کنید: state-management-with-mobx-part1.zip
یک نکتهی تکمیلی: نحوهی نامگذاری ویژهی عناصر در فرمهای جدید Blazor SSR
اگر با نگارشهای دیگر Blazor کار کرده باشید، عموما یک EditForm را به صفحه اضافه کرده و چند المان را به آن اضافه میکنیم و ... کار میکند. حتی اگر کامپوننتهای سفارشی را هم بر این مبنا تهیه کنیم ... بازهم بدون نکتهی خاصی کار میکنند. اما ... در برنامههای Blazor SSR اینطور نیست! زمانیکه برای مثال مدل فرم را به این صورت تعریف میکنیم:
[SupplyParameterFromForm] public OrderPlace? MyModel { get; set; }
و آنرا به نحو متداولی در صفحه نمایش میدهیم:
<InputText @bind-Value="MyModel.City"/>
اگر به المان رندر شدهی در مرورگر مراجعه کنیم، ویژگی name حاصل، با MyModel.City مقدار دهی شدهاست و ... این موضوع درج نام خاصیت مدل (و یا اصطلاحا Html Field Prefix)، برای Blazor SSR بسیار مهم است! تاحدی که اگر از آن آگاه نباشید، ممکن است ساعتی را مشغول دیباگ برنامه شوید که چرا، مقدار نالی را دریافت کردهاید و یا عناصر تعریف شدهی در کامپوننتهای سفارشی، کار نمیکنند و مقدار نمیگیرند!
متاسفانه API بازگشت نام کامل عناصری که توسط Blazor SSR تولید میشود، عمومی نیست و internal است. اگر از کامپوننتهای استاندارد خود Blazor استفاده میکنید، نیازی نیست تا به این موضوع فکر کنید و مدیریت آن خودکار است؛ اما همینکه قصد تولید کامپوننتهای سفارشی مخصوص SSR را داشته باشید، اولین مشکلی را که با آن مواجه خواهید شد، دقیقا همین مسالهی تولید صحیح HtmlFieldPrefixها است.
برای رفع این مشکل و دسترسی به API پشت صحنهی تولید نام فیلدها در Blazor SSR، میتوان از کامپوننت پایهی InputBase خود Blazor ارثبری کرد و به این ترتیب به خاصیت جدید NameAttributeValue آن دسترسی یافت (این خاصیت به داتنت 8 و مخصوص Blazor SSR، اضافه شدهاست) که اینکار در کلاس BlazorHtmlField انجام شدهاست. روش استفادهی از آن هم به صورت زیر است:
private BlazorHtmlField<T?> ValueField => new(ValueExpression ?? throw new InvalidOperationException(message: "Please use @bind-Value here.")); [Parameter] public T? Value { set; get; } [Parameter] public EventCallback<T?> ValueChanged { get; set; } [Parameter] public Expression<Func<T?>> ValueExpression { get; set; } = default!;
زمانیکه میخواهیم در یک کامپوننت سفارشی، خاصیتی bind پذیر را طراحی کنیم، روش کار آن، مانند مثال فوق است که به همراه یک خاصیت، یک EventCallback و یک Expression است تا اعتبارسنجی و انقیاد دوطرفه را فعال کند. اما ... اگر همین Value را مستقیما در فیلدهای کامپوننت استفاده کنیم ... مقدار نمیگیرد؛ چون به همراه نام کامل خاصیت بایند شدهی به آن نیست. برای مثال بجای MyModel.City فقط City درج میشود (که به علت نداشتن .MyModel، سیستم binding از مقدار آن صرفنظر میکند). اکنون با استفاده از BlazorHtmlField فوق، میتوان به نام کامل تولیدی توسط Blazor SSR دسترسی یافت و از آن استفاده کرد:
<input type="text" dir="ltr" name="@ValueField.HtmlFieldName" id="@ValueField.HtmlFieldName" />
HtmlFieldName ای که در اینجا درج میشود، توسط خود Blazor محاسبه شده و با انتظارات موتور binding آن تطابق دارد و دیگر به خواص بایند شدهای که مقدار نمیگیرند، نخواهیم رسید.
ایجاد کاتالوگهای Full text search و ایندکسهای آن
همانطور که در قسمت قبل نیز عنوان شد، فیلترهای FTS آفیس، علاوه بر اینکه امکان جستجوی پیشرفته FTS را بر روی کلیه فایلهای مجموعه آفیس میسر میکنند، امکان جستجوی FTS را بر روی خواص ویژه اضافی آنها، مانند نام نویسنده، واژههای کلیدی، تاریخ ایجاد و امثال آن نیز به همراه دارند.
اینکه چه خاصیتی را بتوان جستجو کرد نیز بستگی به نوع فیلتر نصب شده دارد. برای تعریف خواص قابل جستجوی یک سند، باید یک SEARCH PROPERTY LIST را ایجاد کرد:
CREATE SEARCH PROPERTY LIST WordSearchPropertyList; GO ALTER SEARCH PROPERTY LIST WordSearchPropertyList ADD 'Authors' WITH (PROPERTY_SET_GUID = 'F29F85E0-4FF9-1068-AB91-08002B27B3D9', PROPERTY_INT_ID = 4, PROPERTY_DESCRIPTION = 'System.Authors - authors of a given item.'); GO
بهبود کیفیت جستجو توسط Stop lists و Stop words
به یک سری از کلمات و حروف، اصطلاحا noise words گفته میشود. برای مثال در زبان انگلیسی حروف و کلماتی مانند a، is، the و and به صورت خودکار از FTS حذف میشوند؛ چون جستجوی آنها بیحاصل است. به اینها stop words نیز میگویند.
با استفاده از کوئری ذیل میتوان لیست stop words تعریف شده در بانک اطلاعاتی جاری را مشاهده کرد:
-- Check the Stopwords list SELECT w.stoplist_id, l.name, w.stopword, w.language FROM sys.fulltext_stopwords AS w INNER JOIN sys.fulltext_stoplists AS l ON w.stoplist_id = l.stoplist_id;
-- Stopwords list CREATE FULLTEXT STOPLIST SQLStopList; GO -- Add a stopword ALTER FULLTEXT STOPLIST SQLStopList ADD 'SQL' LANGUAGE 'English'; GO
کاتالوگهای Full Text Search
ایندکسهای ویژهی FTS، در مکانهایی به نام Full Text Catalogs ذخیره میشوند. این کاتالوگها صرفا یک شیء مجازی بوده و تنها برای تعریف ظرفی دربرگیرندهی ایندکسهای FTS تعریف میشوند. در نگارشهای پیش از 2012 اس کیوال سرور، این کاتالوگها اشیایی فیزیکی بودند؛ اما اکنون تبدیل به اشیایی مجازی شدهاند.
حالت کلی تعریف یک fulltext catalog به نحو ذیل است:
create fulltext catalog catalog_name on filegroup filegroup_name in path 'rootpath' with some_options as default authoriztion owner_name accent_sensivity = {on|off}
به صورت پیش فرض حساسیت به لهجه یا accent_sensivity خاموش است. اگر روشن شود، باید کل ایندکس مجددا بازسازی شود.
ایجاد ایندکسهای Full Text
پس از ایجاد یک fulltext catalog، اکنون نوبت به تعریف ایندکسهایی فیزیکی هستند که داخل این کاتالوگها ذخیره خواهند شد:
-- Full-text catalog CREATE FULLTEXT CATALOG DocumentsFtCatalog; GO -- Full-text index CREATE FULLTEXT INDEX ON dbo.Documents ( docexcerpt Language 1033, doccontent TYPE COLUMN doctype Language 1033 STATISTICAL_SEMANTICS ) KEY INDEX PK_Documents ON DocumentsFtCatalog WITH STOPLIST = SQLStopList, SEARCH PROPERTY LIST = WordSearchPropertyList, CHANGE_TRACKING AUTO; GO
CHANGE_TRACKING AUTO به این معنا است که SQL Server به صورت خودکار کار به روز رسانی این ایندکس را با تغییرات رکوردها انجام خواهد داد.
ذکر STATISTICAL_SEMANTICS، منحصر به SQL Server 2012 بوده و کار آن تشخیص واژههای کلیدی و ایجاد ایندکسهای یافتن اسناد مشابه است. برای استفاده از آن حتما نیاز است مطابق توضیحات قسمت قبل، Semantic Language Database پیشتر نصب شده باشد.
توسط STOPLIST، لیست واژههایی که قرار نیست ایندکس شوند را معرفی خواهیم کرد. SQLStopList را در ابتدای بحث ایجاد کردیم.
Language 1033 به معنای استفاده از زبان US English است.
نحوهی استفاده از SEARCH PROPERTY LIST ایی که پیشتر تعریف کردیم را نیز در اینجا ملاحظه میکنید.
مثالی برای ایجاد ایندکسهای FTS
برای اینکه ربط منطقی نکات عنوان شده را بهتر بتوانید بررسی و آزمایش کنید، مثال ذیل را درنظر بگیرید.
ابتدا جدول Documents را برای ذخیره سازی تعدادی سند، ایجاد میکنیم:
CREATE TABLE dbo.Documents ( id INT IDENTITY(1,1) NOT NULL, title NVARCHAR(100) NOT NULL, doctype NCHAR(4) NOT NULL, docexcerpt NVARCHAR(1000) NOT NULL, doccontent VARBINARY(MAX) NOT NULL, CONSTRAINT PK_Documents PRIMARY KEY CLUSTERED(id) );
سپس اطلاعاتی را در این جدول ثبت میکنیم:
-- Insert data -- First row INSERT INTO dbo.Documents (title, doctype, docexcerpt, doccontent) SELECT N'Columnstore Indices and Batch Processing', N'docx', N'You should use a columnstore index on your fact tables, putting all columns of a fact table in a columnstore index. In addition to fact tables, very large dimensions could benefit from columnstore indices as well. Do not use columnstore indices for small dimensions. ', bulkcolumn FROM OPENROWSET (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\ColumnstoreIndicesAndBatchProcessing.docx', SINGLE_BLOB) AS doc; -- Second row INSERT INTO dbo.Documents (title, doctype, docexcerpt, doccontent) SELECT N'Introduction to Data Mining', N'docx', N'Using Data Mining is becoming more a necessity for every company and not an advantage of some rare companies anymore. ', bulkcolumn FROM OPENROWSET (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\IntroductionToDataMining.docx', SINGLE_BLOB) AS doc; -- Third row INSERT INTO dbo.Documents (title, doctype, docexcerpt, doccontent) SELECT N'Why Is Bleeding Edge a Different Conference', N'docx', N'During high level presentations attendees encounter many questions. For the third year, we are continuing with the breakfast Q&A session. It is very popular, and for two years now, we could not accommodate enough time for all questions and discussions! ', bulkcolumn FROM OPENROWSET (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\WhyIsBleedingEdgeADifferentConference.docx', SINGLE_BLOB) AS doc; -- Fourth row INSERT INTO dbo.Documents (title, doctype, docexcerpt, doccontent) SELECT N'Additivity of Measures', N'docx', N'Additivity of measures is not exactly a data warehouse design problem. However, you have to realize which aggregate functions you will use in reports for which measure, and which aggregate functions you will use when aggregating over which dimension.', bulkcolumn FROM OPENROWSET (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\AdditivityOfMeasures.docx', SINGLE_BLOB) AS doc; GO
fts_docs.zip
در ادامه میخواهیم قادر باشیم تا بر روی متادیتای نویسندهی این اسناد نیز جستجوی کامل FTS را انجام دهیم. به همین جهت SEARCH PROPERTY LIST آنرا نیز ایجاد خواهیم کرد:
-- Search property list CREATE SEARCH PROPERTY LIST WordSearchPropertyList; GO ALTER SEARCH PROPERTY LIST WordSearchPropertyList ADD 'Authors' WITH (PROPERTY_SET_GUID = 'F29F85E0-4FF9-1068-AB91-08002B27B3D9', PROPERTY_INT_ID = 4, PROPERTY_DESCRIPTION = 'System.Authors - authors of a given item.'); GO
-- Stopwords list CREATE FULLTEXT STOPLIST SQLStopList; GO -- Add a stopword ALTER FULLTEXT STOPLIST SQLStopList ADD 'SQL' LANGUAGE 'English'; GO
اکنون زمان ایجاد یک کاتالوگ FTS است:
-- Full-text catalog CREATE FULLTEXT CATALOG DocumentsFtCatalog; GO
و در آخر ایندکس FTS ایی را که پیشتر در مورد آن بحث کردیم، ایجاد خواهیم کرد:
-- Full-text index CREATE FULLTEXT INDEX ON dbo.Documents ( docexcerpt Language 1033, doccontent TYPE COLUMN doctype Language 1033 STATISTICAL_SEMANTICS ) KEY INDEX PK_Documents ON DocumentsFtCatalog WITH STOPLIST = SQLStopList, SEARCH PROPERTY LIST = WordSearchPropertyList, CHANGE_TRACKING AUTO; GO
در این تصویر محل یافتن اجزای مختلف Full text search را در management studio مشاهده میکنید.
یک نکتهی تکمیلی
برای زبان فارسی نیز یک سری stop words وجود دارند. لیست آنها را از اینجا میتوانید دریافت کنید:
stopwords.sql
متاسفانه زبان فارسی جزو زبانهای پشتیبانی شده توسط FTS در SQL Server نیست (نه به این معنا که نمیتوان با آن کار کرد؛ به این معنا که برای مثال دستورات صرفی زبان را ندارد) و به همین جهت از زبان انگلیسی در اینجا استفاده شدهاست.
یکی از نیازهای تهیه یک گزارش خوب، تکرار سرستونها در صفحات مختلف است. شاید در ابتدا این ایده مطرح شود که مثلا میخواهیم 25 ردیف را در هر صفحه نمایش دهیم. بر همین اساس میتوان هر 25 ردیف یکبار، یک سطر footer و در ادامه در صفحه بعد یک سطر header را اضافه کرد و همینطور الی آخر. مهمترین ایراد این روش آن است که الزامی ندارد که واقعا 25 ردیف در یک صفحه جا شوند. عموما بر اساس اندازهی محتوای نمایش داده شده، ممکن است یک صفحه 20 ردیف شود، صفحهای دیگر 10 ردیف. این مورد تمام محاسبات را به هم خواهد ریخت. به همین جهت دو خاصیت مهم به نامهای HeaderRows و FooterRows در شیء PdfPTable قابل تنظیم است. این دو خاصیت نیاز به اندکی توضیح دارند که در ادامه ذکر خواهد شد:
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
namespace HeadersAndFooters
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();
var table1 = new PdfPTable(1);
table1.HeaderRows = 2;
table1.FooterRows = 1;
//header row
table1.AddCell(new Phrase("header"));
//footer row
table1.AddCell(new Phrase("footer"));
//adding some rows
for (int i = 0; i < 70; i++)
{
table1.AddCell(new Phrase("Row " + i));
}
pdfDoc.Add(table1);
}
//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}
HeaderRows = 2 به این معنا است که 2 سطری را که بلافاصله در ادامه اضافه میکنید، در محاسبات نمایش خودکار header یا footer قرار میگیرند. FooterRows = 1 به این معنا است که از این تعداد، آخرین سطر، معنای footer را میدهد. بنابراین اولین table1.AddCell ، همان header خودکار نمایش داده شده در بالای تمام صفحات خواهد بود و table1.AddCell بعدی جهت نمایش footer خودکار بکار میرود. این دو سطر کاربرد دیگری ندارند.
مثالی دیگر جهت مشخص شدن این مفاهیم:
table1.HeaderRows = 3;
table1.FooterRows = 1;
از این طراحی لذت میبرید؟!
Blazor 5x - قسمت هفتم - مبانی Blazor - بخش 4 - انتقال اطلاعات از کامپوننتهای فرزند به کامپوننت والد
<div class="bg-light border p-2 col-5 offset-1 mt-2" @onclick="@(() => AmenitySelectionChanged(Amenity.Name))"> <h4 class="text-secondary">Amenity - @Amenity.Id</h4> @Amenity.Name<br /> @Amenity.Description<br /> </div> @code { [Parameter] public BlazorAmenity Amenity { get; set; } [Parameter] public EventCallback<string> OnAmenitySelection { get; set; } protected async Task AmenitySelectionChanged(string name) { await OnAmenitySelection.InvokeAsync(name); } }
<div condition="Model.Approved"> <p> This website has <strong surround="em">@Model.Approved</strong> been approved yet. Visit www.contoso.com for more information. </p> </div> <div condition="!User.Identity.IsAuthenticated">I'm not a valid user</div> <div condition="User.Identity.IsAuthenticated">I can use the system</div>