اولین پروژه
معمولا برای شروع از تاریخچه و توضیحات دیگر استفاده میکنند اما روش آموزشی که در پیش خواهیم گرفت با انجام پروژههای عملی بوده و هر جا که نیاز به توضیح باشد ، بیان میکنیم ...
ایجاد اولین پروژه Win32 Console Application
ویژوال استادیو را اجرا نمایید و از گزینه File -> New -> Project و سپس طبق عکس زیر پروژه Win32 Console Application را انتخاب نمایید ، دقت کنید که زبان انتخاب شده ++Visual C باشد.
در این مرحله میتوانید محل ذخیره شده پروژه را در قسمت Location تنظیم نمایید و از قسمت Name میتوانید نام دلخواه را وارد کنید در حالت پیش فرض اگر اولین پروژه Win32 Console در مسیر تعین شدهی قسمت Location باشد ، نام ConsoleApplication1 قرار گرفته است . پس از تنظیمات Ok کنید .
در این مرحله Next را بزنید .
در این مرحله در قسمت Additional options تیک Empty project را بزنید ، همانند عکس فوق تنظیمات را انجام دهید .
پس از انجام مراحل فوق پروژه بصورت شکل زیر ظاهر میشود .
برای کد نویسی روی نام پروژه که در اینجا ConsoleApplication1 میباشد ، راست کلیک میکنیم و گزینه Add و سپس New Item را انتخاب میکنیم .
طبق عکس زیر فایل با پسوند cpp را انتخاب و Add میکنیم .
فایلی که اکنون به پروژه اضافه کردیم خالی و با نام پیش فرض Source.cpp میباشد ، دستورات زیر را در آن تایپ کنید .حال پروژه به شکل زیر خواهد بود .
#include<iostream> int main() { std::cout<<"Hello world ...\n"; return 0; }
برای اجرای پروژه کلید F5 را فشار دهید و اگر میخواهید نتیجه کار را مشاهده کنید کلید Ctrl + F5 را امتحان کنید .
شما اولین پروژه VC++ را اجرا نمودید ( آفرین ) .
اما توضیحات :
خط اول برنامه یک راهنمای پیش پردازنده است ، کاراکتر # که نشان میدهد این خط یک راهنمای پیش پردازنده است و بعد عبارت include و نام یک فایل کتابخانه ای که بین علامت <> قرار داده شده ، فایل سرآیند استفاده شده در اینجا iostream میباشد . (به فایلهای کتابخانه ای ، فایلهای سرآیند (Header Files) نیز گفته میشود. ) راهنمای پیش پردازنده خطی است که به کامپایلر اطلاع میدهد در برنامه موجودیتی است که تعریف آن را در فایل سرآیند مذکور جستجو کند . در این برنامه از std::cout استفاده شده ، که کامپایلر در مورد آن چیزی نمیداند لذا به فایل iostream مراجعه نموده ، تعریف آن را مییابد و آن را اجرا میکند .
خط 3 :
بخشی از هر برنامه تابع میباشد . پرانتزهای واقع پس از آن main نشان میدهند که main یک بلوک برنامه بنام تابع است. برنامهها میتوانند حاوی یک یا چندین تابع باشند، اما main تابع اصلی برنامه است که وجود آن الزامی میباشد . کلمه کلیدی int که در سمت چپ main قرار گرفته، بر این نکته دلالت دارد که main یک مقدار صحیح برمیگرداند.
خط 5 :
با استفاده از این دستور رشته ای را به خروجی استاندارد که معمولا صفحه نمایش باشد ارسال میکنیم .
خط 6 :
که ;0 return میباشد مقدار برگشتی تابع را مشخص میکند در حقیقت این خط که مقدار 0 را برمیگرادند نشان دهنده اتمام موفقیت آمیز برنامه میباشد .
به مرور زمان نسبت به موارد بالا بیشتر و مفصل صحبت خواهیم نمود .
ILSpy 8.0 منتشر شد
SQL Antipattern #2
بخش دوم : Naive Trees
فرض کنید یک وب سایت حرفهای خبری یا علمی-پژوهشی داریم که قابلیت دریافت نظرات کاربران را در مورد هر مطلب مندرج در سایت یا نظرات داده شده در مورد آن مطالب را دارا میباشد. یعنی هر کاربر علاوه بر توانایی اظهار نظر در مورد یک خبر یا مطلب باید بتواند پاسخ نظرات کاربران دیگر را نیز بدهد. اولین راه حلی که برای طراحی این مطلب در پایگاه داده به ذهن ما میرسد، ایجاد یک زنجیره با استفاده از کد sql زیر میباشد:
CREATE TABLE Comments ( comment_idSERIAL PRIMARY KEY, parent_idBIGINT UNSIGNED, comment TEXT NOT NULL, FOREIGN KEY (parent_id) REFERENCES Comments(comment_id) );
البته همان طور که پیداست بازیابی زنجیرهای از پاسخها در یک پرسوجوی sql کار سختی است. این نخها معمولا عمق نامحدودی دارند و برای به دست آوردن تمام نخهای یک زنجیره باید پرسوجوهای زیادی را اجرا نمود.
ایدهی دیگر میتواند بازیابی تمام نظرها و ذخیرهی آنها در حافظهی برنامه به صورت درخت باشد. ولی این روش برای ذخیره هزاران نظری که ممکن است در سایت ثبت شود و علاوه بر آن مقالات جدیدی که اضافه میشوند، تقریبا غیرعملی است.
1.2 هدف: ذخیره و ایجاد پرسوجو در سلسلهمراتب
وجود سلسله مراتب بین دادهها امری عادی محسوب میگردد. در ساختار دادهای درختی هر ورودی یک گره محسوب میگردد. یک گره ممکن است تعدادی فرزند و یک پدر داشته باشد. گره اول پدر ندارد، ریشه و گره فرزند که فرزند ندارد، برگ و گرهای دیگر، گرههای غیربرگ نامیده میشوند.
مثالهایی که از ساختار درختی دادهها وجود دارد شامل موارد زیر است:
Organizational chart: در این ساختار برای مثال در ارتباط بین کارمندان و مدیر، هر کارمند یک مدیر دارد که نشاندهندهی پدر یک کارمند در ساختار درختی است. هر مدیر هم یک کارمند محسوب میگردد.
Threaded discussion: در این ساختار برای مثال در سیستم نظردهی و پاسخها، ممکن است زنجیرهای از نظرات در پاسخ به نظرات دیگر استفاده گردد. در درخت، فرزندان یک گرهی نظر، پاسخهای آن گره هستند.
در این فصل ما از مثال ساختار دوم برای نشان دادن Antipattern و راه حل آن بهره میگیریم.
2.2 Antipattern : همیشه مبتنی بر یکی از والدین
راه حل ابتدایی و ناکارآمد
اضافه نمودن ستون parent_id . این ستون، به ستون نظر در همان جدول ارجاع داده میشود و شما میتوانید برای اجرای این رابطه از قید کلید خارجی استفاده نمایید. پرسوجویی که برای ساخت مثالی که ما در این بحث از آن استفاده میکنیم در ادامه آمده است:
CREATE TABLE Comments ( comment_idSERIAL PRIMARY KEY, parent_idBIGINT UNSIGNED, bug_idBIGINT UNSIGNED NOT NULL, author BIGINT UNSIGNED NOT NULL, comment_dateDATETIME NOT NULL, comment TEXT NOT NULL, FOREIGN KEY (parent_id)REFERENCES Comments(comment_id), FOREIGN KEY (bug_id) REFERENCES Bugs(bug_id), FOREIGN KEY(author) REFERENCES Accounts(account_id) );
لیست مجاورت :
لیست مجاورت خود میتواند به عنوان یک antipattern در نظر گرفته شود. البته این مطلب از آنجایی نشأت میگیرد که این روش توسط بسیاری از توسعهدهندگان مورد استفاده قرار میگیرد ولی نتوانسته است به عنوان راه حل برای معمولترین وظیفهی خود، یعنی ایجاد پرسوجو بر روی کلیه فرزندان، باشد.
• با استفاده از پرسوجوی زیر میتوان فرزند بلافاصلهی یک "نظر" را برگرداند:
SELECT c1.*, c2.* FROM Comments c1 LEFT OUTER JOIN Comments c2 ON c2.parent_id = c1.comment_id;
ضعف پرسوجوی فوق این است که فقط میتواند دو سطح از درخت را برای شما برگرداند. در حالیکه خاصیت درخت این است که شما را قادر میسازد بدون هیچ گونه محدودیتی فرزندان و نوههای متعدد (سطوح بیشمار) برای درخت خود تعریف کنید. بنابراین به ازای هر سطح اضافه باید یک join به پرسجوی خود اضافه نمایید. برای مثال اگر پرسوجوی زیر میتواند درختی با چهار سطح برای شما برگرداند ولی نه بیش از آن:
SELECT c1.*, c2.*, c3.*, c4.* FROM Comments c1 -- 1st level LEFT OUTER JOIN Comments c2 ON c2.parent_id = c1.comment_id -- 2nd level LEFT OUTER JOIN Comments c3 ON c3.parent_id = c2.comment_id -- 3rd level LEFT OUTER JOIN Comments c4 ON c4.parent_id = c3.comment_id; -- 4th level
این پرسوجو به این دلیل که با اضافه شدن ستونهای دیگر، نوهها را سطوح عمیقتری برمیگرداند، پرسوجوی مناسبی نیست. در واقع استفاده از توابع تجمیعی ، مانند COUNT() مشکل میشود.
راه دیگر برای به دست آوردن ساختار یک زیردرخت از لیست مجاورت برای یک برنامه، این است که سطرهای مورد نظر خود را از مجموعه بازیابی نموده و سلسهمراتب مورد نظر را در حافظه بازیابی نماییم و از آن به عنوان درخت استفاده نماییم:
SELECT * FROM Comments WHERE bug_id = 1234;
INSERT INTO Comments (bug_id, parent_id, author, comment) VALUES (1234, 7, 'Kukla' , 'Thanks!' );
UPDATE Comments SET parent_id = 3 WHERE comment_id = 6;
SELECT parent_id FROM Comments WHERE comment_id = 6; -- returns 4 UPDATE Comments SET parent_id = 4 WHERE parent_id = 6; DELETE FROM Comments WHERE comment_id = 6;
- چه تعداد سطح برای پشتیبانی در درخت نیاز خواهیم داشت؟
- من همیشه از کار با کدی که ساختار دادهی درختی را مدیریت میکند، میترسم
- من باید اسکریپتی را به طور دورهای اجرا نمایم تا سطرهای یتیم موجود در درخت را حذف کند.
WITH CommentTree (comment_id, bug_id, parent_id, author, comment, depth) AS ( SELECT *, 0 AS depth FROM Comments WHERE parent_id IS NULL UNION ALL SELECT c.*, ct.depth+1 AS depth FROM CommentTreect JOIN Comments c ON (ct.comment_id = c.parent_id) ) SELECT * FROM CommentTree WHERE bug_id = 1234;
SELECT * FROM Comments START WITH comment_id = 9876 CONNECT BY PRIOR parent_id = comment_id;
CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY, path VARCHAR(1000), bug_id BIGINT UNSIGNED NOT NULL, author BIGINT UNSIGNED NOT NULL, comment_date DATETIME NOT NULL, comment TEXT NOT NULL, FOREIGN KEY (bug_id) REFERENCES Bugs(bug_id), FOREIGN KEY (author) REFERENCES Accounts(account_id)
SELECT * FROM Comments AS c WHERE '1/4/6/7/' LIKE c.path || '%' ;
SELECT * FROM Comments AS c WHERE c.path LIKE '1/4/' || '%' ;
CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY, nsleft INTEGER NOT NULL, nsright INTEGER NOT NULL, bug_id BIGINT UNSIGNED NOT NULL, author BIGINT UNSIGNED NOT NULL, comment_date DATETIME NOT NULL, comment TEXT NOT NULL, FOREIGN KEY (bug_id) REFERENCES Bugs (bug_id), FOREIGN KEY (author) REFERENCES Accounts(account_id) );
شمارهی سمت چپ یک گره از تمام شمارههای سمت چپ فرزندان آن گره کوچکتر و شمارهی سمت راست آن گره از تمام شمارههای سمت راست آن گره بزرگتر است. این شمارهها هیچ ارتباطی به comment_id مربوط به آن گره ندارند.
یک راه حل ساده برای تخصیص این شمارهها به گرهها این است که از سمت چپ یک گره آغاز میکنیم و اولین شماره را اختصاص میدهیم و به همین به گرهای سمت چپ فرزندان میآییم و شمارهها را به صورت افزایشی به سمت چپ آنها نیز اختصاص میدهیم. سپس در ادامه به سمت راست آخرین نود رفته و از آن جا به سمت بالا میآییم و به همین ترتیب به صورت بازگشتی تخصیص شمارهها را ادامه میدهیم.
با اختصتص شمارهها به هر گره، میتوان از آنها برای یافتن نیاکان و فرزندان آن گره بهره جست. برای مثال برای بازیابی گرهی 4 و فرزندان (نوههای) آن باید دنبال گرههایی باشیم که شمارههای آن گرهها بین nsleft و nsright گرهی شماره4 باشد:
SELECT c2.* FROM Comments AS c1 JOIN Comments as c2 ON c2.nsleft BETWEEN c1.nsleft AND c1.nsright WHERE c1.comment_id = 4;
SELECT c2.* FROM Comments AS c1 JOIN Comment AS c2 ON c1.nsleft BETWEEN c2.nsleft AND c2.nsright WHERE c1.comment_id = 6;
SELECT parent.* FROM Comment AS c JOIN Comment AS parent ON c.nsleft BETWEEN parent.nsleft AND parent.nsright LEFT OUTER JOIN Comment AS in_between ON c.nsleft BETWEEN in_between.nsleft AND in_between.nsright AND in_between.nsleft BETWEEN parent.nsleft AND parent.nsright WHERE c.comment_id = 6 AND in_between.comment_id IS NULL;
-- make space for NS values 8 and 9 UPDATE Comment SET nsleft = CASE WHEN nsleft >= 8 THEN nsleft+2 ELSE nsleft END, nsright = nsright+2 WHERE nsright >= 7; -- create new child of comment #5, occupying NS values 8 and 9 INSERT INTO Comment (nsleft, nsright, author, comment) VALUES (8, 9, 'Fran' , 'Me too!' );
CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY, bug_id BIGINT UNSIGNED NOT NULL, author BIGINT UNSIGNED NOT NULL, comment_date DATETIME NOT NULL, comment TEXT NOT NULL, FOREIGN KEY (bug_id) REFERENCES Bugs(bug_id), FOREIGN KEY (author) REFERENCES Accounts(account_id) ); CREATE TABLE TreePaths ( ancestor BIGINT UNSIGNED NOT NULL, descendant BIGINT UNSIGNED NOT NULL, PRIMARY KEY(ancestor, descendant), FOREIGN KEY (ancestor) REFERENCES Comments(comment_id), FOREIGN KEY (descendant) REFERENCES Comments(comment_id) );
به جای استفاده از جدول Comments برای ذخیرهی اطلاعات مربوط به یک درخت از جدول TreePath استفاده میکنیم. به ازای هر یک جفت گره در این درخت یک سطر در جدول ذخیره میشود که ارتباط پدر فرزندی را نمایش میدهد و الزاما نباید این دو پدر فرزند بلافصل باشد. همچنین یک سطر هم به ازای ارتباط هر گره با خودش به جدول اضافه میگردد.
پرسوجوهای بازیابی نیاکان و فرزندان (گرهها) از طریق جدول TreePaths سادهتر از روش مجموعههای تودرتو است. مثلا برای بازیابی فرزندان (نوههای) گرهی شمارهی 4، سطرهایی که نیاکان آنها 4 است را به دست میآوریم:
SELECT c.* FROM Comments AS c JOIN TreePaths AS t ON c.comment_id = t.descendant WHERE t.ancestor = 4;
SELECT c.* FROM Comments AS c JOIN TreePaths AS t ON c.comment_id = t.ancestor WHERE t.descendant = 6;
INSERT INTO TreePaths (ancestor, descendant) SELECT t.ancestor, 8 FROM TreePaths AS t WHERE t.descendant = 5 UNION ALL SELECT 8, 8;
DELETE FROM TreePaths WHERE descendant = 7;
DELETE FROM TreePaths WHERE descendant IN (SELECT descendant FROM TreePaths WHERE ancestor = 4);
DELETE FROM TreePaths WHERE descendant IN (SELECT descendant FROM TreePaths WHERE ancestor = 6) AND ancestor IN (SELECT ancestor FROM TreePaths WHERE descendant = 6 AND ancestor != descendant);
INSERT INTO TreePaths (ancestor, descendant) SELECT supertree.ancestor, subtree.descendant FROM TreePaths AS supertree CROSS JOIN TreePaths AS subtree WHERE supertree.descendant = 3 AND subtree.ancestor = 6;
میتوان عملکرد Closure Table را برای ایجاد پرسوجو روی فرزندان و پدر بلافصل را آسانتر نیز نمود. اگر فیلد path_length را به جدول TreePaths اضافه نماییم این کار انجام میشود. path_length گرهای که به خودش ارجاع میشود، صفر است. path_length فرزند بلافصل هر گره 1، path_length نوهی آن 2 میباشد و به همین ترتیب path_lengthها را در هر سطر مقداردهی میکنیم. اکنون یا فتن فرزند گرهی شمارهی 4 آسانتر است:
SELECT * FROM TreePaths WHERE ancestor = 4 AND path_length = 1;
اگر از آخرین نگارش Ubuntu استفاده میکنید، با توجه به همکاری مایکروسافت و شرکت پشتیبان آن، نصب داتنت به سادگی اجرای دستور زیر است:
$ sudo apt update && sudo apt install -y dotnet-sdk-8.0 $ dotnet --version
و اگر میخواهید بدانید که چه نگارشی از داتنت، بههمراه مخازن استاندارد Ubuntu است، دستور زیر را صادر کنید:
$ apt search dotnet-sdk*
که یک نمونه خروجی آن به صورت زیر است:
$ apt search dotnet-sdk* Sorting... Done Full Text Search... Done dotnet-sdk-8.0/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64 .NET 8.0 Software Development Kit dotnet-sdk-8.0-source-built-artifacts/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64 Internal package for building the .NET 8.0 Software Development Kit dotnet-sdk-dbg-8.0/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64 .NET SDK debug symbols.
نصب اسکریپتی آخرین نگارش داتنت بر روی تمام توزیعهای لینوکسی
مایکروسافت، اسکریپتی را برای دریافت و نصب خودکار آخرین نگارش داتنت، تهیه کردهاست که کار با آن بسیار سادهاست و با تمام توزیعهای لینوکسی و نه فقط Ubuntu سازگار است.
پیش از هرکاری ابتدا مخزنهای بستهها و برنامههای مرتبط را یکبار بهروز کرده و سیستم را ریاستارت میکنیم:
sudo apt update -q && sudo apt upgrade -y && reboot
سپس دستور زیر را صادر میکنیم تا اسکریپت نصاب مخصوص داتنت خود مایکروسافت را دریافت کنیم :
wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh chmod +x ./dotnet-install.sh
توسط این دو دستور، فایل dotnet-install.sh دریافت شده و همچنین دسترسی اجرایی بودن آن نیز تنظیم میشود.
پس از آن، برای نصب آخرین نگارش داتنت SDK موجود، تنها کافی است دستور زیر را صادر کنیم:
./dotnet-install.sh --version latest
و یا اگر فقط میخواهید runtime آنرا نصب کنید، پارامترهای نصب، به صورت زیر تغییر میکنند:
./dotnet-install.sh --version latest --runtime aspnetcore
همچنین اگر نگارشهای پایینتر مدنظر شما هستند، میتوانید کانال نصب را هم مشخص کنید:
./dotnet-install.sh --channel 7.0
که یک نمونه خروجی اجرای دستور dotnet-install.sh --version latest/. آن به صورت زیر است:
$ ./dotnet-install.sh --version latest dotnet-install: Attempting to download using aka.ms link https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz dotnet-install: Remote file https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz size is 223236164 bytes. dotnet-install: Extracting archive from https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz dotnet-install: Downloaded file size is 223236164 bytes. dotnet-install: The remote and local file sizes are equal. dotnet-install: Installed version is 8.0.303 dotnet-install: Adding to current process PATH: `/home/vahid/.dotnet`. Note: This change will be visible only when sourcing script. dotnet-install: Note that the script does not resolve dependencies during installation. dotnet-install: To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the "Dependencies" section. dotnet-install: Installation finished successfully.
همانطور که مشاهده میکنید، داتنت 8.0.303، نصب شدهاست.
پس از پایان نصب، دو دستور زیر را هم باید اجرا کنید تا بتوان در خط فرمان به NET CLI. دسترسی یافت:
$ export DOTNET_ROOT=$HOME/.dotnet $ export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools
پس از این تنظیمات، امکان اجرای دو دستور آزمایشی زیر میسر میشوند:
$ dotnet --version $ dotnet --list-sdks
نصب داتنت بر روی نگارشهای قدیمیتر Ubuntu
برای اینکه بتوان به روش متداولی (بدون دریافت اسکریپت نصاب فوق) به آخرین تغییرات انجام شده و آخرین بهروز رسانیها و همچنین تمام نگارشهای داتنت دسترسی داشت، میتوان از مخازن بستههای خود مایکروسافت استفاده کنیم که باید آنها را به سیستم عامل اضافه کرد. برای اینکار باید مراحل زیر طی شوند:
ابتدا تمام وابستگیهای احتمالی موجود را حذف میکنیم:
$ sudo apt remove 'dotnet*' 'aspnet*' 'netstandard*'
سپس فایل زیر را ایجاد کرده:
sudo nano touch /etc/apt/preferences
و آنرا با محتوای زیر تکمیل میکنیم؛ تا فقط از مخازن خود مایکروسافت استفاده شود و سایر مخازن مرتبط در این حالت، اولویت کمتری داشته باشند:
Package: dotnet* aspnet* netstandard* Pin: origin "archive.ubuntu.com" Pin-Priority: -10 Package: dotnet* aspnet* netstandard* Pin: origin "security.ubuntu.com" Pin-Priority: -10
پس از آن، دستورات زیر، کار افزودن مخازن بستههای مایکروسافت را انجام میدهند:
# Get OS version info which adds the $ID and $VERSION_ID variables source /etc/os-release # Download Microsoft signing key and repository wget https://packages.microsoft.com/config/$ID/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb # Install Microsoft signing key and repository sudo dpkg -i packages-microsoft-prod.deb # Clean up rm packages-microsoft-prod.deb # Update packages sudo apt update sudo apt upgrade -y
بعد از این بهروز رسانیها، دستور متداول زیر، کار نصب آخرین نگارش NET SDK. را انجام میدهد:
sudo apt-get install -y dotnet-sdk-8.0
و همچنین هربار هم که سیستم را با دستورات sudo apt update -q && sudo apt upgrade -y به روز کنیم، در صورت وجود بهروز رسانی داتنتی جدیدی، آنرا به صورت خودکار دریافت و نصب میکند.
استفاده از چندین Context در EF 6 Code first
- ORMها صرفا کارهایی را قادر به انجام هستند که بانک اطلاعاتی مورد استفاده پشتیبانی میکند. برای نمونه در SQL Server امکان تهیه رابطه بین دو جدول از دو بانک اطلاعاتی مختلف وجود ندارد. منظور مثلا ایجاد کلید خارجی بین جداول دو بانک اطلاعاتی مختلف است و نه نوشتن کوئری بین آنها که از این لحاظ مشکلی نیست.
A FOREIGN KEY constraint can reference columns in tables in the same database or within the same table
- هدف از پشتیبانی چند Context در EF 6، تلفیق اینها با هم در قالب یک بانک اطلاعاتی است (مطلب فوق).
- همچنین در EF 6 میتوان چندین Context را به چندین بانک اطلاعاتی مختلف با رشتههای اتصالی متفاوت، انتساب داد. مباحث آغاز بانکهای اطلاعاتی هر کدام جداگانه عمل کرده و مستقل از هم عمل میکنند.
یک مثال جهت اجرا و آزمایش:
Sample25.cs
- اگر میخواهید به انعطاف پذیری Linked servers برسید (مثلا در طی یک کوئری و نه چند کوئری از جداول دو بانک اطلاعاتی مختلف در دو سرور متفاوت کوئری بگیرید)، نیاز خواهید داشت مثلا View یا SP تهیه کنید و سپس اینها را در برنامه مورد استفاده قرار دهید. Viewها با استفاده از T-SQL میتوانند کوئری واحدی از چند بانک اطلاعاتی تهیه کنند. اینبار برنامه هم نهایتا به یک بانک اطلاعاتی متصل میشود و برای آن اهمیتی ندارد که View یا SP به چه نحوی تهیه شده و با چند بانک اطلاعاتی کار میکند.
- تراکنشهای توزیع شده هم نیاز به تنظیمات خاصی در SQL Server دارند و باید MSDTC راه اندازی و تنظیم شود: (^). در غیراینصورت پیام خطای The underlying provider failed on Open. MSDTC on server is unavailable را دریافت خواهید کرد. بعد از آن باید از TransactionScope برای کار همزمان با چند Context استفاده کنید.
EF Code First #1
کدش از کتاب Code First که معرفی کردین استفاده کردم اما کد خودتون خطا نداره
using System; using System.Collections.Generic; namespace ChapterOneProject { public class Patient { public Patient() { Visits = new List<Visit>(); } public int Id { get; set; } public string Name { get; set; } public DateTime BirthDate { get; set; } //[ForeignKey("AnimalTypeId")] public AnimalType AnimalType { get; set; } //public int AnimalTypeId { get; set; } public DateTime FirstVisit { get; set; } public List<Visit> Visits { get; set; } } public class Visit { [Key] public int Id { get; set; } public DateTime Date { get; set; } public String ReasonForVisit { get; set; } public String Outcome { get; set; } public Decimal Weight { get; set; } //[ForeignKey("PatientId")] //public virtual Patient Patient { get; set; } public int PatientId { get; set; } } public class AnimalType { public int Id { get; set; } public string TypeName { get; set; } } }
کد کانتکست
public class VetContext : DbContext { public DbSet<Patient> Patients { get; set; } public DbSet<Visit> Visits { get; set; } //public DbSet<AnimalType> AnimalTypes { get; set; } }
var dog = new AnimalType { TypeName = "Dog" }; var visit = new List<Visit> { new Visit { Date = new DateTime(2011, 9, 1), Outcome = "Test", ReasonForVisit = "Test", Weight = 32, } }; var patient = new Patient { Name = "Sampson", BirthDate = new DateTime(2008, 1, 28), AnimalType = dog, Visits = visit, }; using (var context = new VetContext()) { context.Patients.Add(patient); context.SaveChanges(); }
کدهای دیگه تست کردم مشکلی نداشت اما این مورد ؟
با profiler چک کردم خطای عدم توانایی در تبدیل نوع datetime2 به datetime میده
namespace MvcHealthCheckTest.Controllers { public class HomeController : Controller { public IActionResult ViewDetails() { return View(); }
<a asp-controller="Home" asp-action="ViewDetails">View Details</a>
https://localhost:5001/Home/ViewDetails
الف) تمام مسیرها lowercase باشند (مناسب برای SEO؛ از این جهت که تعداد مسیرهای تکراری یکسان با حروف بزرگ و کوچک، به حداقل میرسند)
ب) ViewDetailsها تبدیل به view-details شوند. یعنی بین حروف جدا شدهی با کلمات بزرگ و کوچک، یک - قرار گیرد.
انجام یک چنین تغییرات سراسری در ASP.NET Core 2.2 با معرفی IOutboundParameterTransformer میسر شدهاست:
using System.Text.RegularExpressions; using Microsoft.AspNetCore.Routing; public class CustomUrlTransformer : IOutboundParameterTransformer { public string TransformOutbound(object value) { return value == null ? null : Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2"); } }
روش معرفی آن نیز به سیستم به صورت زیر است:
ابتدا نیاز است این Transformer به صورت یک ConstraintMap جدید به سیستم مسیریابی اضافه شود. در اینجا امکان تنظیم تولید LowercaseUrls نیز وجود دارد. به همین جهت نیازی نیست تا در متد TransformOutbound فوق، در انتهای کار، متد ToLower را نیز فراخوانی کنیم:
namespace MvcTest { public class Startup { public void ConfigureServices(IServiceCollection services) { // ... services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddRouting(option => { option.ConstraintMap.Add(key: "transformer1", value: typeof(CustomUrlTransformer)); option.LowercaseUrls = true; }); }
namespace MvcTest { public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // ... app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller:transformer1}/{action:transformer1}/{id?}", defaults: new { controller = "Home", action = "Index" } ); }); }
app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller:transformer1=Home}/{action:transformer1=Index}/{id?}" ); });
https://localhost:5001/home/view-details
مقدمه
SQL Server، با هر تقاضا به عنوان یک واحد مستقل رفتار میکند. در وضعیتهای پیچیده ای که فعالیتها توسط مجموعه ای از دستورات SQL انجام میشود، به طوری که یا همه باید اجرا شوند یا هیچکدام اجرا نشوند، این روش مناسب نیست. در چنین وضعیت هایی، نه تنها تقاضاهای موجود در یک دنباله به یکدیگر بستگی دارند، بلکه شکست یکی از تقاضاهای موجود در دنباله، به معنای این است که کل تقاضاهای موجود در دنباله باید لغو شوند، و تغییرات حاصل از تقاضاهای اجراشده در آن دنباله خنثی شوند تا بانک اطلاعاتی به حالت قبلی برگردد.1- تراکنش چیست؟
تراکنش شامل مجموعه ای از یک یا چند دستور SQL است که به عنوان یک واحد عمل میکنند. اگر یک دستور SQL در این واحد با موفقیت اجرا نشود، کل آن واحد خنثی میشود و داده هایی که در اجرای آن واحد تغییر کرده اند، به حالت اول برگردانده میشود. بنابراین تراکنش وقتی موفق است که هر یک از دستورات آن با موفقیت اجرا شوند. برای درک مفهوم تراکنش مثال زیر را در نظر بگیرید: سهامدار A در معامله ای 400 سهم از شرکتی را به سهامدار B میفروشد. در این سیستم، معامله وقتی کامل میشود که حساب سهامدار A به اندازه 400 بدهکار و حساب سهامدار B همزمان به اندازه 400 بستانکار شود. اگر هر کدام از این مراحل با شکست مواجه شود، معامله انجام نمیشود.2- خواص تراکنش
هر تراکنش دارای چهار خاصیت است (معروف به ACID) که به شرح زیر میباشند:2-1- خاصیت یکپارچگی (Atomicity)
یکپارچگی به معنای این است که تراکنش باید به عنوان یک واحد منسجم (غیر قابل تفکیک) در نظر گرفته شود. در مثال مربوط به مبادله سهام، یکپارچگی به معنای این است که فروش سهام توسط سهامدار A و خرید آن سهام توسط سهامدار B، مستقل از هم قابل انجام نیستند و برای این که تراکنش کامل شود، هر دو عمل باید با موفقیت انجام شوند.اجرای یکپارچه، یک عمل "همه یا هیچ" است. در عملیات یکپارچه، اگر هر کدام از دستورات موجود در تراکنش با شکست مواجه شوند، اجرای تمام دستورات قبلی خنثی میشود تا به جامعیت بانک اطلاعاتی آسیب نرسد.
2-2- خاصیت سازگاری (Consistency)
سازگاری زمانی وجود دارد که هر تراکنش، سیستم را در یک حالت سازگار قرار دهد (چه تراکنش به طور کامل انجام شود و چه در اثر وجود خطایی خنثی گردد). در مثال مبادله سهام، سازگاری به معنای آن است که هر بدهکاری مربوط به حساب فروشنده، موجب همان میزان بستانکاری در حساب خریدار میشود.در SQL Server، سازگاری با راهکار ثبت فایل سابقه انجام میگیرد که تمام تغییرات را در بانک اطلاعاتی ذخیره میکند و جزییات را برای ترمیم تراکنش ثبت مینماید. اگر سیستم در اثنای اجرای تراکنش خراب شود، فرآیند ترمیم SQL Server با استفاده از این اطلاعات، تعیین میکند که آیا تراکنش با موفقیت انجام شده است یا خیر، و در صورت عدم موفقیت آن را خنثی میکند. خاصیت سازگاری تضمین میکند که بانک اطلاعاتی هیچگاه تراکنشهای ناقص را نشان نمیدهد.
2-3- خاصیت تفکیک (Isolation)
تفکیک موجب میشود هر تراکنش در فضای خودش و جدا از سایر تراکنشهای دیگری که در سیستم انجام میگیرد، اجرا شود و نتایج هر تراکنش فقط در صورت کامل شدن آن قابل مشاهده است. اگر چندین تراکنش همزمان در سیستم در حال اجرا باشند، اصل تفکیک تضمین میکند که اثرات یک تراکنش تا کامل شدن آن، قابل مشاهده نیست. در مثال مربوط به مبادله سهام، اصل تفکیک به معنای این است که تراکنش بین دو سهامدار، مستقل از تمام تراکنشهای دیگری است که در سیستم به مبادله سهام میپردازند و اثر آن وقتی برای افراد قابل مشاهده است که آن تراکنش کامل شده باشد. این اصل در مواردی که سیستم همزمان از چندین کاربر پشتیبانی میکند، مفید است.2-4- پایداری (Durability)
پایداری به معنای این است که تغییرات حاصل از نهایی شدن تراکنش، حتی در صورت خرابی سیستم نیز پایدار میماند. اغلب سیستمهای مدیریت بانک اطلاعاتی رابطه ای، از طریق ثبت تمام فعالیتهای تغییر دهندهی دادهها در بانک اطلاعاتی، پایداری را تضمین میکنند. در صورت خرابی سیستم یا رسانه ذخیره سازی داده ها، سیستم قادر است آخرین بهنگام سازی موفق را هنگام راه اندازی مجدد، بازیابی کند. در مثال مربوط به مبادله سهام، پایداری به معنای این است که وقتی انتقال سهام از سهامدار A به B با موفقیت انجام گردید، حتی اگر سیستم بعداً خراب شد، باید نتیجهی آن را منعکس سازد.3- مشکلات همزمانی(Concurrency Effects):
3-1- Dirty Read:
زمانی روی میدهد که تراکنشی رکوردی را میخواند، که بخشی از تراکنشی است که هنوز تکمیل نشده است، اگر آن تراکنش Rollback شود اطلاعاتی از بانک اطلاعاتی دارید که هرگز روی نداده است.اگر سطح جداسازی تراکنش (پیش فرض) Read Committed باشد، این مشکل بوجود نمیآید.
3-2- Non-Repeatable Read:
زمانی ایجاد میشود که رکوردی را دو بار در یک تراکنش میخوانید و در این اثنا یک تراکنش مجزای دیگر دادهها را تغییر میدهد. برای پیشگیری از این مسئله باید سطح جداسازی تراکنش برابر با Repeatable Read یا Serializable باشد.3-3- Phantoms:
با رکوردهای مرموزی سروکار داریم که گویی تحت تاثیر عبارات Update و Delete صادر شده قرار نگرفته اند. به طور خلاصه شخصی عبارت Insert را درست در زمانی که Update مان در حال اجرا بوده انجام داده است، و با توجه به اینکه ردیف جدیدی بوده و قفلی وجود نداشته، به خوبی انجام شده است. تنها چاره این مشکل تنظیم سطح Serializable است و در این صورت بهنگام رسانیهای جداول نباید درون بخش Where قرار گیرد، در غیر این صورت Lock خواهند شد.3-4- Lost Update:
زمانی روی میدهد که یک Update به طور موفقیت آمیزی در بانک اطلاعاتی نوشته میشود، اما به طور اتفاقی توسط تراکنش دیگری بازنویسی میشود. راه حل این مشکل بستگی به کد شما دارد و بایست به نحوی تشخیص دهید، بین زمانی که دادهها را میخوانید و زمانی که میخواهید آنرا بهنگام کنید، اتصال دیگری رکورد شما را بهنگام کرده است.4- منابع قابل قفل شدن
6 منبع قابل قفل شدن برای SQL Server وجود دارد و آنها سلسله مراتبی را تشکیل میدهند. هر چه سطح قفل بالاتر باشد، Granularity کمتری دارد. در ترتیب آبشاری Granularity عبارتند از:• Database: کل بانک اطلاعاتی قفل شده است، معمولاً طی تغییرات Schema بانک اطلاعاتی روی میدهد.
• Table: کل جدول قفل شده است، شامل همه اشیای مرتبط با جدول.
• Extent: کل Extent (متشکل از هشت Page) قفل شده است.
• Page: همه دادهها یا کلیدهای Index در آن Page قفل شده اند.
• Key: قفلی در کلید مشخصی یا مجموعه کلید هایی Index وجود دارد. ممکن است سایر کلیدها در همان Index Page تحت تاثیر قرار نگیرند.
• (Row or Row Identifier (RID: هر چند قفل از لحاظ فنی در Row Identifier قرار میگیرد ولی اساساً کل ردیف را قفل میکند.
5- تسریع قفل (Lock Escalation) و تاثیرات قفل روی عملکرد
اگر تعداد آیتمهای قفل شده کم باشد نگهداری سطح بهتری از Granularity (مثلاً RID به جای Page) معنی دار است. هرچند با افزایش تعداد آیتمهای قفل شده، سربار مرتبط با نگهداری آن قفلها در واقع باعث کاهش عملکرد میشود، و میتواند باعث شود قفل به مدت طولانیتری در محل باشد(هر چه قفل به مدت طولانیتری در محل باشد، احتمال این که شخصی آن رکورد خاص را بخواهد بیشتر است).هنگامی که تعداد قفل نگهداری شده به آستانه خاصی برسد آن گاه قفل به بالاترین سطح بعدی افزایش مییابد و قفلهای سطح پایینتر نباید به شدت مدیریت شوند (آزاد کردن منابع و کمک به سرعت در مجادله).
توجه شود که تسریع مبتنی بر تعداد قفل هاست و نه تعداد کاربران.
6- حالات قفل (Lock Modes):
همانطور که دامنه وسیعی از منابع برای قفل شدن وجود دارد، دامنه ای از حالات قفل نیز وجود دارد.6-1- (Shared Locks (S:
زمانی استفاده میشود، که فقط باید دادهها را بخوانید، یعنی هیچ تغییری ایجاد نخواهید کرد. Shared Lock با سایر Shared Lockهای دیگر سازگار است، البته قفلهای دیگری هستند که با Shared Lock سازگار نیستند. یکی از کارهایی که Shared Lock انجام میدهد، ممانعت از انجام Dirty Read از طرف کاربران است.6-2- (Exclusive Locks (X:
این قفلها با هیچ قفل دیگری سازگار نیستند. اگر قفل دیگری وجود داشته باشد، نمیتوان به Exclusive Lock دست یافت و همچنین در حالی که Exclusive Lock فعال باشد، به هر قفل جدیدی از هر شکل اجازه ایجاد شدن در منبع را نمیدهند.این قفل از اینکه دو نفر همزمان به حذف کردن، بهنگام رسانی و یا هر کار دیگری مبادرت ورزند، پیشگیری میکند.
6-3- (Update Locks (U:
این قفل ها نوعی پیوند میان Shared Locks و Exclusive Locks هستند.برای انجام Update باید بخش Where را (در صورت وجود) تایید اعتبار کنید، تا دریابید فقط چه ردیف هایی را میخواهید بهنگام رسانی کنید. این بدان معنی است که فقط به Shared Lock نیاز دارید، تا زمانی که واقعاً بهنگام رسانی فیزیکی را انجام دهید. در زمان بهنگام سازی فیزیکی نیاز به Exclusive Lock دارید.
Update Lock نشان دهنده این واقعیت است که دو مرحله مجزا در بهنگام رسانی وجود دارد، Shared Lock ای دارید که در حال تبدیل شدن به Exclusive Lock است. Update Lock تمامی Update Lockهای دیگر را از تولید شدن باز میدارند، و همچنین فقط با Shared Lock و Intent Shared Lockها سازگار هستند.
6-4- Intent Locks:
با سلسله مراتب شی سر و کار دارد. بدون Intent Lock، اشیای سطح بالاتر نمیدانند چه قفلی را در سطح پایینتر داشته اید. این قفلها کارایی را افزایش میدهند و 3 نوع هستند:6-4-1- (Intent Shared Lock (IS:
Shared Lock در نقطه پایینتری در سلسله مراتب، تولید شده یا در شرف تولید است. این نوع قفل تنها به Table و Page اعمال میشود.6-4-2- (Intent Exclusive Lock (IX:
همانند Intent Shared Lock است اما در شرف قرار گرفتن در آیتم سطح پایینتر است.6-4-3- (Shared With Intent Exclusive (SIX:
Shared Lock در پایین سلسله مراتب شی تولید شده یا در شرف تولید است اما Intent Lock قصد اصلاح دادهها را دارد بنابراین در نقطه مشخصی تبدیل به Intent Exclusive Lock میشود.6-5- Schema Locks:
به دو شکل هستند:6-5-1- (Schema Modification Lock (Sch-M:
تغییر Schema به شی اعمال شده است. هیچ پرس و جویی یا سایر عبارتهای Create، Alter و Drop نمیتوانند در مورد این شی در مدت قفل Sch-M اجرا شوند. با همه حالات قفل ناسازگار است.6-5-2- (Schema Stability Lock (Sch-S:
بسیار شبیه به Shared Lock است، هدف اصلی این قفل پیشگیری از Sch-M است وقتی که قبلاً قفل هایی برای سایر پرس و جو-ها (یا عبارتهای Create، Alter و Drop) در شی فعال شده اند. این قفل با تمامی انواع دیگر قفل سازگار است به جز با Sch-M.6-6- (Bulk Update Locks (BU:
این قفلها بارگذاری موازی دادهها را امکان پذیر میکنند، یعنی جدول در مورد هر فعالیت نرمال (عبارات T-SQL) قفل میشود، اما چندین عمل bcp یا Bulk Insert را میتوان در همان زمان انجام داد. این قفل فقط با Sch-S و سایر قفل هایBU سازگار است.7- سطوح جداسازی (Isolation Level):
7-1- Read Committed (وضعیت پیش فرض):
با Read Committed همه Shared Lockهای ایجاد شده، به محض اینکه عبارت ایجاد کننده آنها تکمیل شود، به طور خودکار آزاد میشوند. به طور خلاصه قفلهای مرتبط با عبارت Select به محض تکمیل عبارت Select آزاد میشوند و SQL Server منتظر پایان تراکنش نمیماند. اگر تراکنش پرس و جویی را انجام میدهد که دادهها را اصلاح میکند (Insert، Delete و Update) قفلها برای مدت تراکنش نگه داشته میشوند.با این سطح پیش فرض، میتوانید مطمئن شوید جامعیت کافی برای پیشگیری از Dirty Read دارید، اما همچنان Phantoms و Non-Repeatable Read میتواند روی دهد.
7-2- Read Uncommitted:
خطرناکترین گزینه از میان تمامی گزینهها است، اما بالاترین عملکرد را به لحاظ سرعت دارد. در واقع با این تنظیم سطح تجربه همه مسائل متعدد هم زمانی مانند Dirty Read امکان پذیر است. در واقع با تنظیم این سطح به SQL Server اعلام میکنیم هیچ قفلی را تنظیم نکرده و به هیچ قفلی اعتنا نکند، بنابراین هیچ تراکنش دیگری را مسدود نمیکنیم.میتوانید همین اثر Read Uncommitted را با اضافه کردن نکته بهینه ساز NOLOCK در پرس و جوها بدست آورید.
7-3- Repeatable Read:
سطح جداسازی را تا حدودی افزایش میدهد و سطح اضافی محافظت همزمانی را با پیشگیری از Dirty Read و همچنین Non-Repeatable Read فراهم میکند.پیشگیری از Non-Repeatable Read بسیار مفید است اما حتی نگه داشتن Shared Lock تا زمان پایان تراکنش میتواند دسترسی کاربران به اشیا را مسدود کند، بنابراین به بهره وری لطمه وارد میکند.
نکته بهینه ساز برای این سطح REPEATEABLEREAD است.
7-4- Serializable:
این سطح از تمام مسائل هم زمانی پیشگیری میکند به جز برای Lost Update.این تنظیم سطح به واقع بالاترین سطح آنچه را که سازگاری نامیده میشود، برای پایگاه داده فراهم میکند. در واقع فرآیند بهنگام رسانی برای کاربران مختلف به طور یکسان عمل میکند به گونه ای که اگر همه کاربران یک تراکنش را در یک زمان اجرا میکردند، این گونه میشد « پردازش امور به طور سریالی».
با استفاده از نکته بهینه ساز SERIALIZABLE یا HOLDLOCK در پرس و جو شبیه سازی میشود.
7-5- Snapshot:
جدترین سطح جداسازی است که در نسخه 2005 اضافه شد، که شبیه ترکیبی از Read Committed و Read Uncommitted است. به طور پیش فرض در دسترس نیست، در صورتی در دسترس است که گزینه ALLOW_SNAPSHOT_ISOLATION برای بانک اطلاعاتی فعال شده باشد.(برای هر بانک اطلاعاتی موجود در تراکنش)Snapshot مشابه Read Uncommitted هیچ قفلی ایجاد نمیکند. تفاوت اصلی آنها در این است که تغییرات صورت گرفته در بانک اطلاعاتی را در زمانهای متفاوت تشخیص میدهند. هر تغییر در بانک اطلاعاتی بدون توجه به زمان یا Commit شدن آن، توسط پرس و جو هایی که سطح جداسازی Read Uncommitted را اجرا میکنند، دیده میشود. با Snapshot فقط تغییراتی که قبل از شروع تراکنش، Commit شده اند، مشاهده میشود.
از شروع تراکنش Snapshot، تمامی دادهها دقیقاً مشاهده میشوند، زیرا در شروع تراکنش Commit شده اند.
نکته: در حالی که Snapshot توجهی به قفلها و تنظیمات آنها ندارد، یک حالت خاص وجود دارد. چنانچه هنگام انجام Snapshot یک عمل Rollback (بازیافت) بانک اطلاعاتی در جریان باشد، تراکنش Snapshot قفلهای خاصی را برای عمل کردن به عنوان یک مکان نگهدار و سپس انتظار برای تکمیل Rollback تنظیم میکند. به محض تکمیل Rollback، قفل حذف شده و Snapshot به طور طبیعی به جلو حرکت خواهد کرد.