This is a companion extension to the C# extension that brings some improvements to the experience for debugging Blazor WebAssembly in VS Code.
علاوه بر مواردی که ذکر شد، حذف منطقی میتواند به عنوان روشی برای حذف مطرح شود؛ به این صورت که حذف یک رکورد، در دو مرحله صورت گیرد:
- مرحله اول، حذف منطقی: کاربر اقدام به حذف رکورد مورد نظر را میکند. بعد از حذف، خبری از نمایش رکورد مربوطه نخواهد بود .
- مرحله دوم، حذف فیزیکی: مدیر اصلی میتواند تصمیم بگیرد که رکوردهای حذف منطقی شده واقعا حذف شوند یا خیر. فقط مدیر اصلی و سایر افردای که دسترسی حذف مرحله دوم را داشته باشند، از روند دو مرحلهای حذف با خبر هستند.
در ادامه قصد داریم به مزایا و معایب حذف منطقی و روشهایی برای مدیریت آن بپردازیم.
پیشهاد میکنم سری مقالات مفید تحلیل سیستم مدیریت محتوا DTNCMS را حتما مطالعه نمایید.
برای اینکه بخواهیم حذف منطقی را پیاده سازی نماییم، نیاز داریم به هر رکورد، فیلدی اضافه شود تا از طریق آن مشخص نماییم که آیا رکورد حذف شده است یا خیر. برای این منظور باید فیلدی از نوع boolean به تمام کلاسها (جداول) اضافه شود. میتوانیم این فیلد را به صورت زیر تعریف کنیم:
public bool IsDeleted { get; set; }
public interface ISoftDelete { bool IsDeleted { get; set; } }
public class Post :ISoftDelete { }
بعد از افزودن فیلد فوق، نیاز داریم تا در تمام کوئریها شرطی را اضافه نماییم تا فقط رکوردهایی را از دیتابیس واکشی کند که حذف نشدهاند. یعنی فیلد فوق برابر False باشد. در ادامه روشهایی برای این هدف بیان خواهند شد.
روش هایی برای فیلتر رکوردهای حذف شده
1- افزودن فیلتر زیر در تمامی کوئریها:
where (IsDeleted=false && ...)
2- نوشتن یک متد الحاقی:
برای جلوگیری از تکرار شرط فوق میتوان یک متد الحاقی را به صورت زیر پیاده سازی نمود و در تمامی شرطها، آن را فراخوانی کرد:
public static class EntityFrameworkExtentions { public static ObservableCollection<TEntity> Alive<TEntity>(this DbSet<TEntity> set) where TEntity : class, ISoftDelete { var data = set.Where(e => e.IsDeleted == false); return new ObservableCollection<TEntity>(data); } }
3-استفاده از کتابخانه EntityFramework.DynamicFilte
ابتدا اقدام به نصب بسته آن نمایید:
Install-Package EntityFramework.DynamicFilters
modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);
مشکلاتی پیرامون حذف منطقی
- کلاس User و Post را در نظر بگیرد که یک User چندین Post دارد. حال اگر حذف، فیزیکی باشد و کاربر اقدام به حذف User مورد نظر کند، با خطای زیر مواجه میشود:
The DELETE statement conflicted with the REFERENCE constraint ....
1- در سرویسهای مربوط به کلاسهایی که از ISoftDelet ارث بری کردهاند، متدی تحت عنوان CanDelete، به صورت زیر تعریف شود:
public bool CanDelete(user model) { return !model.posts.Any() && ! model.news.Any(); }
2- برای جلوگیری از تکرار قطعه کد فوق، میتوان از روش زیر استفاده کرد:
- یک Attribute سفارشی را به صورت زیر تعریف نمایید:
[AttributeUsage(AttributeTargets.Property)] public class MustBeEmptyToDeleteAttribute : Attribute { }
public class User { public int Id { get; set; } public bool IsDeleted { get; set; } [MustBeEmptyToDelete] public virtual ICollection<Post> Posts { get; set; } [MustBeEmptyToDelete] public virtual ICollection<File> Files { get; set; } // etc... }
public static class EntityExtensions { public static bool CanDelete(this object entity) { return entity.GetType().GetProperties() .Where(x => x.IsDefined(typeof(MustBeEmptyToDeleteAttribute))) .Select(x => x.GetValue(entity)) .OfType<IEnumerable<object>>() .All(x => !x.Any()); }
همانطور که بیان شد، در حذف منطقی فقط رکورد مورد نظر به روز رسانی میشود. برای این منظور میتوان دو متد را همانند زیر در نظر گرفت و هر کدام که مورد نیاز بود، فراخوانی شود:
public void MarkAsSoftDeleted<TEntity>(TEntity entity) where TEntity : ISoftDelete { Entry(entity).State = EntityState.Modified; // set IsDelete=true // here you can set other logs like who deleted ,when ,... } public void MarkAsDeleted<TEntity>(TEntity entity) where TEntity : class { Entry(entity).State = EntityState.Deleted; }
پیشنهادها
- اگر از حذف منطقی استفاده میکنید، امکانی را در سیستم قرار دهید تا در صورت تمایل رکوردهای حذف منطقی را بتوان حذف کرد (تهیه backup و حذف)، حذف منطقی در دراز مدت حجم دیتابیس را بالا میبرد.
- تا حد امکان به کاربران استفاده کننده، وجود امکان حذف منطقی را اطلاع ندهید. اطلاع از این امر شاید باعث عدم دقت افراد استفاده کننده شود.
روش تعیین props پیشفرض توسط TypeScript
اگر بخواهیم توسط روشهای خود React، مقادیر پیشفرض props را تعیین کنیم، میتوان از defaultProps به صورت زیر با تعریف یک شیء جاوا اسکریپتی از پیش مقدار دهی شده، استفاده کرد:
Head.defaultProps = { title: "Hello", isActive: true };
type Props = { title: string; isActive: boolean; }; export const Head = ({ title = "Hello", isActive = true }: Props) => {
در این حالت انتظار داریم که در حین استفاده و تعریف المان کامپوننت Head، اگر برای مثال ویژگی isActive را ذکر نکردیم، کامپایلر TypeScript خطایی را گزارش نکند؛ که اینطور نیست:
هنوز هم در اینجا میتوان خطای عدم تعریف خاصیت isActive را مشاهده کرد. برای رفع این مشکل، به صورت زیر عمل میکنیم:
type Props = { title: string; isActive?: boolean; }; export const Head = ({ title, isActive = true }: Props) => {
اکنون با تعریف isActive?: boolean، دیگر شاهد نمایش خطایی در حین تعریف المان Head، بدون ذکر خاصیت isActive، نخواهیم بود.
تعریف انواع و اقسام نوعهای props
تا اینجا نوعهای سادهای مانند string و boolean و همچنین نحوهی تعریف اجباری و اختیاری آنها را بررسی کردیم. در ادامه یک نمونهی کاملتر را مشاهده میکنید:
type User = { name: string; }; type Props = { title: string; isActive?: boolean; count: number; options: string[]; status: "loading" | "loaded"; thing: {}; thing2: { name: string; }; user: User; func: () => void; };
- سپس نحوهی تعریف آرایهای از رشتهها را مشاهده میکنید.
- یا میتوان مقدار یک خاصیت را تنها به مقادیری خاص محدود کرد؛ مانند خاصیت status در اینجا و اگر در حین مقدار دهی این خاصیت، از مقدار دیگری استفاده شود، تایپاسکریپت، خطایی را صادر میکند.
- در ادامه سه روش تعریف اشیاء تو در تو را مشاهده میکنید؛ خاصیت thing از نوع یک شیء خالی تعریف شدهاست (بجای آن میتوان از نوع object هم استفاده کرد). خاصیت thing2 از نوع یک شیء که دارای خاصیت رشتهای name است، تعریف شده و یا بهتر است این نوع تعاریف را به یک type مستقل دیگر مانند User منتقل کرد و سپس از آن جهت تعیین نوع خاصیتی مانند user استفاده نمود.
- در اینجا حتی میتوان یک خاصیت را که از نوع یک تابع است، تعریف کرد. در این تعریف، void نوع خروجی آن است.
روش تعریف props تابعی در TypeScript
برای بررسی روش تعریف نوع توابع ارسالی از طریق props، ابتدا کامپوننت جدید src\components\Button.tsx را ایجاد میکنیم. سپس آنرا به صورت زیر تکمیل خواهیم کرد:
import React from "react"; type Props = { // onClick(): string; method returns string // onClick(): void method returns nothing; // onClick(text: string): void; method with params // onClick: () => void; function returns nothing onClick: (text: string) => void; // function with params }; export const Button = ({ onClick }: Props) => { return <button onClick={() => onClick("hi")}>Click Me</button>; };
با توجه به تعریف { onClick }، در همان لحظه، خطای any بودن نوع آن از طرف TypeScript گزارش داده میشود. بنابراین نوع جدید Props را ایجاد کرده و برای onClick، نوع متناسبی را تعریف میکنیم. در اینجا 4 روش مختلف تعریف نوع function را در TypeScript مشاهده میکنید؛ دو حالت آن با ذکر پرانتزها و درج امضای متد انجام شده و دو حالت دیگر به کمک arrow functions پیاده سازی شدهاند.
برای نمونه آخرین حالت تعریف شده از روش arrow functions استفاده میکند که متداولترین روش تعریف نوع توابع است (چون عنوان میکند که نوع onClick، یک تابع است و آنرا شبیه به یک متد معمولی نمایش نمیدهد) که در آن در ابتدا امضای پارامترهای این تابع مشخص شدهاند و در ادامه پس از <=، نوع خروجی این تابع تعریف شدهاست که void میباشد (این تابع چیزی را بر نمیگرداند).
در آخر، تعریف المان آنرا به صورت زیر به فایل src\App.tsx اضافه میکنیم که onClick آن یک مقدار را دریافت کرده و سپس آنرا در کنسول نمایش میدهد.
البته خروجی از نوع void، در اینجا بسیار معمول است؛ چون هدف از این نوع توابع بیشتر ارسال مقادیری به کامپوننت در برگیرندهی آنها است (مانند value در اینجا) و اگر برای مثال خروجی رشتهای را داشته باشند، باید در حین درج و تعریف المان آنها، برای نمونه یک "return "value1 را هم در انتهای کار قرار داد که عملا استفادهای ندارد و بیمعنا است:
import { Button } from "./components/Button"; import { Head } from "./components/Head"; // ... function App() { return ( <div className="App"> <Head title="Hello" /> <Button onClick={(value) => { console.log(value); }} /> // ...
مقایسه Angular vs. React vs. Vue
If you love TypeScript: Angular or React
If you love object-orientated-programming (OOP): Angular
If you need guidance, structure and a helping hand: Angular
If you like flexibility: React
If you love big ecosystems: React
If you like choosing among dozens of packages: React
If you love JS & the “everything-is-Javascript-approach”: React
If you like really clean code: Vue
If you want the easiest learning curve: Vue
If you want the most lightweight framework: Vue
If you want separation of concerns in one file: Vue
If you are working alone or have a small team: Vue or React
If your app tends to get really large: Angular or React
If you want to build an app with react-native: React
If you want to have a lot of developers in the pool: Angular or React
If you work with designers and need clean HTML files: Angular or Vue
If you like Vue but are afraid of the limited ecosystem: React
If you can’t decide, first learn React, then Vue, then Angular
- Performance : کارآیی - که در ارتباط با مدت زمان پاسخ گویی و قابل استفاده شدن برنامه میباشد. (مقایسه به درصد - هرچه بیشتر عملکرد بهتر)
- Size : اندازه - که نشان دهنده حجم نهایی فایلهای تولید شده ( Css-Html-JavaScript ) فریم ورک است. این مقایسه اندازه فریم ورک و تمام وابستگیهای آن است که به bundle نهایی برنامه اضافه شده است (هر چه اندازه فایل کمتر باشد بهتر است چراکه توسط کاربر نهایی زودتر دانلود میشود).
- Lines of Code : تعداد خطوط کد - نشان دهنده این است که یک نویسنده بر اساس این فریم ورکها چند سطر کد را باید برای تهیهی یک برنامهی جدید بنویسد.
npx degit sveltejs/template my-svelte-project cd my-svelte-project npm install npm run dev
ابزار Memory Optimization Advisor
Memory Optimization Advisor یک Wizard مانند است که از آن برای گرفتن مشاوره در مورد تبدیل جداول موجود مبتنی بر دیسک سخت، به نمونههای بهینه سازی شده برای حافظه میتوان استفاده کرد. کار آن بررسی ساختار جداولی است که قصد مهاجرت آنها را دارید. برای مثال همانطور که پیشتر نیز عنوان شد، جداول بهینه سازی شده برای حافظه محدودیتهایی دارند؛ مثلا نباید کلید خارجی داشته باشند. این Wizard یک چنین مواردی را آنالیز کرده و گزارشی را ارائه میدهد. پس از اینکه مراحل آنرا به پایان رساندید و مشکلاتی را که گزارش میدهد، برطرف نمودید، کد تبدیل جدول را نیز به صورت خودکار تولید میکند.
برای دسترسی به آن، فقط کافی است بر روی نام جدول خود کلیک راست کرده و گزینهی memory optimization advisor را انتخاب کنید.
در دو قسمت اول این Wizard، کار بررسی ساختار جدول در حال مهاجرت صورت میگیرد. اگر نوع دادهای در آن پشتیبانی نشود یا قیود ویژهای در آن تعریف شده باشند، گزارشی را جهت رفع، دریافت خواهید کرد. پس از رفع آن، به صفحهی گزینههای مهاجرت میرسیم:
همانطور که ملاحظه میکنید، گروه فایل ایجاد شده در قسمت قبل، به صورت خودکار انتخاب شدهاست.
در ادامه میتوان نام دیگری را برای جدول مبتنی بر دیسک وارد کرد. در اینجا به صورت خودکار کلمهی old به آخر نام جدول اضافه شدهاست. در حین تولید جدول جدید بهینه سازی شدهی بر اساس ساختار جدول فعلی، این جدول قدیمی به صورت خودکار تغییر نام خواهد یافت و کلیه اطلاعات آن حفظ میشود.
همچنین تخمینی را نیز از مقدار حافظهی مورد نیاز برای نگهداری این جدول جدید درون حافظهای نیز ارائه میدهد. در این مثال چون رکوردی در جدول انتخابی وجود نداشتهاست، تخمین آن صفر است. عدد ارائه شده توسط آن بسیار مهم است و باید به همین میزان برای سیستم خود حافظه تهیه نمائید و یا از حافظهی موجود استفاده کنید.
در پایین صفحه میتوان انتخاب کرد که آیا دادههای جدول فعلی، به جدول درون حافظهای انتقال یابند یا خیر. به علاوه نوع ماندگاری اطلاعات آن نیز قابل تنظیم است. اگر گزینهی آخر را انتخاب کنید به معنای حالت SCHEMA_ONLY است. حالت پیش فرض آن SCHEMA_AND_DATA میباشد که در قسمتهای قبل بیشتر در مورد آن بحث شد.
در دو صفحهی بعد، کار انتخاب hash index و range index انجام میشود:
در اینجا hash index بر روی فیلد ID تولید شدهاست، به همراه تعیین bucket count آن و در صفحهی بعدی range index بر روی فیلد تاریخ تعریف گردیدهاست:
در آخر میتوان با کلیک بر روی دکمهی Script، صرفا دستورات T-SQL تغییر ساختار جدول را دریافت کرد و یا با کلیک بر روی دکمهی migrate به صورت خودکار کلیه موارد تنظیم شده را اجرا نمود.
خلاصهی این مراحل که توسط دکمهی Script آن تولید میشود، به صورت زیر است:
USE [testdb2] GO EXEC dbo.sp_rename @objname = N'[dbo].[tblNormal]', @newname = N'tblNormal_old', @objtype = N'OBJECT' GO USE [testdb2] GO SET ANSI_NULLS ON GO CREATE TABLE [dbo].[tblNormal] ( [CustomerID] [int] NOT NULL, [Name] [nvarchar](250) COLLATE Persian_100_CI_AI NOT NULL, [CustomerSince] [datetime] NOT NULL, INDEX [ICustomerSince] NONCLUSTERED ( [CustomerSince] ASC ), CONSTRAINT [tblNormal_primaryKey] PRIMARY KEY NONCLUSTERED HASH ( [CustomerID] )WITH ( BUCKET_COUNT = 131072) )WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_AND_DATA ) GO INSERT INTO [testdb2].[dbo].[tblNormal] ([CustomerID], [Name], [CustomerSince]) SELECT [CustomerID], [Name], [CustomerSince] FROM [testdb2].[dbo].[tblNormal_old] GO
علاوه بر memory optimization advisor مخصوص جداول، ابزار دیگری نیز به نام Native compilation advisor برای آنالیز رویههای ذخیره شده تهیه شدهاست:
آیا سیستم فعلی ما واقعا نیازی به ارتقاء به جداول درون حافظهای دارد؟
تا اینجا در مورد نحوهی ایجاد جداول درون حافظهای و یا نحوهی تبدیل جداول موجود را به ساختار جدید بررسی کردیم. ولی آیا واقعا یک چنین تغییراتی برای ما سودمند هستند؟ برای پاسخ دادن به این سؤال ابزاری به نام AMR به management studio 2014 اضافه شدهاست (Analyze, Migrate, Report). کار آن تحت نظر قرار دادن جداول و رویههای ذخیره شدهی بانک اطلاعاتی است و سپس بر اساس بار سیستم، تعداد درخواستهای همزمان و میزان استفاده از جداول و تراکنشهای مرتبط با آنها، گزارشی را ارائه میدهد. بر این اساس بهتر میتوان تصمیم گرفت که کدام جداول بهتر است به جداول درون حافظهای تبدیل شوند.
برای تنظیم آن باید مراحل ذیل طی شوند:
در Management Studio، به برگهی Object Explorer آن مراجعه کنید. سپس پوشهی Management آنرا یافته و بر روی گزینهی Data Collection کلیک راست نمائید:
در اینجا گزینهی Configure Management Data Warehouse را انتخاب نمائید. در صفحهی باز شده، ابتدا بانک اطلاعاتی مدنظر را انتخاب نمائید. همچنین بهتر است بر روی دکمهی new کلیک کرده و یک بانک اطلاعاتی جدید را برای آن ایجاد نمائید، تا دچار تداخل اطلاعاتی و ساختاری نگردد:
در ادامه نام کاربری را که قرار است کار مدیریت ثبت و جمع آوری اطلاعات را انجام دهد، به همراه نقشهای آن انتخاب نمائید:
و در آخر در صفحهی بعدی بر روی دکمهی Finish کلیک کنید.
پس از ایجاد و انتخاب بانک اطلاعاتی Management Data Warehouse، نوبت به تنظیم گزینههای جمع آوری اطلاعات است:
در اینجا ابتدا سرور جاری را انتخاب کنید. پس از آن به صورت خودکار در لیست بانکهای اطلاعاتی قابل انتخاب، تنها همان بانک اطلاعاتی جدیدی را که برای مرحلهی قبل ایجاد کردیم، میتوان مشاهده کرد.
در صفحهی بعد، گزینهی «Transaction Performance Collection Sets» را انتخاب نمائید که دقیقا گزینهی مدنظر ما جهت یافتن آماری از وضعیت تراکنشهای سیستم است.
در ادامه بر روی گزینههای next و finish کلیک کنید تا کار تنظیمات به پایان برسد.
اکنون اگر به لیست وظایف تعریف شده در SQL Server agent مراجعه کنید، میتوانید، وظایف مرتبط با جمع آوری دادهها را نیز مشاهده نمائید:
وظایف Stored Procedure Usage Analysis هر نیم ساعت یکبار و وظایف Table Usage Analysis هر 15 دقیقه یکبار اجرا میشوند. البته امکان اجرای دستی این وظایف نیز مانند سایر وظایف SQL Server وجود دارند.
همچنین در پوشهی management، گزینهی Data collection نیز دو زیر شاخه اضافه شدهاند که نمایانگر آنالیز میزان مصرف جداول و رویههای ذخیره شده میباشند:
پس از این کارها باید مدتی صبر کنید (مثلا یک ساعت) تا سیستم به صورت معمول کارهای متداول خودش را انجام دهد. پس از آن میتوان به گزارشات AMR مراجعه کرد.
برای اینکار بر روی بانک اطلاعاتی Management Data Warehouse که در ابتدای عملیات ایجاد شد، کلیک راست نمائید و سپس مراحل ذیل را طی کنید:
Reports > Management Data Warehouse > Transaction Performance Analysis Overview
در گزارش ایجاد شده، ذیل گزینهی usage analysis لینکهایی وجود دارند که با مراجعه به آنها، چارتهایی از میزان مصرف بانکهای اطلاعاتی مختلف سیستم ارائه میشود. اگر پیام No data available را مشاهده کردید، یعنی هنوز باید مقداری صبر کنید تا کار جمع آوری اطلاعات به پایان برسد.
در این چارتها بانکهای اطلاعاتی که در سمت راست، بالای تصویر قرار میگیرند، انتخاب مناسبی برای تبدیل به بانکهای اطلاعاتی درون حافظهای هستند. محور افقی آن از چپ به راست بیانگر میزان کاهش سختی انتقال یک جدول به جدول درون حافظهای است (با درنظر گرفتن تمام مسایلی که باید تغییر کنند یا نوعهای دادهای که باید اصلاح شوند) و محور عمودی آن نمایانگر میزان بالا رفتن پاسخ دهی سیستم در جهت انجام کار بیشتر است.
هر زمان هم که کار تصمیمگیری شما به پایان رسید، میتوانید بر روی گزینهی Data collection کلیک راست کرده و آنرا غیرفعال نمائید.
برای مطالعه بیشتر
SQL Server 2014 Field Benchmarking In-Memory OLTP and Buffer Pool Extension Features
New AMR Tool: Simplifying the Migration to In-Memory OLTP
A Tour of the Hekaton AMR Tool
SQL Server 2014 Memory Optimization Advisor
Getting started with the AMR tool for migration to SQL Server In-memory OLTP Tables
How to Use Microsoft's AMR Tool
SQL Server 2014's Analysis, Migrate, and Report Tool
Blazer یک فریمورک جدید تحت وب هست که این امکان را به برنامه نویسان دات نت میدهد تا از طریق Open Web Standards بتوانند کدهای خود را در مرورگر اجرا و تجربه جدیدی از ساخت برنامههای تک صفحهای را داشته باشند. در این نوشتار قصد داریم ساختار و نحوه کارکرد این فناوری را بررسی نماییم. قبل از هر چیزی به دوران قبل از ایجاد Web Assembly برمیگردیم :
همانطور که در شکل زیر میبینید، زمانی تنها جاوااسکریپت فرمانروای یک مرورگر محسوب میشد. در این حالت کدهای جاوااسکریپت به هر شکلی که نوشته شده باشند در اختیار parser قرار میگیرند و یک درخت از کدهای نوشته شده ایجاد شده و از طریق یک کامپایلر، کدها به سطح پایینتری مشابه بایت کدها تبدیل میگردند و سپس از طریق یک مفسر دسترسی به بخشهای مختلف api یک مرورگر در اختیار این کدها قرار میگیرند تا کار مورد نظر انجام شود.
در تصویر بعدی Web Assembly به بخش مفسر تزریق میشود و از طریق آن زبانهای مختلف باید بر اساس Web Standard، به کدهای سطحهای پایینتری کامپایل شوند. در اینجا این نکته مدنظر باشد که کدهایی که به سطح پایینتری کامپایل میشوند، تنها در داخل مرورگر شناخته شده میباشند و در خارج از دنیای وب قابل استفاده نیستند و نمیتوانند در سطح سیستم عامل قابل اجرا باشند. به همین جهت به شکل یک sandbox مورد استفاده قرار میگیرند و از این لحاظ، مشکلات امنیتی را در خارج از مرورگر ایجاد نمیکنند.
در شکل سوم Blazor که ترکیبی از
نام Browser + Razor
میباشد اضافه میشود. Blazor در
اینجا وظیفه دارد محتوای فایل دریافتی را که شامل کدهای HTML و CSS و
جاوااسکریپت است، به کدهای قابل فهمی برای مرورگر تبدیل کند. سپس mono وارد کار میشود. همانطور که میدانید mono جهت پشتیبانی از اجرای چندسکویی پروژههای دات نت اضافه شده که در اینجا هم همان وظیفه را منتها برای مرورگرهای مختلف، دارد. بدین جهت مونوی کامپایل شده بر روی Web Assembly قرار میگیرد تا کدهای دریافتی را تفسیر نماید. Blazor در اینجا dllهای لازم را در mono بارگذاری میکند و سپس mono کدها را برای Web Assembly تفسیر میکند.