سلام، ببینید من ورژنهای متعددی که برای دانلود نسخه 2012 قرار داده شده رو به همراه حجمشون پایین نوشتم. به عنوان مبتدی میخواستم بدونم چه وقت از کدام یک از اینها استفاده میشه؟
یک حالتی هست که ویژوال استودیو از من میخواد نسخه اکسپرس رو نصب کنم، خوب نسخه ای با حجم و امکانات بالا رو نصب میکنم. یک حالتی هم هست که بصورت توکار در یک برنامه قرار داده میشه که کاربر نهایی در اجرای برنامه به مشک برنخوره، حالا چطور متوجه بشیم که کدوم نسخه برای کاربر نهایی مناسبه.
تشکر
SQL Server Express with Tools (with LocalDB, Includes the database engine and SQL Server Management Studio Express) 1.4G
SQL Server Management Studio (Tools only) 939MB
SQL Server Express LocalDB (MSI installer) 24MB
SQL Server Express with Advanced Services (contains the database engine, Express Tools, Reporting Services, and Full Text Search) 1.7G
SQL Server Express (Containing only the database engine) 133MB
در مورد افزونه YSlow افزونه Firebug فایرفاکس پیشتر صحبت شد. این افزونه پس از آنالیز یک سایت، پیشنهاداتی را نیز جهت بهبود سرعت، ارائه میدهد.
همانطور که در شکل بالا مشخص است، عناصری مانند css و js ، قسمت expires اشان (تاریخ منقضی شدن کش آنها در سمت کلاینت) خالی است و پیشنهاد داده که به هر کدام از این عناصر، هدر مخصوص مشخص سازی مدت زمان کش شدن در سمت کلاینت اضافه شود.
ASP.Net در مورد کش کردن اطلاعات صفحات پویا به اندازهی کافی امکانات در اختیار برنامه نویس قرار میدهد اما در مورد اضافه کردن این هدر جهت یک فایل css غیر پویا شاید نتوان مطلب خاصی را یافت.
در IIS7 امکانات ویژهای برای این منظور در نظر گرفته شده که نحوه استفاده از آن در ASP.Net به صورت زیر است:
فایل وب کانفیگ سایت را باز کرده و به قسمت system.webServer چند سطر زیر را اضافه کنید:
<staticContent>
<clientCache httpExpires="Sun, 29 Mar 2020 00:00:00 GMT" cacheControlMode="UseExpires" />
</staticContent>
این مورد فقط مختص به IIS7 است و بر روی نگارشهای پایینتر کار نمیکند.
با این کار، تاریخ منقضی شدن هر آنچه که توسط موتور ASP.net سرو نمیشود به سال 2020 تنظیم خواهد شد. (کلیه محتوای غیرپویای سایت، اعم از تصاویر، فایلهای css ، js و غیره)
پس از این تنظیم مجددا YSlow را اجرا کرده و Performance Grade ایی را که نمایش میدهد بررسی نمائید.
بدیهی است اگر یکی از فایلهای css یا js شما تغییر کند، کلاینت، اطلاعات جدیدی را تا سال 2020 دریافت نمیکند. برای حل این مشکل یک کوئری استرینگ ساده به انتهای لینک مربوط به css یا js خود اضافه کنید تا URL جدید با URL قبلی آن یکسان نباشد (این کوئری استرینگ تاثیری روی محتوای ایستای ما ندارد). به این صورت این آدرس جدید، مجددا دریافت شده و تا سال 2020 کش خواهد شد.
نکته:
اعمال تنظیم فوق، در IIS7 ویندوز سرور 2008 مجاز است؛ اما در IIS7 ویندوز ویستا قفل شده است و قابل override نیست. برای تغییر آن، فایل زیر را پیدا کنید:
open %systemroot%\System32\inetsrv\config\applicationHost.config
<section name="staticContent" overrideModeDefault="Deny" />
<section name="staticContent" overrideModeDefault="Allow" />
مآخذ:
YSlow: Add expires header to images in IIS 7
IIS7: How to set cache control for static content?
مطالب دورهها
ایجاد کاتالوگهای Full text search و ایندکسهای آن
جستجو بر روی خواص و متادیتای اسناد آفیس
همانطور که در قسمت قبل نیز عنوان شد، فیلترهای FTS آفیس، علاوه بر اینکه امکان جستجوی پیشرفته FTS را بر روی کلیه فایلهای مجموعه آفیس میسر میکنند، امکان جستجوی FTS را بر روی خواص ویژه اضافی آنها، مانند نام نویسنده، واژههای کلیدی، تاریخ ایجاد و امثال آن نیز به همراه دارند.
اینکه چه خاصیتی را بتوان جستجو کرد نیز بستگی به نوع فیلتر نصب شده دارد. برای تعریف خواص قابل جستجوی یک سند، باید یک SEARCH PROPERTY LIST را ایجاد کرد:
در این تعریف، PROPERTY_INT_ID و PROPERTY_SET_GUIDها استاندارد بوده و لیست آنها را در آدرس ذیل میتوانید مشاهده نمائید:
بهبود کیفیت جستجو توسط Stop lists و Stop words
به یک سری از کلمات و حروف، اصطلاحا noise words گفته میشود. برای مثال در زبان انگلیسی حروف و کلماتی مانند a، is، the و and به صورت خودکار از FTS حذف میشوند؛ چون جستجوی آنها بیحاصل است. به اینها stop words نیز میگویند.
با استفاده از کوئری ذیل میتوان لیست stop words تعریف شده در بانک اطلاعاتی جاری را مشاهده کرد:
و برای تعریف stop words از دستورات ذیل کمک گرفته میشود:
کاتالوگهای Full Text Search
ایندکسهای ویژهی FTS، در مکانهایی به نام Full Text Catalogs ذخیره میشوند. این کاتالوگها صرفا یک شیء مجازی بوده و تنها برای تعریف ظرفی دربرگیرندهی ایندکسهای FTS تعریف میشوند. در نگارشهای پیش از 2012 اس کیوال سرور، این کاتالوگها اشیایی فیزیکی بودند؛ اما اکنون تبدیل به اشیایی مجازی شدهاند.
حالت کلی تعریف یک fulltext catalog به نحو ذیل است:
اما اکثر گزینههای آن مانند on filegroup و in path صرفا برای حفظ سازگاری با نگارشهای قبلی حضور دارند و دیگر نیازی به ذکر آنها نیست؛ چون تعریف کنندهی ماهیت فیزیکی این کاتالوگها میباشند.
به صورت پیش فرض حساسیت به لهجه یا accent_sensivity خاموش است. اگر روشن شود، باید کل ایندکس مجددا بازسازی شود.
ایجاد ایندکسهای Full Text
پس از ایجاد یک fulltext catalog، اکنون نوبت به تعریف ایندکسهایی فیزیکی هستند که داخل این کاتالوگها ذخیره خواهند شد:
در اینجا توسط KEY INDEX نام منحصربفرد ایندکس مشخص میشود.
CHANGE_TRACKING AUTO به این معنا است که SQL Server به صورت خودکار کار به روز رسانی این ایندکس را با تغییرات رکوردها انجام خواهد داد.
ذکر STATISTICAL_SEMANTICS، منحصر به SQL Server 2012 بوده و کار آن تشخیص واژههای کلیدی و ایجاد ایندکسهای یافتن اسناد مشابه است. برای استفاده از آن حتما نیاز است مطابق توضیحات قسمت قبل، Semantic Language Database پیشتر نصب شده باشد.
توسط STOPLIST، لیست واژههایی که قرار نیست ایندکس شوند را معرفی خواهیم کرد. SQLStopList را در ابتدای بحث ایجاد کردیم.
Language 1033 به معنای استفاده از زبان US English است.
نحوهی استفاده از SEARCH PROPERTY LIST ایی که پیشتر تعریف کردیم را نیز در اینجا ملاحظه میکنید.
مثالی برای ایجاد ایندکسهای FTS
برای اینکه ربط منطقی نکات عنوان شده را بهتر بتوانید بررسی و آزمایش کنید، مثال ذیل را درنظر بگیرید.
ابتدا جدول Documents را برای ذخیره سازی تعدادی سند، ایجاد میکنیم:
اگر به این جدول دقت کنید، هدف از آن ذخیرهی اسناد آفیس است که فیلترهای FTS آنرا در قسمت قبل نصب کردیم. ستون doctype، معرف نوع سند و doccontent ذخیره کنندهی محتوای کامل سند خواهند بود.
سپس اطلاعاتی را در این جدول ثبت میکنیم:
4 ردیف ثبت شده در جدول اسناد، نیاز به 4 فایل docx نیز دارند که آنها را از آدرس ذیل میتوانید برای تکمیل سادهتر آزمایش دریافت کنید:
fts_docs.zip
در ادامه میخواهیم قادر باشیم تا بر روی متادیتای نویسندهی این اسناد نیز جستجوی کامل FTS را انجام دهیم. به همین جهت SEARCH PROPERTY LIST آنرا نیز ایجاد خواهیم کرد:
همچنین میخواهیم از واژهی SQL در این اسناد، در حین ساخت ایندکسهای FTS صرفنظر شود. برای این منظور یک FULLTEXT STOPLIST را به نام SQLStopList ایجاد کرده و سپس واژهی مدنظر را به آن اضافه میکنیم:
صحت عملیات آنرا توسط کوئری «Check the Stopwords list» ذکر شده در ابتدای بحث میتوانید بررسی کنید.
اکنون زمان ایجاد یک کاتالوگ FTS است:
با توجه به اینکه در نگارشهای جدید SQL Server این کاتالوگ صرفا ماهیتی مجازی دارد، سادهترین Syntax آن برای کار ما کفایت میکند.
و در آخر ایندکس FTS ایی را که پیشتر در مورد آن بحث کردیم، ایجاد خواهیم کرد:
در این تصویر محل یافتن اجزای مختلف Full text search را در management studio مشاهده میکنید.
یک نکتهی تکمیلی
برای زبان فارسی نیز یک سری stop words وجود دارند. لیست آنها را از اینجا میتوانید دریافت کنید:
stopwords.sql
متاسفانه زبان فارسی جزو زبانهای پشتیبانی شده توسط FTS در SQL Server نیست (نه به این معنا که نمیتوان با آن کار کرد؛ به این معنا که برای مثال دستورات صرفی زبان را ندارد) و به همین جهت از زبان انگلیسی در اینجا استفاده شدهاست.
همانطور که در قسمت قبل نیز عنوان شد، فیلترهای 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 نیست (نه به این معنا که نمیتوان با آن کار کرد؛ به این معنا که برای مثال دستورات صرفی زبان را ندارد) و به همین جهت از زبان انگلیسی در اینجا استفاده شدهاست.
گاهی از اوقات یک سری از امکانات جدید در دسترس هستند اما فراموش میشوند. برای مثال روش یافتن رکوردهای غیر یکسان دو جدول یکسان. مثلا یک دیتابیس قدیمی دارید دقیقا مشابه دیتابیس کاری فعلی با همان ساختار (ری استور شده از یک بک آپ). اکنون میخواهید بدانید در طول این مدت چه رکوردهایی به دیتابیس کاری اضافه شده که در دیتابیس قدیمی ری استور شده موجود نیست و کلا کدام رکوردها با هم متفاوتند. چه باید کرد؟
مثال:
دو جدول موقتی یکسان زیر را در نظر بگیرید.
CREATE TABLE #tableA
(
column1 INT,
column2 INT
)
INSERT INTO #tableA
VALUES
(1,1)
,(1, 2)
,(1, 3)
,(2, 1)
SELECT column1,
column2
FROM #tableA
CREATE TABLE #tableB
(
column1 INT,
column2 INT
)
INSERT INTO #tableB
VALUES
(1,1)
,(1, 3)
,(2, 2)
SELECT column1,
column2
FROM #tableB
یک سری دیتای دلخواه به آنها اضافه شده است. (از روش اضافه کردن چندین رکورد توسط یک عبارت insert که در اس کیوال سرور 2008 معرفی شده، استفاده گردیده است)
#tableA
column1 column2
----------- -----------
1 1
1 2
1 3
2 1
#tableB
column1 column2
----------- -----------
1 1
1 3
2 2
اکنون میخواهیم رکوردهایی از جدول A را که در جدول B نیستند، پیدا کنیم. روش متداول انجام اینکار در اس کیوال سرور 2000 به صورت زیر است:
SELECT column1,
column2
FROM #tableA
WHERE NOT EXISTS (
SELECT *
FROM #tableB
WHERE #tableA.column1 = #tableB.column1
AND #tableA.column2 = #tableB.column2
)
column1 column2
----------- -----------
1 2
2 1
SELECT column1, column2 FROM #tableA
EXCEPT
SELECT column1, column2 FROM #tableB
column1 column2
----------- -----------
1 2
2 1
در این حالت تعداد ستونهای در نظر گرفته شده برای مقایسه باید یکسان و یک نوع باشند.
همچنین اگر میخواهید رکوردهایی از جدول A را که در جدول B وجود دارند بیابید، میتوان از intersect استفاده کرد.
در دنیای NET. همواره دو نوع LINQ وجود داشته داشته است: LINQ to Objects و ... مابقی. در حالت اول با <IEnumerable<Tها کار میکنیم که تمام عملیات در حافظه انجام میشود و در مابقی حالات یک <IQueryable<T وجود دارد که عبارت حاصل از آن جهت کاربردهای مختلفی به زبانهای متفاوتی مانند SQL ترجمه میشوند. در هر دو حالت کلی، Syntax نهایی یکی است و تنها اگر به منبع دادهی آنها دقت کنیم، میتوانیم نوع آنها را تشخیص دهیم. برای نمونه کوئری ذیل بر اساس منبع Blogs است که میتواند LINQ to Objects باشد و یا حالت <Queryable<Blog که قرار است به زبانی مشخص ترجمه شود:
اکنون فرض کنید که این عبارت قرار است به SQL ترجمه شده و سپس بر روی یک بانک اطلاعاتی اجرا شود. در این حالت مفسر LINQ باید بداند که متد Contains را چگونه به معادل SQL آن ترجمه کند و این ترجمه میتواند بر اساس بانکهای اطلاعاتی مختلف، متفاوت نیز باشد. اما در حالت LINQ to Objects یک چنین مشکلی وجود ندارد و این ترجمه مستقیما بر روی متد Contains کلاس string انجام میشود.
اما اکنون چطور؟
فرض کنید یک متد الحاقی را به نام ComputeHash به کلاس string اضافه کردهایم. یک چنین کوئری را اگر بر روی EF 6.x اجرا کنیم، برنامه با یک استثناء متوقف خواهد شد؛ چون امکان ترجمهی متد ComputeHash را به معادل SQL آن ندارد؛ اما EF Core برای انجام یک چنین کوئریهایی بهبود یافتهاست که به آن، محاسبات سمت کلاینت گفته میشود.
یک مثال: بررسی تاثیر ارزیابیهای سمت کلاینت در EF Core
فرض کنید ساختار جدول بلاگها به صورت زیر است:
همچنین یک متد الحاقی را به نام ComputeHash به صورت ذیل تعریف کردهایم:
اکنون میخواهیم بلاگهایی را پیدا کنیم که Hash مربوط به Url آنها بیشتر از 10 است (صرفا جهت نمایش این قابلیت جدید):
اگر این کوئری را اجرا کنیم، یک چنین خروجی SQL ایی تولید خواهد شد و همچنین برنامه کرش هم نمیکند:
به این معنا که در ارزیابیهای سمت کلاینت:
الف) مفسر LINQ در EF Core، شروع به ارزیابی کوئری نوشته شده میکند و هرجائیکه متدی را یافت و از درک آن عاجز بود (معادل SQL ایی را برای آن نیافت)، آنرا از کوئری حذف میکند.
ب) کوئری SQL نهایی بدون متد ComputeHash بر روی بانک اطلاعاتی اجرا شده و نتیجه به سمت کلاینت بازگشت داده میشود. به همین جهت است که در خروجی SQL فوق خبری از متد ComputeHash نیست.
ج) اکنون که EF Core اطلاعات لازم را از سمت سرور دریافت کردهاست، متد ComputeHash را در سمت کلاینت بر روی این نتیجهی دریافتی اعمال میکند. یعنی مرحلهی آخر همان LINQ to Objects متداول خواهد بود.
به این ترتیب است که EF Core قابلیت اجرای هر نوع متدی را که معادل SQL ایی برای آن وجود ندارد، خواهد یافت.
چگونه متوجه شویم که ارزیابی سمت کلاینت رخ دادهاست؟
EF Core این قابلیت را دارد تا گزارش کاملی را از ارزیابیهای سمت کلاینت صورت گرفته ارائه دهد. هرچند در مثال فوق متد الحاقی ComputeHash بسیار واضح است، اما برای نمونه متد string.Join نیز معادل SQL ایی ندارد:
این مثال بدون مشکل توسط EF Core و قابلیت جدید ارزیابی سمت کلاینت آن اجرا میشود، اما بهتر است از وقوع یک چنین رخدادهایی مطلع شویم:
برای این منظور تنها کافی است درخواست فعالسازی لاگ کردن QueryClientEvaluationWarning را در قسمت ConfigureWarnings آن ارائه دهیم. در این حالت اگر برنامه را مجددا اجرا کنیم، ابتدا یک چنین خروجی لاگ میشود:
عنوان کردهاست که قابلیت ترجمهی ComputeHash را به SQL نداشته و آنرا در نهایت به صورت محلی و در سمت کلاینت محاسبه میکند.
اگر میخواهید ارزیابی سمت کلاینت را ممنوع کنید، در تنظیمات فوق warnings.Log را به warnings.Throw تغییر دهید. این مورد سبب خواهد شد تا اگر برنامه به این نوع ارزیابیها رسید، با یک استثناء متوقف شود (شبیه به حالت EF 6.x).
تاثیر ارزیابیهای سمت کلاینت بر روی کارآیی برنامه
هرچند قابلیت ارزیابیهای سمت کلاینت بسیار مفید است اما باید دقت داشت:
الف) در این حالت چون ابتدا متدهایی که قابلیت ارزیابی در سمت سرور را دارا نیستند، حذف خواهند شد، ممکن است تمام رکوردها به سمت کلاینت بازگشت داده شده و سپس فیلترینگ نهایی در سمت کلاینت صورت گیرد. مانند مثال محاسبهی hash که در SQL تولیدی آن، خبری از قسمت where نیست و این شرط در انتهای کار، در سمت کلاینت و به صورت LINQ to Objects اعمال میشود.
ب) این قابلیت ممکن است برنامه نویسها را از تفکر در مورد یافتن روشهای محاسباتی سمت سرور دور کند. برای مثال هر چند مثال string.Join نوشته شده در سمت کلاینت محاسبه خواهد شد و این کوئری بدون مشکل اجرا میشود، اما اگر آنرا به صورت ذیل جایگزین کنیم:
اینبار به یک خروجی SQL قابل محاسبهی در سمت سرور، خواهیم رسید:
به همین جهت حداقل لاگ کردن ارزیابیهای سمت کلاینت را روشن کنید تا از وقوع یک چنین مسایلی مطلع گردید.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: ClientSideEvaluation.zip
var blogs = from blog in Blogs where blog.Name.Contains("Development") select blog;
اما اکنون چطور؟
var blogs = from blog in Blogs where blog.Name.ComputeHash() == 0 select blog;
یک مثال: بررسی تاثیر ارزیابیهای سمت کلاینت در EF Core
فرض کنید ساختار جدول بلاگها به صورت زیر است:
public class Blog { public int BlogId { get; set; } public string Url { get; set; } }
public static class StringExtensions { public static int ComputeHash(this string str) { var hash = 0; foreach (var ch in str) { hash += (int)ch; } return hash; } }
using (var context = new BloggingContext()) { var blogs = context.Blogs .Where(blog => blog.Url.ComputeHash() >= 10) .ToList(); Console.WriteLine(blogs.First().Url); }
SELECT [blog].[BlogId], [blog].[Url] FROM [Blogs] AS [blog]
الف) مفسر LINQ در EF Core، شروع به ارزیابی کوئری نوشته شده میکند و هرجائیکه متدی را یافت و از درک آن عاجز بود (معادل SQL ایی را برای آن نیافت)، آنرا از کوئری حذف میکند.
ب) کوئری SQL نهایی بدون متد ComputeHash بر روی بانک اطلاعاتی اجرا شده و نتیجه به سمت کلاینت بازگشت داده میشود. به همین جهت است که در خروجی SQL فوق خبری از متد ComputeHash نیست.
ج) اکنون که EF Core اطلاعات لازم را از سمت سرور دریافت کردهاست، متد ComputeHash را در سمت کلاینت بر روی این نتیجهی دریافتی اعمال میکند. یعنی مرحلهی آخر همان LINQ to Objects متداول خواهد بود.
به این ترتیب است که EF Core قابلیت اجرای هر نوع متدی را که معادل SQL ایی برای آن وجود ندارد، خواهد یافت.
چگونه متوجه شویم که ارزیابی سمت کلاینت رخ دادهاست؟
EF Core این قابلیت را دارد تا گزارش کاملی را از ارزیابیهای سمت کلاینت صورت گرفته ارائه دهد. هرچند در مثال فوق متد الحاقی ComputeHash بسیار واضح است، اما برای نمونه متد string.Join نیز معادل SQL ایی ندارد:
var idUrls = context.Blogs .Select(b => new { IdUrlString = string.Join(", ", b.BlogId, b.Url), }).ToList();
public class BloggingContext : DbContext { public BloggingContext() { } public BloggingContext(DbContextOptions options) : base(options) { } public DbSet<Blog> Blogs { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Demo.ClientSideEvaluation;Trusted_Connection=True;"); optionsBuilder.ConfigureWarnings(warnings => { warnings.Log(CoreEventId.IncludeIgnoredWarning); warnings.Log(RelationalEventId.QueryClientEvaluationWarning); }); } } }
warn: Microsoft.EntityFrameworkCore.Query[200500] The LINQ expression 'where ([blog].Url.ComputeHash() >= 10)' could not be translated and will be evaluated locally.
اگر میخواهید ارزیابی سمت کلاینت را ممنوع کنید، در تنظیمات فوق warnings.Log را به warnings.Throw تغییر دهید. این مورد سبب خواهد شد تا اگر برنامه به این نوع ارزیابیها رسید، با یک استثناء متوقف شود (شبیه به حالت EF 6.x).
تاثیر ارزیابیهای سمت کلاینت بر روی کارآیی برنامه
هرچند قابلیت ارزیابیهای سمت کلاینت بسیار مفید است اما باید دقت داشت:
الف) در این حالت چون ابتدا متدهایی که قابلیت ارزیابی در سمت سرور را دارا نیستند، حذف خواهند شد، ممکن است تمام رکوردها به سمت کلاینت بازگشت داده شده و سپس فیلترینگ نهایی در سمت کلاینت صورت گیرد. مانند مثال محاسبهی hash که در SQL تولیدی آن، خبری از قسمت where نیست و این شرط در انتهای کار، در سمت کلاینت و به صورت LINQ to Objects اعمال میشود.
ب) این قابلیت ممکن است برنامه نویسها را از تفکر در مورد یافتن روشهای محاسباتی سمت سرور دور کند. برای مثال هر چند مثال string.Join نوشته شده در سمت کلاینت محاسبه خواهد شد و این کوئری بدون مشکل اجرا میشود، اما اگر آنرا به صورت ذیل جایگزین کنیم:
var idUrls2 = context.Blogs .Select(b => new { IdUrlString = b.BlogId + "," + b.Url }).ToList();
SELECT (CAST([b].[BlogId] AS nvarchar(max)) + N',') + [b].[Url] AS [IdUrlString] FROM [Blogs] AS [b]
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: ClientSideEvaluation.zip
پیشنیاز: «تنظیمات ارث بری کلاسها در EF Code first»
در مطلب پیشنیاز فوق، تنظیمات روابط ارث بری را تا EF 6.x، میتوانید مطالعه کنید. در EF Core 1.0 RTM، فقط رابطهی TPH که در آن تمام کلاسهای سلسه مراتب ارث بری، به یک جدول در بانک اطلاعاتی نگاشت میشوند، پشتیبانی میشود. سایر روشهای ارث بری که در EF 6.x وجود دارند، مانند TPT و TPC، قرار است به نگارشهای پس از 1.0 RTM آن اضافه شوند:
- لیست مواردی که قرار است به نگارشهای بعدی اضافه شوند
- پیگیری وضعیت پیاده سازی TPT
- پیگیری وضعیت پیاده سازی TPC
طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط
مرسوم است که یک کلاس ویژه را به نام BaseEntity، به شکل زیر تعریف کنند؛ که اهدف آن حداقل سه مورد ذیل است:
الف) کاهش ذکر فیلدهای تکراری در سایر کلاسهای دومین برنامه، مانند فیلد Id
ب) نشانه گذاری موجودیتهای برنامه، جهت یافتن سریع آنها توسط Reflection (برای مثال افزودن خودکار موجودیتها به Context برنامه با یافتن تمام کلاسهایی که از نوع BaseEntity هستند)
ج) مقدار دهی خودکار یک سری از فیلدهای ویژه، مانند زمان افزوده شدن رکورد و آخرین زمان ویرایش شدن رکورد و امثال آن
و پس از آن هر موجودیت برنامه به این شکل خلاصه شده و نشانه گذاری میشود:
حالت پیش فرض ارث بریها در EF Core، همان حالت TPH است که در ادامه توضیح داده خواهد شد. اما هدف ما در اینجا تنظیم هیچکدام از حالتهای ارث بری نیست. هدف صرفا کاهش تعداد فیلدهای تکراری ذکر شدهی در کلاسهای دومین برنامه است. بنابراین جهت لغو تنظیمات ارث بری EF Core، نیاز است یک چنین تنظیمی را انجام داد:
با فراخوانی متد Ignore بر روی کلاس پایهی تهیه شده، این کلاس دیگر وارد تنظیمات روابط EF Core نمیشود و در جداول نهایی، فیلدهای آن به صورت معمول در کنار سایر فیلدهای جداول مشتق شدهی از آنها قرار میگیرند.
مشکل! اگر بر روی کلاس پایهی تعریف شده تنظیماتی را اعمال کنید (هر نوع تنظیمی را)، با توجه به فراخوانی متد Ignore، این تنظیمات نیز ندید گرفته خواهند شد.
اگر علاقمند بودید تا این تنظیمات را به تمام کلاسهای مشتق شدهی از BaseEntity به صورت خودکار اعمال کنید، روش کار به صورت ذیل است:
کاری که در اینجا انجام شده، تنظیم خاصیت DateAdded کلاس پایه، به حالت ValueGeneratedOnAdd و تنظیم خاصیت DateUpdated کلاس پایه به حالت ValueGeneratedOnAddOrUpdate با مقدار پیش فرض getdate است. این مفاهیم را در مطلب «شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکسها» پیشتر بررسی کردیم.
خلاصهی آن نیز به این صورت است:
الف) نیازی نیست تا در حین ثبت اطلاعات موجودیتهای خود، فیلدهای DateAdded و یا DateUpdated را مقدار دهی کنید.
ب) فیلد DateAdded فقط در زمان اولین بار ثبت در بانک اطلاعاتی، به صورت خودکار توسط متد getdate مقدار دهی میشود.
ج) فیلد DateUpdated در هر بار فراخوانی متد SaveChanges (یعنی در هر دو حالت ثبت و یا به روز رسانی) به صورت خودکار توسط متد getdate مقدار دهی میشود.
تذکر! بدیهی است متد getdate، یک متد بومی سمت SQL Server است و این روش خاص تعیین مقدار پیش فرض فیلدها، فقط با SQL Server کار میکند. همچنین این getdate، به معنای دریافت تاریخ و ساعت سروری است که SQL Server بر روی آن نصب شدهاست و نه سروری که برنامهی وب شما در آن قرار دارد و برنامه کوچکترین دخالتی را در مقدار دهی این مقادیر نخواهد داشت.
در قسمتهای بعدی که مباحث Tracking را بررسی خواهیم کرد، روش دیگری را برای طراحی کلاسهای پایه و مقدار دهی خواص ویژهی آنها مطرح میکنیم که مستقل است از نوع بانک اطلاعاتی مورد استفاده.
بررسی تنظیمات رابطهی Table per Hierarchy یا TPH
رابطهی TPH یا تشکیل یک جدول بانک اطلاعاتی، به ازای تمام کلاسهای دخیل در سلسه مراتب ارث بری تعریف شده، بسیار شبیه است به حالت BaseEntity فوق که در آن نیز ارث بری تعریف شده، در نهایت منجر به تشکیل یک جدول، در سمت بانک اطلاعاتی میگردد. با این تفاوت که در حالت TPH، فیلد جدیدی نیز به نام Discriminator، به تعریف نهایی جدول ایجاد شده، اضافه میشود. از فیلد Discriminator جهت درج نام کلاسهای متناظر با هر رکورد، استفاده شده است. به این ترتیب EF در حین کار با اشیاء، دقیقا میداند که چگونه باید خواص متناظر با کلاسهای مختلف را مقدار دهی کند و نوع ردیف درج شدهی در بانک اطلاعاتی چیست؟
باید دقت داشت که تنظیمات TPH، شیوه برخورد پیش فرض EF Core با ارث بری کلاسها است و نیاز به هیچگونه تنظیم اضافهتری را ندارد. اما اگر علاقمند بودید تا نام فیلد خودکار Discriminator و مقادیری را که در آن درج میشوند، سفارشی سازی کنید، روش کار صرفا توسط Fluent API میسر است و به صورت زیر میباشد:
در اینجا نام فیلد Discriminator، به blog_type، مقدار نوع متناظر با کلاس Blog، به blog_base و مقدار نوع متناظر با کلاس RssBlog، به blog_rss تنظیم شدهاست.
اگر این تنظیمات سفارشی صورت نگیرند، از نامهای پیش فرض نوعها برای مقدار دهی ستون Discriminator، مانند تصویر ذیل استفاده خواهد شد:
برای کوئری نوشتن در این حالت میتوان از متد الحاقی OfType جهت فیلتر کردن اطلاعات بر اساس کلاسی خاص، کمک گرفت:
در مطلب پیشنیاز فوق، تنظیمات روابط ارث بری را تا EF 6.x، میتوانید مطالعه کنید. در EF Core 1.0 RTM، فقط رابطهی TPH که در آن تمام کلاسهای سلسه مراتب ارث بری، به یک جدول در بانک اطلاعاتی نگاشت میشوند، پشتیبانی میشود. سایر روشهای ارث بری که در EF 6.x وجود دارند، مانند TPT و TPC، قرار است به نگارشهای پس از 1.0 RTM آن اضافه شوند:
- لیست مواردی که قرار است به نگارشهای بعدی اضافه شوند
- پیگیری وضعیت پیاده سازی TPT
- پیگیری وضعیت پیاده سازی TPC
طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط
مرسوم است که یک کلاس ویژه را به نام BaseEntity، به شکل زیر تعریف کنند؛ که اهدف آن حداقل سه مورد ذیل است:
الف) کاهش ذکر فیلدهای تکراری در سایر کلاسهای دومین برنامه، مانند فیلد Id
ب) نشانه گذاری موجودیتهای برنامه، جهت یافتن سریع آنها توسط Reflection (برای مثال افزودن خودکار موجودیتها به Context برنامه با یافتن تمام کلاسهایی که از نوع BaseEntity هستند)
ج) مقدار دهی خودکار یک سری از فیلدهای ویژه، مانند زمان افزوده شدن رکورد و آخرین زمان ویرایش شدن رکورد و امثال آن
public class BaseEntity { public int Id { set; get; } public DateTime? DateAdded { set; get; } public DateTime? DateUpdated { set; get; } }
public class Person : BaseEntity { public string FirstName { get; set; } public string LastName { get; set; } }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BaseEntity>();
مشکل! اگر بر روی کلاس پایهی تعریف شده تنظیماتی را اعمال کنید (هر نوع تنظیمی را)، با توجه به فراخوانی متد Ignore، این تنظیمات نیز ندید گرفته خواهند شد.
اگر علاقمند بودید تا این تنظیمات را به تمام کلاسهای مشتق شدهی از BaseEntity به صورت خودکار اعمال کنید، روش کار به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BaseEntity>(); foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { var dateAddedProperty = entityType.FindProperty("DateAdded"); dateAddedProperty.ValueGenerated = ValueGenerated.OnAdd; dateAddedProperty.SqlServer().DefaultValueSql = "getdate()"; var dateUpdatedProperty = entityType.FindProperty("DateUpdated"); dateUpdatedProperty.ValueGenerated = ValueGenerated.OnAddOrUpdate; dateUpdatedProperty.SqlServer().ComputedColumnSql = "getdate()"; }
خلاصهی آن نیز به این صورت است:
الف) نیازی نیست تا در حین ثبت اطلاعات موجودیتهای خود، فیلدهای DateAdded و یا DateUpdated را مقدار دهی کنید.
ب) فیلد DateAdded فقط در زمان اولین بار ثبت در بانک اطلاعاتی، به صورت خودکار توسط متد getdate مقدار دهی میشود.
ج) فیلد DateUpdated در هر بار فراخوانی متد SaveChanges (یعنی در هر دو حالت ثبت و یا به روز رسانی) به صورت خودکار توسط متد getdate مقدار دهی میشود.
تذکر! بدیهی است متد getdate، یک متد بومی سمت SQL Server است و این روش خاص تعیین مقدار پیش فرض فیلدها، فقط با SQL Server کار میکند. همچنین این getdate، به معنای دریافت تاریخ و ساعت سروری است که SQL Server بر روی آن نصب شدهاست و نه سروری که برنامهی وب شما در آن قرار دارد و برنامه کوچکترین دخالتی را در مقدار دهی این مقادیر نخواهد داشت.
در قسمتهای بعدی که مباحث Tracking را بررسی خواهیم کرد، روش دیگری را برای طراحی کلاسهای پایه و مقدار دهی خواص ویژهی آنها مطرح میکنیم که مستقل است از نوع بانک اطلاعاتی مورد استفاده.
بررسی تنظیمات رابطهی Table per Hierarchy یا TPH
رابطهی TPH یا تشکیل یک جدول بانک اطلاعاتی، به ازای تمام کلاسهای دخیل در سلسه مراتب ارث بری تعریف شده، بسیار شبیه است به حالت BaseEntity فوق که در آن نیز ارث بری تعریف شده، در نهایت منجر به تشکیل یک جدول، در سمت بانک اطلاعاتی میگردد. با این تفاوت که در حالت TPH، فیلد جدیدی نیز به نام Discriminator، به تعریف نهایی جدول ایجاد شده، اضافه میشود. از فیلد Discriminator جهت درج نام کلاسهای متناظر با هر رکورد، استفاده شده است. به این ترتیب EF در حین کار با اشیاء، دقیقا میداند که چگونه باید خواص متناظر با کلاسهای مختلف را مقدار دهی کند و نوع ردیف درج شدهی در بانک اطلاعاتی چیست؟
باید دقت داشت که تنظیمات TPH، شیوه برخورد پیش فرض EF Core با ارث بری کلاسها است و نیاز به هیچگونه تنظیم اضافهتری را ندارد. اما اگر علاقمند بودید تا نام فیلد خودکار Discriminator و مقادیری را که در آن درج میشوند، سفارشی سازی کنید، روش کار صرفا توسط Fluent API میسر است و به صورت زیر میباشد:
public class Blog { public int BlogId { get; set; } public string Url { get; set; } } public class RssBlog : Blog { public string RssUrl { get; set; } } class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasDiscriminator<string>("blog_type") .HasValue<Blog>("blog_base") .HasValue<RssBlog>("blog_rss"); } }
اگر این تنظیمات سفارشی صورت نگیرند، از نامهای پیش فرض نوعها برای مقدار دهی ستون Discriminator، مانند تصویر ذیل استفاده خواهد شد:
برای کوئری نوشتن در این حالت میتوان از متد الحاقی OfType جهت فیلتر کردن اطلاعات بر اساس کلاسی خاص، کمک گرفت:
var blog1 = db.Blogs.OfType<RssBlog>().FirstOrDefault(x => x.RssUrl == "………");
نظرات مطالب
مهاجرت از SQL Membership به ASP.NET Identity
ممنون از مطلب خوبتون.
من جداول خودم رو برای احراز هویت دارم آیا میشه همین جداول رو با ASP.NET Identity کار کنم؟
اگر نمیشه، این بانک ASP.NET Identity که توی SQL Server نمیسازه بانک Database.mdf رو میسازه، میشه توی SQL بسازیم از همون ابتدای پروژه؟