.NET 7 Beginner Course 🚀 Web API, Entity Framework 7 & SQL Server
Table of Contents:
00:00:00 .NET 7 Beginner Course 🚀 Web API, Entity Framework 7 & SQL Server
00:01:18 Tools (Visual Studio Code & .NET SDK)
00:02:48 Create a new Web API
00:11:34 First API Call
00:15:23 Git Repository & .gitignore File
00:19:07 Web API Introduction
00:19:42 The Model-View-Controller (MVC) Pattern
00:22:03 New Models
00:26:17 New Controller & GET a New Character
00:36:35 First Steps with Attribute Routing
00:40:52 Routing with Parameters
00:43:34 HTTP Request Methods Explained
00:46:48 Add a New Character with POST
00:50:23 Best Practice: Web API Structure
00:53:42 Character Service
01:02:38 Fix the “Possible ArgumentNullException”
01:04:43 Asynchronous Calls
01:08:53 Proper Service Response with Generics
01:17:06 Data-Transfer-Objects (DTOs)
01:22:58 AutoMapper
01:35:30 Modify a Character with PUT
01:47:40 Modify a Character with AutoMapper
01:49:12 Delete a Character
01:54:15 Web API Summary
01:55:01 Entity Framework 7 Introduction
01:55:50 Object-Relational-Mapping & Code-First Migration Explained
01:57:42 Installing Entity Framework 7
02:00:48 Installing SQL Server Express (with Management Studio)
02:02:04 Implementing the DataContext
02:05:37 ConnectionString & Adding the DbContext
02:10:29 First Migration
02:14:49 GET Implementations
WCF
- مقایسهای کوتاه بین WCF و ASMX
- نحوه استفاده از TransactionFlow در WCF
- مدیریت Instance در WCF
- WCF Method Overloading
- مدیریت تغییرات در سیستمهای مبتنی بر WCF
- آشنایی با KnownTypeAttribute در WCF
- Data Contracts and Circular References
- استفاده از Lambda Expression در پروژههای مبتنی بر WCF
- فراخوانی سرویسهای WCF به صورت Async
- مقایسه بین Proxy و ChannelFactory در WCF
- بررسی متدهای یک طرفه در WCF
- Message Header سفارشی در WCF
- MTOM در WCF
- اعتبارسنجی سرویسهای WCF
- تغییر فضای نام کلاس poco استفاده شده در WCF و از کار افتادن برنامهی مشتری بدون دریافت پیام خطا
- پیاده سازی ServiceHostFactory سفارشی در WCF
- پیاده سازی InstanceProvider برای سرویسهای WCF
- Long Polling در WCF
- Routing Service در WCF
- فیلترها در WCF Routing Service
- ایجاد سرویس چندلایهی WCF با Entity Framework در قالب پروژه - 1
- ایجاد سرویس چندلایهی WCF با Entity Framework در قالب پروژه - 2
- ایجاد سرویس چندلایهی WCF با Entity Framework در قالب پروژه - 3
- ایجاد سرویس چندلایهی WCF با Entity Framework در قالب پروژه - 4
- ایجاد سرویس چندلایهی WCF با Entity Framework در قالب پروژه - 5
- ایجاد سرویس چندلایهی WCF با Entity Framework در قالب پروژه - 6
- ایجاد سرویس چندلایهی WCF با Entity Framework در قالب پروژه - 7
- ایجاد سرویس چندلایهی WCF با Entity Framework در قالب پروژه - 8
- ایجاد سرویس چندلایهی WCF با Entity Framework در قالب پروژه - 9
- ایجاد سرویس چندلایهی WCF با Entity Framework در قالب پروژه - 10
- فعال سازی فشرده سازی
- انتقال لاگها به یک سیستم راه دور
- حذف لاگ فایلهای قدیمی از طریق اسکریپت نویسی
- حذف لاگ فایلهای قدیمی توسط IIS Log File Cleaner
فشرده سازی دایرکتوری لاگ فایل ها
%SystemDrive%\inetpub\logs\LogFiles
این روش سادهترین روش موجود برای مدیریت لاگ هاست ولی روش نهایی نیست و باز به مرور زمان این روش هم کارایی خودش را از دست خواهد داد. این روش بیشتر شبیه خرید زمان میباشد تا اینکه یک راه حل نهایی برای حل مشکل باشد. البته این را هم باید مدنظر داشت که موقع تیک زدن گزینه بالا عملیات فشرده سازی باعث کند شدن سرعت کامپیوتر در حین آغاز عمل ذخیره سازی لاگ فایلها هم خواهد شد. پس اگر قصد چنین کاری ذا دارید در ساعاتی که سرور کمترین فشار از طرف کاربران را دارد یا اصطلاحا پیک کاری آن پایین است انجامش دهید.
انتقال لاگ فایلها به یک سیستم راه دور
همانطور که در بالا اشاره کردیم محل پیش فرض ذخیره سازی لاگها درمسیر
%SystemDrive%\inetpub\logs\LogFiles
قرار دارد و این محل ذخیره سازی برای هر سرور یا حتی یک وب سایت خاص در صفحه تنظیمات Logging مشخص شده است و شما در میتوانید این لاگها را حتی برای کل سرور یا مربوط به یک سایت خاص، به سروری دیگر انتقال دهید. این امکان میتواند به امنیت سیستم هم کمک فراوانی کند تا اگر دیسک محلی Local Disk هم دچار مشکل شد، باز خواندن لاگ فایلها میسر باشد و با استفاده از ابزارهای تحلیل لاگ فایل ها، آنها را مورد بررسی قرار دهیم. برای تغییر محل ذخیره سازی لاگها به یک سیستم راه دور، راه حل زیر را طی کنید.
در IIS وب سایتی را که میخواهید لاگ آن انتقال یابد، انتخاب کنید؛ یا اگر لاگ کل سیستم IIS را میخواهید انتقال بدهید نام سرور را در لیست درختی انتخاب کنید و از ماژولهای سمت راست، ماژول Logging را انتخاب کنید و در قسمت Directory که محل ذخیره سازی فعلی لاگها را نوشته شده است، به صورت UNC آدرس دهی کنید. در آدرس زیر اولی نام سرور است Contoso-server1\\ و دومی هم Logs نام پوشهای که به اشتراک گذاشته شده است.
حذف لاگ فایلهای قدیمی با استفاده از اسکریپت
با این روش میتوانید لاگ فایل هایی را که بعد از مدتی معین که دلخواه شما هست، از سیستم حذف نمایید و اگر این اسکریپت را زمان بندی خودکار نمایید، میتوانید از مراقبت مداوم و ثابت این کار نیز رها شوید.
با ستفاده از VBScript بررسی میکنیم که اگر مثلا عمر لاگ فایل به 30 روز رسیده است، باید حذف شوند. خط دوم کد زیر نهایت عمر یک لاگ فایل را مشخص میکند:
sLogFolder = "c:\inetpub\logs\LogFiles" iMaxAge = 30 'in days Set objFSO = CreateObject("Scripting.FileSystemObject") set colFolder = objFSO.GetFolder(sLogFolder) For Each colSubfolder in colFolder.SubFolders Set objFolder = objFSO.GetFolder(colSubfolder.Path) Set colFiles = objFolder.Files For Each objFile in colFiles iFileAge = now-objFile.DateCreated if iFileAge > (iMaxAge+1) then objFSO.deletefile objFile, True end if Next Next
اسکریپت بالا تمامی subfolderها را برای همه سایتها بررسی کرده و لاگهای آنان را حذف میکند. ولی اگر دوست دارید این عملیات را تنها به یک وب سایت محدود کنید، باید مسیر را در خط اول دقیقتر مشخص کنید.
برای اجرای دستی اسکریپت در cmd تایپ کنید:
cscript.exe c:\scripts\retentionscript.vbs
ولی اگر میخواهید این اسکریپت در هر دورهی زمانی خاص اجرا شود، یا زمان بندی Scheduling گردد، دیگر مجبور نیستید هر بار به فکر نگهداری از لاگها باشید.
زمان بندی اجرای اسکریپت
server manager (قابل تست در ویندوزهای سرور) را باز کرده و از منوی Tools گزینه Task Scheduler را انتخاب کنید و در قسمت Actions گزینه Create Task را انتخاب نمایید. در کادر باز شده نام "Delete Log Files " را برای مثال برگزینید و در قسمت Security هم کاربری که اجازه اجرای اسکریپت را دارد مشخص کنید.
برگه Triggers را انتخاب کرده و گزینه New را انتخاب کنید و عملیات زمان بندی را تنظیم کنید و حتما بعد از زمان بندی مطمئن باشید که تیک Enabled فعال است.
در برگه Actions هم گزینه New را انتخاب کنید؛ در کادر باز شده از لیست Start a program را انتخاب کرده و در قسمت Program\script، دستور cscript را ذکر نمایید و به عنوان آرگومان ورودی Add arguments هم مسیر اسکریپت خود را ذکر نمایید و کادر را تایید کنید.
برای آغاز زمان بندی در لیست وظیفههای فعال active task pane، وظیفه ای که الان ساخته اید را اجرا کرده و به مسیر ذخیره لاگها رفته و میبینید که لاگهای مورد نظر حذف شدهاند؛ پس از صحت اجرای اسکریپت مطمئن میشویم. دوباره به لیست وظایف رفته و گزینه End را بزنید تا وظیفه، در حالت Ready قرار گیرد تا از همین الان فرایند زمان بندی اجرای اسکریپت آغاز شود.
حذف لاگ فایلها با استفاده از IIS Log Cleaner Tools
سادهترین ابزار برای مدیریت حذف لاگ فایل هاست که هر یک ساعت یکبار اجرا شده و لاگ فایلهای تاریخ گذشته را که زمانش را شما تعیین میکنید، به سمت سطل زباله که البته درستش بازیافت است Recycle Bin انتقال میدهد تا از ضرر از دست دادن لاگها جلوگیری کند که بعدا شما میتوانید آنها را به صورت دستی حذف کنید. همچنین عملیات خودکار حذف را نیز میتوان متوقف نمود.
ابتدا برنامه را از اینجا دانلود کنید. موقعیکه برنامه را اجرا کنید، در نوتیفیکیشن taskbar مینشیند و برنامه با یک پیغام به شما اعلام میکند، این اولین بار است که برنامه را باز کردهاید. پس یک سر به setting آن بزنید؛ با انتخاب گزینهی settings برنامه بسته شده و فایل Settings.txt برای شما باز میشود که مدت زمان عمر لاگ فایل و مسیر ذخیره آنها، از شما پرسیده میشود که مقدار عمر هر لاگ فایل به طور پیش فرض 30 روز و مسیر ذخیرهی لاگها همان مسیر پیش فرض IIS است که اگر شما دستی آن را تغییر داده اید، با پرسیدن آن، از محل لاگها اطمینان کسب میکند. در صورتی که قصد تغییری را در فایل، دارید آن را تغییر داده و ذخیره کنید و برنامه را مجددا اجرا کنید.
نکات نهایی در مورد این برنامه :
- اگر از ابزار IIS Cleaner Tool استفاده میکنید باید دستی سطل بازیافت را هم پاک کنید و هم اینکه میتوانید یک محدودیت حجمی برای Recycle Bin قرار دهید که اگر به یک حدی رسید، خودکار پاک کند تا مشکلی برای سیستم عامل ایجاد نشود که البته به طور پیش فرض چنین است.
- برنامه بالا به طور پیش فرض ریشهی لاگها را حذف میکند. پس اگر میخواهید فقط سایت خاصی را مد نظر داشته باشد، آدرس دایرکتوری آن را اضافه کنید. البته چون این برنامه فقط روی یک دایرکتوری کار میکند و شما چند وب سایت دارید و مثلا میخواهید سه تای آنها را پاکسازی کنید، چارهی جز استفاده از اسکریپتهای با زمان بندی ندارید.
- برنامهی بالا فقط فایل هایی با پسوند log را به سطل بازیافت انتقال میدهد.
- برنامهی بالا یک سرویس نیست و باید به طور دستی توسط کاربر اجرا گردد. پس اگر ریست هم شد باید دستی اجرا شود یا آن را به داخل پوشه startup بکشید.
- برنامه برای اجرایش نیاز به لاگین کاربر و مجوز نوشتن در آن پوشه را دارد تا به درستی کار کند.
بررسی برخی تغییرات در Angular 8
- کامپایل سریعتری را فراهم میکند (انتشار در انگیولار 9)
- بررسی type در قالبها، خیلی بیشتر بهبود یافته است؛ بهگونهای که میتوان خطاهای بیشتری را در زمان build گرفت که باعث میشود کاربران در زمان runtime به آن خطاها برخورد نکنند (انتشار در انگیولار 9).
- bundleهای با سایز کوچکتری در مقایسه با سایز bundleهای کامپایل شدهی جاری
- کدهای تولید شده توسط Angular compiler، بسیار آسانتر، برای خواندن و درک انسان است.
- آخرین و مهمترین ویژگی مورد علاقه من این است که میتوان قالبها (templates) را debug کرد. من یقین دارم که این ویژگی توسط تعداد زیادی از توسعه دهندگان مورد توجه قرار خواهد گرفت .
ng serve --aot
"projects": { "app-name": { "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", }, "test": { "builder": "@angular-devkit/build-angular:karma", }, "lint": { "builder": "@angular-devkit/build-angular:tslint", }, "e2e": { "builder": "@angular-devkit/build-angular:protractor", } } } }
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;
بهروزرسانی فایلهای Resource در زمان اجرا
یکی از ویژگیهای مهمی که در پیاده سازی محصول با استفاده از فایلهای Resource باید به آن توجه داشت، امکان بروز رسانی محتوای این فایلها در زمان اجراست. از آنجاکه احتمال اینکه کاربران سیستم خواهان تغییر این مقادیر باشند بسیار زیاد است، بنابراین درنظر گرفتن چنین ویژگیای برای محصول نهایی میتواند بسیار تعیین کننده باشد. متاسفانه پیاده سازی چنین امکانی درباره فایلهای Resource چندان آسان نیست. زیرا این فایلها همانطور که در قسمت قبل توضیح داده شد پس از کامپایل به صورت اسمبلیهای ستلایت (Satellite Assembly) درآمده و دیگر امکان تغییر محتوای آنها بصورت مستقیم و به آسانی وجود ندارد.private void TestResXResourceReader() { using (var reader = new ResXResourceReader("Resource1.fa.resx")) { foreach (var item in reader) { var resource = (DictionaryEntry)item; Console.WriteLine("{0}: {1}", resource.Key, resource.Value); } } }
public class ResXResourceEntry { public string Key { get; set; } public string Value { get; set; } public ResXResourceEntry() { } public ResXResourceEntry(object key, object value) { Key = key.ToString(); Value = value.ToString(); } public ResXResourceEntry(DictionaryEntry dictionaryEntry) { Key = dictionaryEntry.Key.ToString(); Value = dictionaryEntry.Value != null ? dictionaryEntry.Value.ToString() : string.Empty; } public DictionaryEntry ToDictionaryEntry() { return new DictionaryEntry(Key, Value); } }
private static List<ResXResourceEntry> Read(string filePath) { using (var reader = new ResXResourceReader(filePath)) { return reader.Cast<object>().Cast<DictionaryEntry>().Select(de => new ResXResourceEntry(de)).ToList(); } }
private static void Write(IEnumerable<ResXResourceEntry> resources, string filePath) { using (var writer = new ResXResourceWriter(filePath)) { foreach (var resource in resources) { writer.AddResource(resource.Key, resource.Value); } } }
private static void AddOrUpdate(ResXResourceEntry resource, string filePath) { var list = Read(filePath); var entry = list.SingleOrDefault(l => l.Key == resource.Key); if (entry == null) { list.Add(resource); } else { entry.Value = resource.Value; } Write(list, filePath); }
private static void Remove(string key, string filePath) { var list = Read(filePath); list.RemoveAll(l => l.Key == key); Write(list, filePath); }
public class ResXResourceManager { private static readonly object Lock = new object(); public string ResourcesPath { get; private set; } public ResXResourceManager(string resourcesPath) { ResourcesPath = resourcesPath; } public IEnumerable<ResXResourceEntry> GetAllResources(string resourceCategory) { var resourceFilePath = GetResourceFilePath(resourceCategory); return Read(resourceFilePath); } public void AddOrUpdateResource(ResXResourceEntry resource, string resourceCategory) { var resourceFilePath = GetResourceFilePath(resourceCategory); AddOrUpdate(resource, resourceFilePath); } public void DeleteResource(string key, string resourceCategory) { var resourceFilePath = GetResourceFilePath(resourceCategory); Remove(key, resourceFilePath); } private string GetResourceFilePath(string resourceCategory) { var extension = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName == "en" ? ".resx" : ".fa.resx"; var resourceFilePath = Path.Combine(ResourcesPath, resourceCategory.Replace(".", "\\") + extension); return resourceFilePath; } private static void AddOrUpdate(ResXResourceEntry resource, string filePath) { var list = Read(filePath); var entry = list.SingleOrDefault(l => l.Key == resource.Key); if (entry == null) { list.Add(resource); } else { entry.Value = resource.Value; } Write(list, filePath); } private static void Remove(string key, string filePath) { var list = Read(filePath); list.RemoveAll(l => l.Key == key); Write(list, filePath); } private static List<ResXResourceEntry> Read(string filePath) { lock (Lock) { using (var reader = new ResXResourceReader(filePath)) { var list = reader.Cast<object>().Cast<DictionaryEntry>().ToList(); return list.Select(l => new ResXResourceEntry(l)).ToList(); } } } private static void Write(IEnumerable<ResXResourceEntry> resources, string filePath) { lock (Lock) { using (var writer = new ResXResourceWriter(filePath)) { foreach (var resource in resources) { writer.AddResource(resource.Key, resource.Value); } } } } }
public class BuildManager { public string ProjectPath { get; private set; } public BuildManager(string projectPath) { ProjectPath = projectPath; } public void Build() { var regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0"); if (regKey == null) return; var msBuildExeFilePath = Path.Combine(regKey.GetValue("MSBuildToolsPath").ToString(), "MSBuild.exe"); var startInfo = new ProcessStartInfo { FileName = msBuildExeFilePath, Arguments = ProjectPath, WindowStyle = ProcessWindowStyle.Hidden }; var process = Process.Start(startInfo); process.WaitForExit(); } }
public class ResXResourceFileManager { public static readonly string BinPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase.Replace("file:///", "")); public static readonly string ResourcesPath = Path.Combine(BinPath, @"..\App_Data\Resources"); public static readonly string ResourceProjectPath = Path.Combine(ResourcesPath, "Resources.csproj"); public static readonly string DefaultsPath = Path.Combine(ResourcesPath, "Defaults"); public static void CopyDlls() { File.Copy(Path.Combine(ResourcesPath, @"bin\debug\Resources.dll"), Path.Combine(BinPath, "Resources.dll"), true); File.Copy(Path.Combine(ResourcesPath, @"bin\debug\fa\Resources.resources.dll"), Path.Combine(BinPath, @"fa\Resources.resources.dll"), true); Directory.Delete(Path.Combine(ResourcesPath, "bin"), true); Directory.Delete(Path.Combine(ResourcesPath, "obj"), true); } public static void RestoreAll() { RestoreDlls(); RestoreResourceFiles(); } public static void RestoreDlls() { File.Copy(Path.Combine(DefaultsPath, @"bin\Resources.dll"), Path.Combine(BinPath, "Resources.dll"), true); File.Copy(Path.Combine(DefaultsPath, @"bin\fa\Resources.resources.dll"), Path.Combine(BinPath, @"fa\Resources.resources.dll"), true); } public static void RestoreResourceFiles(string resourceCategory) { RestoreFile(resourceCategory.Replace(".", "\\")); } public static void RestoreResourceFiles() { RestoreFile(@"Global\Configs"); RestoreFile(@"Global\Exceptions"); RestoreFile(@"Global\Paths"); RestoreFile(@"Global\Texts"); RestoreFile(@"ViewModels\Employees"); RestoreFile(@"ViewModels\LogOn"); RestoreFile(@"ViewModels\Settings"); RestoreFile(@"Views\Employees"); RestoreFile(@"Views\LogOn"); RestoreFile(@"Views\Settings"); } private static void RestoreFile(string subPath) { File.Copy(Path.Combine(DefaultsPath, subPath + ".resx"), Path.Combine(ResourcesPath, subPath + ".resx"), true); File.Copy(Path.Combine(DefaultsPath, subPath + ".fa.resx"), Path.Combine(ResourcesPath, subPath + ".fa.resx"), true); } }
window.location.reload();
ساختار پروژه های Angular
$routeProvider.when('/about', {templateUrl:'views/about.html', resolve:{deps:function($q, $rootScope) { var deferred = $q.defer(); var dependencies = [ 'controllers/AboutViewController.js', 'directives/some-directive.js' ]; //*نکته اول $script(dependencies, function() { // *نکته دوم $rootScope.$apply(function() { deferred.resolve(); }); }); return deferred.promise; }}})
تغییر اندازه تصاویر #2
در خط 103 از برنامه بالا شما میتوانید به جای کد زیر
byte[] sourceImage = GetImageFromDatabase(id);
از کدی شبیه به این استفاده نمایید
byte[] sourceImage = GetImageFromDisk( Path.Combine(context.Server.MapPath("~/uploads"),Path.GetFileName(id.ToString())));
که مسیر uploads یک مسیر دلخواه است که فایلهای شما در ان قرار گرفته است. نحوه فراخوانی در این حالت به شکل زیر خواهد بود
PhotoHandler.ashx?PhotoId=test.jpg&Size=S مانند <img src='PhotoHandler.ashx?PhotoId=test.jpg&Size=S' alt='تصویر ازمایشی' />
درخواست راهنمایی بیشتر
بنده با مطالعه (بخشی از) این مطالب:
مسیر راه ASP.NET MVC ،
بررسی مفاهیم معکوس سازی وابستگیها و ابزارهای مرتبط با آن ،
AutoMapper ،
چک لیست تهیه یک برنامه ASP.NET MVC ،
تا حد زیاد نحوه پیاده سازی back end پروژه را درک کردم (البته ابتدا پروژه فروشگاه اینترنتی شهر طلایی من که سادهتر است را بررسی کردم).
با این وجود بخش هایی از پروژه از نظر بنده همچنان پیچیده است!
- مدیریت کاربران، نقشها و تعیین دسترسی ها؛
- نحوه لاگ کردن فعالیت ها؛
- ارسال و دریافت پیام؛
- نحوه پیادهسازی front end پروژه (به طور خاص)،
لطفا در صورت امکان در این موارد (به ویژه مورد آخر طراحی UI) اگر منبعی یا مسیر راهی وجود دارد، معرفی نمایید.
با تشکر،