نظرات مطالب
پرسش و پاسخ‌های متداول ایجاد یک وبلاگ بلاگری
با سلام وخسته نباشید
دو تا سوال داشتم
سوال اول : توی قالب های جدید بلاگر (قالب هایی با فرمت جدید) ، وقتی روی
Edit HTML
می رم و متن داخل اون رو توی برنامه ی
Microsoft FrontPage
کپی می کنم تا بتونم ویرایشش کنم ، چیزی نشون نمیده و قالب رو به من نشون نمیده . مثلا می خوام واسه ی بک گراند قالبم از عکس استفاده کنم ولی محتویات قالب نشون داده نمیشه . مشکل از کجاست و چطوری باید حلش کنم تا بتونم یه قالب جدید با بک گراند مخصوص درست کنم؟
سوال دوم : توی قالب های کلاسیک بلاگر ، برای تبدیل تاریخ میلادی به شمسی از روشی که توی پست
https://www.dntips.ir/2008/12/blogger.html
توضیح داد هبودین استفاده کردم . ولی دو تا مشکل وجود داره :
اولا اینکه فرمت و رنگ تاریخ متن به صورت مشکی و بزرگ در اومد . چطوری میتونم سایز و رنگ تاریخ رو عوض کنم ؟
دوم اینکه تاریخ آرشیو رو چطور می تونم به فارسی تبدیل کنم؟
وبلاگ آزمایشی من تو این ادرسه که با قالب کلاسیک ، تاریخش رو به شمسی تبدیل کردم :
http://arad-diarist2.blogspot.com
ممنون می شم اگه راهنمایی کنید
اشتراک‌ها
نحوه استفاده از Razor در vscode ( پیش نمایش )

1) ابتدا  فایل  vsix مربوط به افزونه سی شارپ را دریافت کنید.

2) اگر نسخه قدیمی  افزونه سی شارپ برروی vscode نصب است آن را حذف کنید.

3) کلید‌های ترکیبی Ctrl + Shift + P را فشار دهید و Install from VSIX را تایپ کنید و سپس آن را انتخاب کنید.

4)مسیر فایل vsix را به vscode بدهید تا آن را نصب کند.

5) سپس بعد راه اندازی مجدد vscode ، یک پوشه ایجاد کرده و یک پروژه جدید بسازید.

6) در اولین مرحله، افزونه ابتدا بسته‌های موردنیاز را دانلود و سپس نصب می‌کند که در این قسمت بسته Razor Language Server جدید است.


حال به سراغ یک فایل razor بروید:


نحوه استفاده از Razor در vscode ( پیش نمایش )
مطالب
نگاهی به هویت سنجی کاربران در ASP.NET MVC 5
در مقاله پیش رو، سعی شده‌است به شکلی تقریبا عملی، کلیاتی در مورد Authentication در MVC5 توضیح داده شود. هدف روشن شدن ابهامات اولیه در هویت سنجی MVC5 و حل شدن مشکلات اولیه برای ایجاد یک پروژه است.
در MVC 4 برای دسترسی به جداول مرتبط با اعتبار سنجی (مثلا لیست کاربران) مجبور به استفاده از متدهای از پیش تعریف شده‌ی رفرنس‌هایی که برای آن نوع اعتبار سنجی وجود داشت، بودیم. راه حلی نیز برای دسترسی بهتر وجود داشت و آن هم ساختن مدل‌های مشابه آن جدول‌ها و اضافه کردن چند خط کد به برنامه بود. با اینکار دسترسی ساده به Roles و Users برای تغییر و اضافه کردن محتوای آنها ممکن می‌شد. در لینک زیر توضیحاتی در مورد روش اینکار وجود دارد.
 در MVC5 داستان کمی فرق کرده است. برای درک موضوع پروژه ای بسازید و حالت پیش فرض آن را تغییر ندهید و آن را اجرا کنید و ثبت نام را انجام دهید، بلافاصله تصویر زیر در دیتابیس نمایان خواهد شد.

دقت کنید بعد از ایجاد پروژه در MVC5 دو پکیج بصورت اتوماتیک از طریق Nuget به پروژه شما اضافه میشود:
 Microsoft.AspNet.Identity.Core
Microsoft.AspNet.Identity.EntityFrameWork
عامل اصلی تغییرات جدید، همین دو پکیج فوق است.
 اولین پکیج شامل اینترفیس‌های IUser و IRole است که شامل فیلدهای مرتبط با این دو می‌باشد. همچنین اینترفیسی به نام IUserStore وجود دارد که چندین متد داشته و وظیفه اصلی هر نوع اضافه و حذف کردن یا تغییر در کاربران، بر دوش آن است.
 دومین پکیج هم وظیفه پیاده سازی آن‌چیزی را دارد که در پکیج اول معرفی شده است. کلاس‌های موجود در این پکیج ابزارهایی برای ارتباط EntityFramework با دیتابیس هستند.
اما از مقدمات فوق که بگذریم برای درک بهتر رفتار با دیتابیس یک مثال را پیاده سازی خواهیم کرد.

 فرض کنید میخواهیم چنین ارتباطی را بین سه جدول در دیتابیس برقرار کنیم، فقط به منظور یادآوری، توجه کنید که جدول ASPNetUsers جدولی است که به شکل اتوماتیک پیش از این تولید شد و ما قرار است به کمک یک جدول واسط (AuthorProduct) آن را به جدول Product مرتبط سازیم تا مشخص شود هر کتاب (به عنوان محصول) به کدام کاربر (به عنوان نویسنده) مرتبط است.
 بعد از اینکه مدل‌های مربوط به برنامه خود را ساختیم، اولا نیاز به ساخت کلاس کانتکست نداریم چون خود MVC5 کلاس کانتکست را دارد؛ ثانیا نیاز به ایجاد مدل برای جداول اعتبارسنجی نیست، چون کلاسی برای فیلدهای اضافی ما که علاقمندیم به جدول Users اضافه شود، از پیش تعیین گردیده است.

دو کلاسی که با فلش علامت گذاری شده اند، تنها فایل‌های موجود در پوشه مدل، بعد از ایجاد یک پروژه هستند. فایل IdentityModel را به عنوان فایل کانتکست خواهیم شناخت (چون یکی از کلاسهایش Context است). همانطور که پیش از این گفتیم با وجود این فایل نیازی به ایجاد یک کلاس مشتق شده از DbContext نیست. همانطور که در کد زیر میبینید این فایل دارای دو کلاس است:
namespace MyShop.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }
      
}
کلاس اول همان کلاسی است که اگر به آن پراپرتی اضافه کنیم، بطور اتوماتیک آن پراپرتی به جدول ASPNetUsers در دیتابیس اضافه می‌شود و دیگر نگران فیلدهای نداشته‌ی جدول کاربران ASP.NET نخواهیم بود. مثلا در کد زیر چند عنوان به این جدول اضافه کرده ایم.
namespace MyShop.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        [Display(Name = "نام انگلیسی")]
        public string EnglishName { get; set; }

        [Display(Name = "نام سیستمی")]
        public string NameInSystem { get; set; }

        [Display(Name = "نام فارسی")]
        public string PersianName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "آدرس ایمیل")]
        public string Email { get; set; }
     }
}
کلاس دوم نیز محل معرفی مدلها به منظور ایجاد در دیتابیس است. به ازای هر مدل یک جدول در دیتابیس خواهیم داشت. مثلا در شکل فوق سه پراپرتی به جدول کاربران اضافه میشود. دقت داشته باشید با اینکه هیچ مدلی برای جدول کاربران نساخته ایم اما کلاس ApplicatioUsers کلاسی است که به ما امکان دسترسی به مقادیر این جدول را می‌دهد(دسترسی به معنای اضافه و حذف وتغییر مقادیر این جدول است) (در MVC4 به کمک کلاس membership کارهای مشابهی انجام میدادیم)
 در ساختن مدل هایمان نیز اگر نیاز به ارتباط با جدول کاربران باشد، از همین کلاس فوق استفاده میکنیم. کلاس واسط(مدل واسط) بین AspNetUsers و Product در کد زیر زیر نشان داده شده است :
namespace MyShop.Models
{
    public class AuthorProduct
    {
        [Key]
        public int AuthorProductId { get; set; }
       /* public int UserId { get; set; }*/

        [Display(Name = "User")]
        public string ApplicationUserId { get; set; }

        public int ProductID { get; set; }

        public virtual Product Product { get; set; }
    
        public virtual ApplicationUser ApplicationUser { get; set; }
    }
}
همانطور که مشاهده میکنید، به راحتی ارتباط را برقرار کردیم و برای برقراری این ارتباط از کلاس ApplicationUser استفاده کردیم. پراپرتی ApplicationUserId نیز فیلد ارتباطی ما با جدول کاربران است. جدول product هم نکته خاصی ندارد و به شکل زیر مدل خواهد شد.
namespace MyShop.Models
{
    [DisplayName("محصول")]
    [DisplayPluralName("محصولات")]
    public class Product
    {
        [Key]
        public int ProductID { get; set; }

        [Display(Name = "گروه محصول")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public int ProductGroupID { get; set; }

        [Display(Name = "مدت زمان")]
        public string Duration { get; set; }

   
        [Display(Name = "نام تهیه کننده")]
        public string Producer { get; set; }

        [Display(Name = "عنوان محصول")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public string ProductTitle { get; set; }

        [StringLength(200)]
        [Display(Name = "کلید واژه")]
        public string MetaKeyword { get; set; }

        [StringLength(200)]
        [Display(Name = "توضیح")]
        public string MetaDescription { get; set; }

        [Display(Name = "شرح محصول")]
        [UIHint("RichText")]
        [AllowHtml]
        public string ProductDescription { get; set; }

        [Display(Name = "قیمت محصول")]
        [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:#,0 ریال}")]
        [UIHint("Integer")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public int ProductPrice { get; set; }
        [Display(Name = "تاریخ ثبت محصول")]

        public DateTime? RegisterDate { get; set; }

    }
}
به این ترتیب هم ارتباطات را برقرار کرده‌ایم و هم از ساختن یک UserProfile اضافی خلاص شدیم.
برای پر کردن مقادیر اولیه نیز به راحتی از seed موجود در Configuration.cs مربوط به migration استفاده میکنیم. نمونه‌ی اینکار در کد زیر موجود است:
protected override void Seed(MyShop.Models.ApplicationDbContext context)
        {
            context.Users.AddOrUpdate(u => u.Id,
                      new ApplicationUser() {  Id = "1",EnglishName = "MortezaDalil", PersianName = "مرتضی دلیل", UserDescription = "توضیح در مورد مرتضی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "2", EnglishName = "MarhamatZeinali", PersianName = "محسن احمدی", UserDescription = "توضیح در مورد محسن", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "3", EnglishName = "MahdiMilani", PersianName = "مهدی محمدی", UserDescription = "توضیح در مورد مهدی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "4", EnglishName = "Babak", PersianName = "بابک", UserDescription = "کاربر معمولی بدون توضیح", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" }
                     
                        );


            context.AuthorProducts.AddOrUpdate(u => u.AuthorProductId,
              new AuthorProduct() { AuthorProductId = 1, ProductID = 1, ApplicationUserId = "2" },
              new AuthorProduct() { AuthorProductId = 2, ProductID = 2, ApplicationUserId = "1" },
              new AuthorProduct() { AuthorProductId = 3, ProductID = 3, ApplicationUserId = "3" }

          );
 می‌توانیم از کلاس‌های خود Identity برای انجام روش فوق استفاده کنیم؛ فرض کنید بخواهیم یک کاربر به نام admin و با نقش admin به سیستم اضافه کنیم.
            if (!context.Users.Where(u => u.UserName == "Admin").Any())
            {
                var roleStore = new RoleStore<IdentityRole>(context);
                var rolemanager = new RoleManager<IdentityRole>(roleStore);

                var userstore = new UserStore<ApplicationUser>(context);
                var usermanager = new UserManager<ApplicationUser>(userstore);
                
                var user = new ApplicationUser {UserName = "Admin"};
                
                usermanager.Create(user, "121212");
                rolemanager.Create(new IdentityRole {Name = "admin"});
                
                usermanager.AddToRole(user.Id, "admin");
            }
   در عبارت شرطی موجود کد فوق، ابتدا چک کردیم که چنین یوزری در دیتابیس نباشد، سپس از کلاس RoleStore که پیاده سازی شده‌ی اینترفیس IRoleStore است استفاده کردیم. سازنده این کلاس به کانتکست نیاز دارد؛ پس به آن context را به عنوان ورودی می‌دهیم. در خط بعد، کلاس rolemanager را داریم که بخشی از پکیج Core است و پیش از این درباره اش توضیح دادیم ( یکی از دو رفرنسی که خوبخود به پروژه اضافه میشوند) و از ویژگی‌های Identity است. به آن آبجکتی که از RoleStore ساختیم را پاس میدهیم و خود کلاس میداند چه چیز را کجا ذخیره کند.
برای ایجاد کاربر نیز همین روند را انجام می‌دهیم. سپس یک آبجکت به نام user را از روی کلاس ApplicationUser میسازیم. برای آن پسورد 121212 سِت میکنیم و نقش ادمین را به آن نسبت میدهیم. این روش قابل تسری به تمامی بخش‌های برنامه شماست. میتوانید عملیات کنترل و مدیریت اکانت را نیز به همین شکل انجام دهید. ساخت کاربر و لاگین کردن یا مدیریت پسورد نیز به همین شکل قابل انجام است.
 بعد از آپدیت دیتابیس تغییرات را مشاهده خواهیم کرد. 
مطالب
ارتقاء به Angular 6: بررسی تغییرات RxJS
پس از ارتقاء Angular CLI و ساختار پروژه‌ی قبلی خود به نگارش 6، اولین موردی را که مشاهده خواهید کرد، این است: برنامه دیگر کامپایل نمی‌شود! اولین دلیل آن عدم استفاده‌ی از HttpClient معرفی شده‌ی در نگارش 4.3 است و دومین دلیل مهم آن، تغییرات بنیادین RxJS است که خلاصه‌ی کاربردی آن‌را در این مطلب بررسی خواهیم کرد.


RxJS اکنون جزئی از پروژه‌های گوگل است

توسعه دهنده‌ی اصلی RxJS یا همان Ben Lesh اکنون به گوگل پیوسته‌است و جزو تیم Angular است. بنابراین در آینده شاهد یکپارچگی بهتر این دو با هم خواهیم بود. البته RxJS هنوز هم به عنوان یک پروژه‌ی مستقل از Angular مدیریت خواهد شد.


آشنایی با تغییرات RxJS 5.5 جهت مهاجرت به RxJS 6.0 ضروری است

در مطلب «کاهش حجم قابل ملاحظه‌ی برنامه‌های Angular با استفاده از RxJS 5.5» با pipe-able operators آشنا شدیم و این موارد پایه‌های مهاجرت به RxJS 6.0 هستند. بنابراین پیش از مطالعه‌ی ادامه‌ی بحث نیاز است این مطلب را به خوبی مطالعه و بررسی کنید.


تغییر رفتار خطاهای مدیریت نشده در RxJS 6.0

تا پیش از RxJS 6.0 اگر خطای مدیریت نشده‌ای رخ می‌داد، این خطا به صورت synchronous به فراخوان صادر می‌شد. این رفتار در نگارش 6 تغییر کرده و صدور آن اینبار asynchronous شده‌است.
برای مثال یک چنین کدی تا پیش از RxJS 6.0 کار می‌کرد:
try {
    source$.subscribe(nextFn, undefined, completeFn);
} catch (err) {
    handleError(err);
}
از این جهت که دومین پارامتر در اینجا، متدی است که کار مدیریت خطا را انجام می‌دهد و چون ذکر نشده‌است، خطای رخ داده‌ی حاصل، به صورت همزمان به فراخوان صادر شده‌است و try/catch نیز در اینجا کار می‌کند. اما این مثال دیگر در نگارش 6 کار نخواهد کرد و صدور خطای مدیریت نشده، دیگر همزمان نیست و قسمت catch این قطعه کد دیگر هیچگاه فراخوانی نمی‌شود. البته این رفتار طبیعی است که می‌بایستی از نگارش‌های پیشین، با Observable هایی که عموما async هستند، وجود می‌داشت و اکنون اصلاح شده‌است.
برای اصلاح این کد در نگارش 6، همان پارامتر دوم متد را مقدار دهی کنید و try/catch را در صورت وجود حذف نمائید.


تغییرات مهم importها در RxJS 6.0

همانطور که در مطلب «کاهش حجم قابل ملاحظه‌ی برنامه‌های Angular با استفاده از RxJS 5.5» نیز بررسی کردیم، تا نگارش 5 این کتابخانه، importها به صورت زیر بودند:
 import 'rxjs/add/operator/map';
و پس از RxJS 5.5 امکان import آن‌ها با روش مخصوص ES 6 میسر شده‌است:
 import { map } from 'rxjs/operators';
هر چند نگارش 5.5 بهبودهای قابل ملاحظه‌ای و مزایایی مانند حذف ساده‌تر کدهای مرده، عدم تعریف عملگرها به صورت استاتیک و همچنین سازگاری بهتر با ابزارهایی مانند TSLint را به همراه دارد، اما باز هم دست آخر به تعداد زیادی import مانند کدهای زیر می‌رسیدیم:
import { timer } from 'rxjs/observable/timer';  
import { of } from 'rxjs/observable/of';
import { from } from 'rxjs/observable/from';
import { range } from 'rxjs/observable/range';
این مشکل در RxJS 6.0 برطرف شده‌است و در ماژول‌های مختلف برنامه حداکثر به دو سطر خلاصه شده‌ی زیر نیاز خواهیم داشت:
import { interval, of } from 'rxjs';  
import { filter, mergeMap, scan } from 'rxjs/operators';
در نگارش 6، تمام «نوع‌ها» مانند Observable و Subject و «متدهای ایجاد» مانند timer و interval از مسیر rxjs دریافت می‌شوند و تمام «عملگرها» مانند map و filter از مسیر rxjs/operators اضافه خواهند شد و ... همین!
البته RxJS 6.0 در کل به همراه 4 گروه کلی importها است که در زیر مشاهده می‌کنید (در اینجا مواردی که کمتر در برنامه‌های Angular به صورت مستقیم استفاده می‌شوند مانند ajax آن و یا webSocket هم قابل مشاهده هستند):
rxjs
rxjs/operators
rxjs/testing
rxjs/webSocket
rxjs/ajax


مواردی که از RxJS 6.0 حذف شده‌اند

برای کاهش حجم کتابخانه‌ی RxJS و همچنین جلوگیری از بکارگیری متدهایی که نمی‌بایستی خارج از کدهای اصلی خود RxJS استفاده شوند، تعداد زیادی از متدهای قدیمی آن و روش‌های کار پیشین با RxJS حذف شده‌اند. برای مثال شما در RxJS 5.5 می‌توانید برای کار با عملگر of، یا آن‌را از مسیر rxjs/add/observable/of دریافت کنید (همان روش وصله کردن تا پیش از RxJS 5.5) و یا آن‌را از مسیر rxjs/observable/of به روش مخصوص ES 6.0 به برنامه اضافه کنید و یا حتی امکان دریافت آن از مسیر rxjs/observable/fromArray نیز میسر است.
در RxJS 6.0 تمام این‌ها حذف شده‌اند و فقط روش زیر باقی مانده‌است:
 import { of } from 'rxjs';
به این ترتیب نه فقط حجم این کتابخانه کاهش یافته‌است، بلکه با کاهش سطح API آن، یادگیری آن نیز ساده‌تر شده‌است.


معرفی بسته‌ی rxjs-compat

در مطلب «ارتقاء به Angular 6: بررسی تغییرات Angular CLI» روش ارتقاء وابستگی‌های پروژه به نگارش 6 را بررسی کردیم. یکی از مراحل آن اجرای دستور زیر بود:
 ng update rxjs
این مورد صرفا وابستگی rxjs ذکر شده‌ی در فایل package.json را به آخرین نگارش آن به روز رسانی و همچنین نصب می‌کند.

پس از آن اگر پروژه را کامپایل کنید، پر خواهد بود از خطاهای rxjs، مانند:
 ERROR in node_modules/ng2-slim-loading-bar/src/slim-loading-bar.service.d.ts(1,10):
error TS2305: Module '"/node_modules/rxjs/Observable"' has no exported member 'Observable'.
این مشکل با بسته‌های ثالثی وجود دارند که هنوز از نگارش‌های قبلی RxJS استفاده می‌کنند و همانطور که عنوان شد، RxJS 6.0 شامل حذفیات و نقل و انتقالات بسیاری است. به همین جهت هیچکدام از کتابخانه‌های ثالث مبتنی بر نگارش‌های پیشین RxJS دیگر کار نکرده و کامپایل نخواهند شد.
برای رفع این مشکل و ارائه‌ی راه‌حلی کوتاه مدت، بسته‌ای به نام rxjs-compat ارائه شده‌است که سبب هدایت تعاریف قدیمی به تعاریف جدید می‌شود و به این ترتیب کدهای کتابخانه‌ی ثالث، بدون مشکل با نگارش 6 نیز قابل استفاده خواهند بود.
برای نصب آن نیاز است دستور زیر را صادر کنید:
 npm i rxjs-compat --save
اکنون اگر برنامه را مجددا کامپایل کنید، تمام خطاهای مرتبط با کتابخانه‌های ثالث مورد استفاده برطرف شده‌اند.

البته دقت داشته باشید از rxjs-compat به عنوان یک راه حل موقت باید استفاده کرد و نیاز است ابتدا کدهای خود را به روش pipe-able operators بازنویسی کنید و مسیرهای importها را اصلاح کنید و در آخر بسته‌های جدید وابستگی‌های ثالث را که از RxJS6 استفاده می‌کنند، نصب نمائید. در نهایت rxjs-compat را حذف کنید.


خودکار سازی اصلاح importها در برنامه‌های پیشین، جهت مهاجرت به RxJS 6.0

با توجه به این تغییرات و حذف و اضافه شدن‌ها در نگارش 6، تقریبا دیگر هیچکدام از importهای قبلی شما کار نمی‌کنند! و اصلاح آن‌ها نیاز به زمان زیادی خواهد داشت. به همین جهت تیم RxJS ابزاری را طراحی کرده‌اند که با اجرای آن بر روی پروژه، به صورت خودکار تمام importهای قبلی را به نگارش جدید تبدیل می‌کند. برای اینکار ابتدا ابزار rxjs-tslint را نصب کنید:
 npm i -g rxjs-tslint
در ادامه فایل tslint.json پروژه خود را گشوده و مداخل زیر را به آن اضافه و ویرایش نمائید:
{
  "rulesDirectory": [
    "node_modules/rxjs-tslint"
  ],
  "rules": {
    "rxjs-collapse-imports": true,
    "rxjs-pipeable-operators-only": true,
    "rxjs-no-static-observable-methods": true,
    "rxjs-proper-imports": true
  }
}
سپس به ریشه‌ی پروژه‌ی خود وارد شده و دستور زیر را اجرا کنید:
 rxjs-5-to-6-migrate -p src/tsconfig.app.json
کیفیت کار این ابزار تا حدی است که تیم‌های داخلی گوگل از آن برای ارتقاء کدهای پیشین استفاده می‌کنند و بر روی پروژه‌های واقعی آزمایش شده‌است.
البته توصیه شده‌است این ابزار را بیش از یکبار نیاز است اجرا کنید.


خلاصه‌ی روش مهاجرت به RxJS 6x

ابتدا آخرین نگارش rxjs را نصب کنید:
 ng update rxjs
سپس rxjs-compat را جهت رفع کمبودهای کتابخانه‌های ثالث مورد استفاده نصب نمائید:
 npm i rxjs-compat --save
پس از آن ابزار rxjs-tslint را نصب و اجرا کنید:
npm i -g rxjs-tslint
rxjs-5-to-6-migrate -p src/tsconfig.app.json
و در آخر ارتقاء به روش pipe-able را باید مدنظر داشته باشید.


یافتن معادل‌های جدید دستورات قدیمی

در حین تبدیل کدهای قدیمی به جدید نیاز خواهید داشت تا معادل‌ها را بیابید. برای این منظور به مستندات رسمی این مهاجرت مراجعه کنید:
https://github.com/ReactiveX/rxjs/blob/master/MIGRATION.md
برای مثال در اینجا مشاهده خواهید کرد که معادل Observable.throw حذف شده، اکنون throwError است و همینطور برای مابقی.


یک مثال واقعی تغییر یافته

مخزن کد تمام مثال‌های سایت جاری که پیشتر منتشر شده‌اند، به نسخه‌ی 6 ارتقاء داده شد. ریز تغییرات RxJS 6.0 آن‌ها را در اینجا می‌توانید مشاهده کنید.
مطالب
امکان تعریف توابع خاص بانک‌های اطلاعاتی در EF Core
یکی از اهداف کار با ORMها، رسیدن به کدی قابل ترجمه و استفاده‌ی توسط تمام بانک‌های اطلاعاتی ممکن است و یکی از الزامات رسیدن به این هدف، صرفنظر کردن از قابلیت‌های بومی بانک‌های اطلاعاتی است که در سایر بانک‌های اطلاعاتی دیگر معادلی ندارند. برای مثال SQL Server به همراه توابع توکاری مانند datediff و datepart برای کار با زمان و تاریخ است؛ اما این توابع را به صورت مستقیم نمی‌توان در ORMها استفاده کرد. چون به محض استفاده‌ی از آن‌ها، کد تهیه شده دیگر قابلیت انتقال به سایر بانک‌های اطلاعاتی را نخواهد داشت. اما ... اگر این هدف را نداشته باشیم، چطور؟ آیا می‌توان یک تابع DateDiff سفارشی را برای EF Core تهیه نمود و از تمام قابلیت‌های بومی آن در کوئری‌های LINQ استفاده کرد؟ بله! یک چنین قابلیتی تحت عنوان DbFunctions در EF Core پشتیبانی می‌شود که روش تهیه‌ی آن‌ها را در این مطلب بررسی خواهیم کرد.


معرفی موجودیت Person

در مثال این مطلب قصد داریم، معادل توابع بومی مخصوص SQL Server را که امکان کار با DateTime را مهیا می‌کنند، در EF Core تعریف کنیم. به همین جهت نیاز به موجودیتی داریم که دارای خاصیتی از این نوع باشد:
using System;

namespace EFCoreDbFunctionsSample.Entities
{
    public class Person
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public DateTime AddDate { get; set; }
    }
}


گزارشگیری بر اساس تعداد روز گذشته‌ی از ثبت نام

اکنون فرض کنید می‌خواهیم گزارشی را از تمام کاربرانی که در طی 10 روز قبل ثبت نام کرده‌اند، تهیه کنیم. اگر کوئری زیر را برای این منظور تهیه کنیم:
var usersInfo = context.People.Where(person => (DateTime.Now - person.AddDate).Days <= 10).ToList();
با استثنای زیر متوقف خواهیم شد:
'The LINQ expression 'DbSet<Person>.Where(p => (DateTime.Now - p.AddDate).Days <= 10)'
could not be translated. Either rewrite the query in a form that can be translated,
or switch to client evaluation explicitly by inserting a call to either
AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'
عنوان می‌کند که یک چنین کوئری LINQ ای قابلیت ترجمه‌ی به SQL را ندارد. اما ... نکته‌ی مهم اینجا است که خود SQL Server یک چنین توانمندی را به صورت توکار دارا است:
SELECT [p].[Id], [p].[AddDate], [p].[Name]
FROM [People] AS [p]
WHERE DATEDIFF(Day, [p].[AddDate], GETDATE()) <= 10
برای انجام کوئری مدنظر فقط کافی است از تابع DATEDIFF توکار آن با پارامتر Day، استفاده کنیم تا لیست تمام کاربران ثبت نام کرده‌ی در طی 10 روز قبل را بازگشت دهد. اکنون سؤال اینجا است که آیا می‌توان چنین تابعی را به EF Core معرفی کرد؟


روش تعریف تابع DATEDIFF سفارشی در EF Core

برای تعریف متد DateDiff مخصوص EF Core، ابتدا باید یک کلاس static را تعریف کرد و سپس تنها امضای این متد را، معادل امضای تابع توکار SQL Server تعریف کرد. این متد نیازی نیست تا پیاده سازی را داشته باشد. به همین جهت بدنه‌ی آن‌را صرفا با یک throw new InvalidOperationException مقدار دهی می‌کنیم. هدف از این متد، استفاده‌ی از آن در LINQ Expressions است و قرار نیست به صورت مستقیمی بکار گرفته شود:
namespace EFCoreDbFunctionsSample.DataLayer
{
    public enum SqlDateDiff
    {
        Year,
        Quarter,
        Month,
        DayOfYear,
        Day,
        Week,
        Hour,
        Minute,
        Second,
        MilliSecond,
        MicroSecond,
        NanoSecond
    }

    public static class SqlDbFunctionsExtensions
    {
        public static int SqlDateDiff(SqlDateDiff interval, DateTime initial, DateTime end)
            => throw new InvalidOperationException($"{nameof(SqlDateDiff)} method cannot be called from the client side.");
        public static readonly MethodInfo SqlDateDiffMethodInfo = typeof(SqlDbFunctionsExtensions)
            .GetRuntimeMethod(
                nameof(SqlDbFunctionsExtensions.SqlDateDiff),
                new[] { typeof(SqlDateDiff), typeof(DateTime), typeof(DateTime) }
            );
    }
}
در اینجا علاوه بر تعریف امضای متد DateDiff که در اینجا SqlDateDiff نام گرفته‌است، فیلد SqlDateDiffMethodInfo را نیز مشاهده می‌کنید. در حین تعریف و معرفی DbFunctions سفارشی به EF Core، متدهایی که اینکار را انجام می‌دهند، پارامترهای ورودی از نوع MethodInfo دارند. به همین جهت یک چنین تعریفی انجام شده‌است.


روش معرفی تابع DATEDIFF سفارشی به EF Core

پس از تعریف امضای متد معادل DateDiff، اکنون نوبت به معرفی آن به EF Core است:
namespace EFCoreDbFunctionsSample.DataLayer
{
    public class ApplicationDbContext : DbContext
    {
        // ...
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.HasDbFunction(SqlDbFunctionsExtensions.SqlDateDiffMethodInfo)
                .HasTranslation(args =>
                {
                    var parameters = args.ToArray();
                    var param0 = ((SqlConstantExpression)parameters[0]).Value.ToString();
                    return SqlFunctionExpression.Create("DATEDIFF",
                        new[]
                        {
                            new SqlFragmentExpression(param0), // It should be written as DateDiff(day, ...) and not DateDiff(N'day', ...) .
                            parameters[1],
                            parameters[2]
                        },
                        SqlDbFunctionsExtensions.SqlDateDiffMethodInfo.ReturnType,
                        typeMapping: null);
                });
        }
    }
}
کار تعریف DbFunctions سفارشی توسط متد HasDbFunction صورت می‌گیرد. پارامتر این متد، همان MethodInfo معادل امضای تابع توکار مدنظر است.
سپس توسط متد HasTranslation، مشخص می‌کنیم که این متد به چه نحوی قرار است به یک عبارت SQL ترجمه شود. پارامتر args ای که در اینجا در اختیار ما قرار می‌گیرد، دقیقا همان پارامترهای متد public static int SqlDateDiff(SqlDateDiff interval, DateTime initial, DateTime end) هستند که در این مثال خاص، شامل سه پارامتر می‌شوند. پارامترهای دوم و سوم آن‌را به همان نحوی که دریافت می‌کنیم، به SqlFunctionExpression.Create ارسال خواهیم کرد. اما پارامتر اول را از نوع enum تعریف کرده‌ایم و همچنین قرار نیست به صورت 'N'day و رشته‌ای به سمت بانک اطلاعاتی ارسال شود، بلکه باید به همان نحو اصلی آن (یعنی day)، در کوئری نهایی درج گردد، به همین جهت ابتدا Value آن‌را استخراج کرده و سپس توسط SqlFragmentExpression عنوان می‌کنیم آن‌را باید به همین نحو درج کرد.
پارامتر اول متد SqlFunctionExpression.Create، باید دقیقا معادل نام متد توکار مدنظر باشد. پارامتر دوم آن، لیست پارامترهای این تابع است. پارامتر سوم آن، نوع خروجی این تابع است که از طریق MethodInfo معادل، قابل استخراج است.


استفاده‌ی از DbFunction سفارشی جدید در برنامه

پس از این تعاریف و معرفی‌ها، اکنون می‌توان متد سفارشی SqlDateDiff تهیه شده را به صورت مستقیمی در کوئری‌های LINQ استفاده کرد تا قابلیت ترجمه‌ی به SQL را پیدا کنند:
var sinceDays = 10;
users = context.People.Where(person =>
      SqlDbFunctionsExtensions.SqlDateDiff(SqlDateDiff.Day, person.AddDate, DateTime.Now) <= sinceDays).ToList();
/*
SELECT [p].[Id], [p].[AddDate], [p].[Name]
FROM [People] AS [p]
WHERE DATEDIFF(Day, [p].[AddDate], GETDATE()) <= @__sinceDays_0
*/


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: EFCoreDbFunctionsSample.zip
این کدها به همراه چند تابع سفارشی دیگر نیز هستند.
مطالب
اجرای SSIS Package از طریق برنامه کاربردی

مقدمه

در اکثر موارد در یک Landscape عملیاتی، چنانچه به تجمیع و انتقال داده‌ها از بانک‌های اطلاعاتی مختلف نیاز باشد، از SSIS Package اختصار (SQL Server Integration Service) استفاده می‌شود و معمولاً با تعریف یک Job در سطح SQL Server به اجرای Package در زمانهای مشخص می‌پردازند. چنانچه در موقعیتی لازم باشد که از طریق برنامه کاربردی توسعه یافته، به اجرای Package مبادرت ورزیده شود و البته نخواهیم Job تعریف شده را از طریق کد برنامه، اجرا کنیم و در واقع این امکان را داشته باشیم که همانند یک رویه ذخیره شده تعریف شده در سطح بانک اطلاعاتی به اجرای عمل فوق بپردازیم، یک راه حل می‌تواند تعریف یک CLR Stored Procedures باشد. در این مقاله به بررسی این موضوع پرداخته می‌شود، در ابتدا لازم است به بیان تئوری موضوع پرداخته شود (قسمت‌های 1 الی 5) در ادامه به ذکر پیاده سازی روش پیشنهادی پرداخته می‌شود.

1- اجرای Integration Service Package 

جهت اجرای یک Package از ابزارهای زیر می‌توان استفاده کرد:
• command-line ابزار خط فرمان dtexec.exe
• ابزار اجرائی پکیج dtexecui.exe
• استفاده از SQL Server Agent job
توجه: همچنین یک Package را در زمان طراحی در  Business Intelligence Development Studio) BIDS)  می‌توان اجرا نمود.

2- استفاده از dtexec جهت اجرای Package

با استفاده از ابزار dtexec می‌توان Package‌های ذخیره شده در فایل سیستم، یک SQL Instance و یا Package‌های ذخیره شده در Integration Service را اجرا نمود.

توجه:
در سیستم عامل‌های 64 بیتی، ابزار dtexec موجود در Integration Service با نسخه 64 بیتی نصب می‌شود. چنانچه بایست Package‌های معینی را در حالت 32 بیتی اجرا کنید، لازم است ابزار dtexec نسخه 32 بیتی نصب شود. ابزار dtexec دستیابی به تمامی ویژگی‌های پیکربندی و اجرای Package از قبیل اتصالات، مشخصات(Properties)، متغیرها، logging و شاخص‌های پردازشی را فراهم می‌کند.
توجه: زمانی که از نسخه‌ی ابزار dtexec که با SQL Server 2008 ارائه شده استفاده می‌کنید برای اجرای یک SSIS Package نسخه 2005، Integration Service به صورت موقت Package را به نسخه 2008 ارتقا می‌دهد، اما نمی‌توان از ابزار dtexec برای ذخیره این تغییرات استفاده کرد.

2-1- ملاحظات نصب dtexec روی سیستم‌های 64 بیتی

به صورت پیش فرض، یک سیستم عامل 64 بیتی که هر دو نسخه 64 بیتی و 32 بیتی ابزار خط فرمان Integration Service را دارد، نسخه 32 بیتی نصب شده را در خط فرمان اجرا خواهد کرد. نسخه 32 بیتی بدین دلیل اجرا می‌شود که در متغیر محیطی (Path (Path environment variable مسیر directory نسخه 32 بیتی قرار گرفته است.به طور معمول:
(<drive>:\Program Files(x86)\Microsoft SQL Server\100\DTS\Binn)
توجه: اگر از SQL Server Agent برای اجرای Package استفاده می‌کنید، SQL Server Agent به طور خودکار از ابزار نسخه 64 بیتی استفاده می‌کند. SQL Server Agent از Registry و نه از متغیر محیطی Path استفاده می‌کند. برای اطمینان از اینکه نسخه 64 بیتی این ابزار را در خط فرمان اجرا می‌کنید، directory را به directory ای تغییر دهید که شامل نسخه 64 بیتی این ابزار است(<drive>:\Program Files\Microsoft SQL Server\100\DTS\Binn) و ابزار را از این مسیر اجرا کنید و یا برای همیشه مسیر قرار گرفته در متغیر محیطی path را با مسیری که نسخه 64 بیتی قرار دارد، جایگزین کنید.

2-2- تفسیر کدهای خروجی

هنگامی که یک Package اجرا می‌شود، dtexec یک کد خروجی (Return Code) بر می‌گرداند:

 مقدار توصیف
 0  Package با موفقیت اجرا شده است.
 1  Package با خطا مواجه شده است.
 3 Package در حال اجرا توسط کاربر لغو شده است.
 4  Package پیدا نشده است.
 5  Package بارگذاری نشده است.
 6  ابزار با یک خطای نحوی یا خطای معنایی در خط فرمان برخورد کرده است.

2-3- قوانین نحوی dtexec

تمامی گزینه‌ها (Options) باید با یک علامت Slash (/)  و یا Minus (-)  شروع شوند.
یک آرگومان باید در یک quotation mark محصور شود چنانچه شامل یک فاصله خالی باشد.
گزینه‌ها و آرگومان‌ها بجز رمزعبور حساس به حروف کوچک و بزرگ نیستند.

2-3-1- Syntax

 dtexec /option [value] [/option [value]]…


2-3-2- Parameters

نکته: در Integration Service، ابزار خط فرمان dtsrun که برایData Transformation Service) DTS)‌های نسخه SQL Server 2000 استفاده می‌شد، با ابزار خط فرمان dtexec جایگزین شده است.
• تعدادی از گزینه‌های خط فرمان dtsrun به طور مستقیم در dtexec معادل دارند برای مثال نام Server و نام Package.
• تعدادی از گزینه‌های dtsrun به طور مستقیم در dtexec معادل ندارند.
• تعدادی گزینه‌های خط فرمان جدید dtsexec وجود دارد که در ویژگی‌های جدید Integration Service پشتیبانی می‌شود.

2-3-3- مثال

1) به منظور اجرای یک SSIS Package که در SQL Server ذخیره شده است، با استفاده از Windows Authentication :
 dtexec /sq <Package Name> /ser <Server Name>

2) به منظور اجرای یک SSIS Package که در پوشه File System در SSIS Package Store ذخیره شده است :
 dtexec /dts “\File System\<Package File Name>”

3) به منظور اجرای یک SSIS Package که در سیستم فایل ذخیره شده است و مشخص کردن گزینه logging:
 dtexec /f “c:\<Package File Name>” /l “DTS.LogProviderTextFile; <Log File Name>”

4) به منظور اجرای یک SSIS Package که در SQL Server ذخیره شده با استفاده از SQL Server Authentication برای نمونه(user:ssis;pwd:ssis@ssis)و رمز (Package(123:
 dtexec  /server “<Server Name>”  /sql “<Package Name>”  / user “ssis” /Password “ssis@ssis” /De “123”


3- تنظیمات سطح حفاظتی یک Package

به منظور حفاظت از داده‌ها در Package‌های Integration Service می‌توانید یک سطح حفاظتی (protection level) را تنظیم کنید که به حفاظت از داده‌های صرفاً حساس یا تمامی داده‌های یک Package کمک نماید. به  علاوه می‌توانید این داده‌ها را با یک Password یا یک User Key رمزگذاری نمائید یا به رمزگذاری داده‌ها در بانک اطلاعاتی اعتماد کنید. همچنین سطح حفاظتی که برای یک Package استفاده می‌کنید، الزاماً ایستا (static) نیست و در طول چرخه حیات یک Package می‌تواند تغییر کند. اغلب سطح حفاظتی در طول توسعه یا به محض (deploy) استقرار Package تنظیم می‌شود.
توجه: علاوه بر سطوح حفاظتی که توصیف شد، Package‌ها در بانک اطلاعاتی msdb ذخیره می‌شوند که همچنین می‌توانند توسط نقش‌های ثابت در سطح بانک اطلاعاتی (fixed database-level roles) حفاظت شوند. Integration Service شامل 3 نقش ثابت بانک اطلاعاتی برای نسبت دادن مجوزها به Package است که عبارتند از db_ssisadmin  ،db_ssisltduser و db_ssisoperator

3-1- درک سطوح حفاظتی

در یک Package اطلاعات زیر به عنوان حساس تعریف می‌شوند:
• بخش password در یک connection string. گرچه، اگر گزینه ای را که همه چیز را رمزگذاری کند، انتخاب کنید تمامی connection string حساس در نظر گرفته می‌شود.
• گره‌های task-generated XML که برچسب (tagged) هایی حساس هستند.
• هر متغییری که به عنوان حساس نشان گذاری شود.

3-1-1- Do not save sensitive

هنگامی که Package ذخیره می‌شود از ذخیره مقادیر ویژگی‌های حساس در Package جلوگیری می‌کند. این سطح حفاظتی رمزگذاری نمی‌کند اما در عوض از ذخیره شدن ویژگی هایی که حساس نشان گذاری شده اند به همراه Package جلوگیری می‌کند.

3-1-2- Encrypt all with password

به منظور رمزگذاری تمامی Package از یک Password استفاده می‌شود. Package توسط Password ای رمزگذاری می‌شود  که کاربر هنگامی که Package را ایجاد یا Export می‌کند، ارائه می‌دهد. به منظور باز کردن Package در SSIS Designer یا اجرای Package توسط ابزار خط فرمان dtexec کاربر بایست رمز Package را ارائه نماید. بدون رمز کاربر قادر به دستیابی و اجرای Package نیست.

3-1-3- Encrypt all with user key

به منظور رمزگذاری تمامی Package از یک کلید که مبتنی بر Profile کاربر جاری می‌باشد، استفاده می‌شود. تنها کاربری که Package را ایجاد یا Export می‌کند، می‌تواند Package را در SSIS Designer باز کند و یا Package را توسط ابزار خط فرمان dtexec اجرا کند.

3-1-4- Encrypt sensitive with password

به منظور رمزگذاری تنها مقادیر ویژگی‌های حساس در Package از یک Password استفاده می‌شود. برای رمزگذاری از DPAPI استفاده می‌شود. داده‌های حساس به عنوان بخشی از Package ذخیره می‌شوند اما آن داده‌ها با استفاده از Password رمزگذاری می‌شوند. به منظور باز نمودن Package در SSIS Designer کاربر باید رمز Package را ارائه دهد. اگر رمز ارائه نشود، Package بدون داده‌های حساس باز می‌شود و کاربر باید مقادیر جدیدی برای داده‌های حساس فراهم کند. اگر کاربر سعی نماید Package را بدون ارائه رمز اجرا کند، اجرای Package با خطا مواجه می‌شود.

3-1-5- Encrypt sensitive with user key

به منظور رمزگذاری تنها مقادیر ویژگی‌های حساس در Package از یک کلید که مبتنی بر Profile کاربر جاری می‌باشد، استفاده می‌شود. تنها کاربری که از همان Profile استفاده می‌کند، Package را می‌تواند بارگذاری (load) کند. اگر کاربر متفاوتی Package را باز نماید، اطلاعات حساس با مقادیر پوچی جایگزین می‌شود و کاربر باید مقادیر جدیدی برای داده‌های حساس فراهم کند. اگر کاربر سعی نماید Package را بدون ارائه رمز اجرا کند، اجرای Package با خطا مواجه می‌شود. برای رمزگذاری از DPAPI استفاده می‌شود.

3-1-6- (Rely on server storage for encryption (ServerStorage

با استفاده از نقش‌های بانک اطلاعاتی، SQL Server تمامی Package را حفاظت می‌کند. این گزینه تنها زمانی پشتیبانی می‌شود که Package در بانک اطلاعاتی msdb ذخیره شده است.

4- استفاده از نقش‌های Integration Service

برای کنترل کردن دستیابی به Package، SSIS شامل 3 نقش ثابت در سطح بانک اطلاعاتی است. نقش‌ها می‌توانند تنها روی Package هایی که در بانک اطلاعاتی msdb ذخیره شده اند، بکار روند. با استفاده از SSMS می‌توانید نقش‌ها را به Package‌ها نسبت دهید، این انتساب نقش‌ها در بانک اطلاعاتی msdb ذخیره می‌شود.

 Write action  Read action Role
 Import packages
Delete own packages
Delete all packages
Change own package roles
Change all package roles

* به نکته رجوع شود

 Enumerate own packages
Enumerate all packages
View own packages
View all packages
Execute own packages
Execute all packages
Export own packages
Export all packages
Execute all packages in SQL Server Agent
 db_ssisadmin
or
sysadmin

Import packages
Delete own packages
Change own package roles

Enumerate own packages
Enumerate all packages
View own packages
Execute own packages
Export own packages
db_ssisltduser
None
Enumerate all packages
View all packages
Execute all packages
Export all packages
Execute all packages in SQL Server Agent
db_ssisoperator
Stop all currently running packages
View execution details of all running packages
Windows administrators
* نکته: اعضای نقش‌های db_ssisadmin و dc_admin ممکن است قادر باشند مجوزهای خودشان را تا سطح sysadmin ارتقا دهند براساس این ترفیع مجوز امکان اصلاح و اجرای Package‌ها از طریق SQL Server Agent میسر می‌شود. برای محافظت در برابر این ارتقا، با استفاده از یک (account) حساب Proxy  با دسترسی محدود، Job هایی که این Package‌ها را اجرا می‌کنند، پیکربندی شوند یا  تنها اعضای نقش sysadmin به نقش‌های db_ssisadmin و dc_admin افزوده شوند.

همچنین جدول sysssispackages در بانک اطلاعاتی msdb شامل Package هایی است که در SQL Server ذخیره می‌شوند. این جدول شامل ستون هایی که اطلاعاتی درباره نقش هایی که به Package‌ها نسبت داده شده است، می‌باشد.
به صورت پیش فرض، مجوزهای نقش‌های ثابت بانک اطلاعاتی db_ssisadmin و db_ssisoperator و شناسه منحصر به فرد کاربری (unique security identifier) که Package را ایجاد کرده برای خواندن Package بکار می‌رود، و مجوزهای نقش db_ssisadmin و شناسه منحصر به فرد کاربری که Package را ایجاد کرده برای نوشتن Package به کار می‌رود. یک User باید عضو نقش db_ssisadmin و db_ssisltduser یا db_ssisoperator برای داشتن دسترسی خواندن Package باشد. یک User باید عضو نقش db_ssisadmin برای داشتن دسترسی نوشتن Package باشد.

5- اتصال به صورت Remote به Integration Service

زمانی که یک کاربر بدون داشتن دسترسی کافی تلاش کند به یک Integration Service به صورت Remote متصل شود، با پیغام خطای "Access is denied" مواجه می‌شود. برای اجتناب از این پیغام خطا می‌توان تضمین کرد که کاربر مجوز مورد نیاز DCOM را دارد.
به منظور پیکربندی کردن دسترسی کاربر به صورت Remote به سرویس Integration  مراحل زیر را دنبال کنید:
- Component Service را باز نمایید ( در Run عبارت dcomcnfg را تایپ کنید).
- گره Component Service را باز کنید، گره Computer و سپس My Computer را باز نمایید و روی DCOM Config کلیک نمایید.
- گره DCOM Config را باز کنید و از لیست برنامه هایی که می‌توانند پیکربندی شوند MsDtsServer را انتخاب کنید.
- روی Properties برنامه MsDtsServer رفته و قسمت Security را انتخاب کنید.
- در قسمت Lunch and Activation Permissions، مورد Customize را انتخاب و سپس روی Edit کلیک نمایید تا پنجره Lunch Permission باز شود.
- در پنجره Lunch Permission، کاربران را اضافه و یا حذف کنید و مجوزهای مناسب را به کاربران یا گروه‌های مناسب نسبت دهید. مجوزهای موجود عبارتند از Local Lunch، Remote Lunch، Local Activation و Remote Activation .
- در قسمت Access Permission مراحل فوق را به منظور نسبت دادن مجوزهای مناسب به کاربران یا گروه‌های مناسب انجام دهید.
- سرویس Integration  را Restart کنید.
مجوز دسترسی  Lunch به منظور شروع و خاتمه سرویس، اعطا  یا رد  می‌شود و مجوز دسترسیActivation به منظور متصل شدن به سرویس، اعطا (grant) یا رد (deny) می‌شود.

6- پیاده سازی

در ابتدا به ایجاد یک CLR Stored Procedures پرداخته می‌شود نام اسمبلی ساخته شده به این نام RunningPackage.dll می‌باشد و حاوی کد زیر است:
Partial Public Class StoredProcedures
    '------------------------------------------------
    'exec dbo.Spc_NtDtexec 'Package','ssis','ssis@ssis','1234512345'
    '------------------------------------------------
    <Microsoft.SqlServer.Server.SqlProcedure()> _
    Public Shared Sub Spc_NtDtexec(ByVal PackageName As String, _
                                   ByVal UserName As String, _
                                   ByVal Password As String, _
                                   ByVal Decrypt As String)
        Dim p As New System.Diagnostics.Process()
        p.StartInfo.FileName = "C:\Program Files\Microsoft SQL Server\100\DTS\Binn\DTExec.exe"
        p.StartInfo.RedirectStandardOutput = True
        p.StartInfo.Arguments = "/sql " & PackageName & " /User " & UserName & " /Password " & Password & " /De " & Decrypt
        p.StartInfo.UseShellExecute = False
        p.Start()
        p.WaitForExit()
        Dim output As String
        output = p.StandardOutput.ReadToEnd()
        Microsoft.SqlServer.Server.SqlContext.Pipe.Send(output)
    End Sub
End Class
در حقیقت توسط این رویه به اجرای برنامه dtexec.exe و ارسال پارامترهای مورد نیاز جهت اجرا پرداخته می‌شود. با توجه به توضیحات تئوری بیان شده، سطح حفاظتی Package ایجاد شده Encrypt all with password توصیه می‌شود که رمز مذکور در قالب یکی از پارامتر ارسالی به رویه ساخته شده موسوم به Spc_NtDtexec ارسال می‌گردد.

در قدم بعدی نیاز به Register کردن dll ساخته شده در سطح بانک اطلاعاتی SQL Server است، این گام‌ها پس از اتصال به SQL Server Management Studio به شرح زیر است:
1- فعال کردن CLR در سرویس SQL Server
SP_CONFIGURE 'clr enabled',1
GO
RECONFIGURE

2- فعال کردن ویژگی TRUSTWORTHY در بانک اطلاعاتی مورد نظر
 ALTER DATABASE <Database Name> SET TRUSTWORTHY ON
GO
RECONFIGURE

3- ایجاد Assembly و Stored Procedure در بانک اطلاعاتی مورد نظر
Assembly ساخته شده با نام RunningPacakge.dll در ریشه :C کپی شود. بعد از ثبت نمودن این Assembly لزومی به وجود آن نمی‌باشد.
USE <Database Name>
GO
CREATE ASSEMBLY [RunningPackage]
AUTHORIZATION [dbo]
FROM 'C:\RunningPackage.dll'
WITH PERMISSION_SET = UNSAFE
Go
CREATE PROCEDURE [dbo].[Spc_NtDtexec]
@PackageName [nvarchar](50),
@UserName [nvarchar](50),
@Password [nvarchar](50),
@Decrypt [nvarchar](50)
WITH EXECUTE AS CALLER
AS
EXTERNAL NAME [RunningPackage].[RunningPackage.StoredProcedures].[Spc_NtDtexec]
GO
توجه: Application User برنامه بایست دسترسی اجرای رویه ذخیره شده Spc_NtDtexec را در بانک اطلاعاتی مورد نظر داشته باشد همچنین بایست عضو نقش db_ssisoperator در بانک اطلاعاتی msdb باشد.( منظور از Application User، لاگین است که در Connection string برنامه قرار داده اید.)

در برنامه کاربردی تان کافی است متدی به شکل زیر ایجاد و با توجه به نیازتان در برنامه به فراخوانی آن و اجرای Package بپردازید.
    Private Sub ExecutePackage()
        Dim oSqlConnection As SqlClient.SqlConnection
        Dim oSqlCommand As SqlClient.SqlCommand
        Dim strCnt As String = String.Empty
        strCnt = "Data Source=" & txtServer.Text & ";User ID=" & txtUsername.Text & ";Password=" & txtPassword.Text & ";Initial Catalog=" & cmbDatabaseName.SelectedValue.ToString() & ";"
        Try
            oSqlConnection = New SqlClient.SqlConnection(strCnt)
            oSqlCommand = New SqlClient.SqlCommand
            With oSqlCommand
                .Connection = oSqlConnection
                .CommandType = System.Data.CommandType.StoredProcedure
                .CommandText = "dbo.Spc_NtDtexec"
                .Parameters.Clear()
                .Parameters.Add("@PackageName", System.Data.SqlDbType.VarChar, 50)
                .Parameters.Add("@UserName", System.Data.SqlDbType.VarChar, 50)
                .Parameters.Add("@Password", System.Data.SqlDbType.VarChar, 50)
                .Parameters.Add("@Decrypt", System.Data.SqlDbType.VarChar, 50)
                .Parameters("@PackageName").Value = txtPackageName.Text.Trim()
                .Parameters("@UserName").Value = txtUsername.Text.Trim()
                .Parameters("@Password").Value = txtPassword.Text.Trim()
                .Parameters("@Decrypt").Value = txtDecrypt.Text.Trim()
            End With
            If (oSqlCommand.Connection.State <> System.Data.ConnectionState.Open) Then
                oSqlCommand.Connection.Open()
                oSqlCommand.ExecuteNonQuery()
                System.Windows.Forms.MessageBox.Show("Success")
            End If
            If (oSqlCommand.Connection.State = System.Data.ConnectionState.Open) Then
                oSqlCommand.Connection.Close()
            End If
        Catch ex As Exception
            MessageBox.Show(ex.Message, "Error")
        End Try
    End Sub 'ExecutePackage
مطالب
جایگزین کردن jQuery با JavaScript خالص - قسمت اول - یافتن عناصر
jQuery سال‌ها به عنوان جزء اصلی توسعه‌ی برنامه‌های وب مطرح بوده‌است و برای بسیاری از توسعه دهندگان وب، یک پیشنیاز پیش‌فرض محسوب می‌شود؛ ساده‌است، قابل فهم است و به آن اطمینان داریم. زمانیکه از آن استفاده می‌کنیم دیگر نیازی نیست تا آنچنان به DOM، باگ‌های مرورگرها و یا رفتارهای متفاوت آن‌ها فکر کنیم. jQuery تمام این مشکلات را برای ما حل می‌کند. اما ... اگر روزی باگی در jQuery وجود داشت، نیاز به امکاناتی بود که هنوز در jQuery ظاهر نشده‌اند و یا حتی اجازه‌ی استفاده‌ی از jQuery را نداشته باشیم، در این حالت ... وحشت زده و تقریبا بدون هیچ نوع آمادگی به نظر خواهیم رسید.
خالق جی‌کوئری (John Resig)، این کتابخانه را در سال‌های 2006 زمانیکه Internet Explorer نگارش‌های 6 و 7 بیش از 60 درصد بازار مرورگرها را به خود اختصاص داده بودند، ارائه داد. بله؛ در آْن زمان JavaScript Web API بسیار خام، پایداری مرورگرها بسیار پایین و تطابق با استانداردهای وب در بین مرورگرهای مختلف نیز بسیار پایین بود. بنابراین علت محبوبیت کتابخانه‌ای که در این شرایط، تجربه‌ی کاری یکدستی را در بین مرورگرهای مختلف ارائه می‌داد، کاملا واضح بود. اما ... اکنون سال 2018 است و حتی مایکروسافت هم دیگر از نگارش‌های مختلف IE پشتیبانی نمی‌کند. DOM API موجود در مرورگرهای مدرن بسیار توانمند شده‌اند و در بین انواع و اقسام آن‌ها یکدست عمل می‌کنند. حتی اگر دلیل استفاده‌ی از jQuery ایجاد ساده‌تر حلقه‌ها بر روی اشیاء جاوا اسکریپتی باشد (رفع کمبودهای جاوا اسکریپت)، از زمان IE 9 به بعد، متدهای forEach و Object.keys به صورت توکار در جاوا اسکریپت وجود دارند و یا اگر نیاز به inArray.$ داشته باشید، متد Array.prototype.indexOf مدت‌ها است که جزئی از ES5 است. به همین جهت است که این روزها اخباری را مانند «GitHub نیز جی‌کوئری را کنار گذاشت» زیاد می‌شنوید. نه فقط کنار گذاشتن jQuery یک وابستگی ثالث را از برنامه حذف می‌کند، بلکه کار مستقیم با native API مرورگرها همواره به مراتب سریعتر است از کتابخانه‌هایی که سطح بالایی از abstraction آن‌ها را ارائه می‌دهند.


یافتن عناصر توسط JavaScript خالص

زمانیکه نیاز به انتخاب عناصری در صفحه باشند، بلافاصله ذهن ما به سمت ('myElement#')$ و ('myElement.')$ جی‌کوئری، معطوف می‌شود. اما ... این روزها برای انجام این نوع کارها واقعا نیازی به jQuery نیست!


یافتن عناصر بر اساس ID آن‌ها
 <div id="my-element-id"></div>
اگر بخواهیم این شیء div را بر اساس ID آن در صفحه بیابیم، روش کار آن با jQuery به صورت زیر است:
 var result = $('#my-element-id');
در اینجا این ID selector string، یک استاندارد W3C CSS1 است.
انجام این کار توسط web API و یا همان JavaScript خالص، چنین شکلی را دارد:
 var result = document.getElementById('my-element-id');
و جالب است بدانید این روش از زمان IE 5.5 وجود داشته‌است.
روش دیگر انجام اینکار با JavaScript به صورت زیر است:
  var result = document.querySelector('#my-element-id');
این روش و متد querySelector که بسیار شبیه به نمونه‌ی جی‌کوئری ارائه شده‌است، از زمان IE 8.0 قابل استفاده‌است.
در هر دو حالت، خروجی مقایسه‌ی ذیل، true است:
 result.id === 'my-element-id'; // returns true


یافتن عناصر بر اساس کلاس‌های CSS

<span class="some-class"></span>
با جی‌کوئری:
 var result = $('.some-class');
با جاوا اسکریپت خالص از زمان IE 8.0
  var result = document.getElementsByClassName('some-class');
و یا توسط querySelectorAll که شبیه به نمونه‌ی jQuery است و نیاز به پیشوند . را دارد:
 var result = document.querySelectorAll('.some-class');
در هر دو حالت، خروجی بازگشت داده شده، یک آرایه است:
 result[0].className === 'some-class'; // returns true


یافتن عناصر بر اساس تگ‌های عناصر

 <code>Console.WriteLine("Hello world!");</code>
با جی‌کوئری:
 var result = $('code');
با جاوا اسکریپت:
 var result = document.getElementsByTagName('code');
و یا
 var result = document.querySelectorAll('code');
در تمام این حالات، خروجی ارائه شده یک آرایه است:
 result[0].tagName === 'code'; // returns true



یافتن عناصر بر اساس کلاس نماها (Pseudo-classes)

Pseudo-classes از زمان ابتدایی‌ترین پیش‌نویس استاندارد CSS وجود داشته‌اند. برای مثال visited: یک Pseudo-classes است و به لینک‌های بازدید شده‌ی توسط کاربر اشاره می‌کند و یا focus: به المانی اشاره می‌کند که هم اکنون دارای focus است.
  <form>
     <label>Full Name
        <input name="full-name">
     </label>
     <label>Company
        <input name="company">
     </label>
  </form>
در این مثال اگر بخواهیم تکست باکسی را بیابیم که دارای focus است، روش جی‌کوئری آن به صورت زیر است:
  var focusedInputs = $('INPUT:focus');
و روش جاوا اسکریپتی آن به این صورت:
 var companyInput = document.querySelector('INPUT:focus');
کاری که در اینجا انجام شده ترکیب یک tag name و یک pseudo-class modifier است که جزئی از استاندارد CSS می‌باشد. بنابراین روش جی‌کوئری، چیزی بیشتر از انتقال این استاندارد به توابع بومی مرورگر نیست.


یافتن عناصر بر اساس ارتباط والد و فرزندی آن‌ها
  <div>
     <a href="https://www.dntips.ir">
        <span>Go to site</span>
     </a>
     <p>Some text</p>
     Some other text
  </div>
یافتن والدها:
روش یافتن والد anchor tag در جی‌کوئری توسط متد parent؛ با فرض اینکه a$ به شیء anchor اشاره می‌کند:
 var $result = $a.parent();
و در جاوا اسکریپت توسط خاصیت parentNode:
 var result = a.parentNode;

یافتن فرزندان:
در جی‌کوئری:
 var result = $('#myParent').children();
و برای یافتن فرزندان یک المان توسط CSS 2 child selectors:
 var result = document.querySelectorAll('DIV > *');
خروجی این کوئری، المان‌های a و p هستند و یا اگر فقط بخواهیم pها را انتخاب کنیم:
  var result = document.querySelectorAll('DIV > P');
روش دیگر انجام اینکار استفاده از خاصیت childNodes یک المان است:
 var result = document.getElementById('myParent').childNodes;
var result = div.childNodes;
البته این خاصیت آرایه‌ای، Text و Comments را هم علاوه بر عناصر بازگشت می‌دهد. البته اگر می‌خواهید آن‌ها را حذف کنید، از خاصیت children استفاده کنید:
 var result =document.getElementById('myParent').children;
و یا یافتن تمام المان‌های anchor ذیل المانی با Id مساوی myParent:
 var result =document.querySelectorAll('#myParent A');



جستجوی عناصر با صرفنظر کردن از بعضی از آن‌ها
  <ul role="menu">
     <li>choice 1</li>
     <li class="active">choice 2</li>
     <li>choice 3</li>
  </ul>
در این مثال گزینه‌ی دوم دارای class مساوی active است. اگر بخواهیم تمام liهایی را که دارای این کلاس نیستند، انتخاب کنیم، در جی‌کوئری خواهیم داشت:
 var $result = $('UL LI').not('.active');
و در جاوا اسکریپت:
 var result = document.querySelectorAll('UL LI:not(.active)');
هرچند JavaScript دارای متد not جی‌کوئری نیست، اما می‌توان از W3C CSS3 negation pseudo-class بجای آن استفاده کرد. مزیت آن، استاندارد بودن و عدم نیاز به کتابخانه‌ای ثالث برای تدارک آن است.


انتخاب چندین المان با هم

  <div id="link-container">
     <a href="https://github.com/VahidN">GitHub</a>
  </div>
  <ol>
     <li>one</li>
     <li>two</li>
  </ol>
  <span class="my-name">VahidN</span>
در اینجا می‌خواهیم المان‌های link-container، my-name و لیست مرتب شده را بدون نوشتن حلقه‌ای انتخاب کنیم. روش انجام اینکار در jQuery به صورت زیر است:
 var $result = $('#link-container, .my-name, OL');
و در جاوا اسکریپت خواهیم داشت:
 var result = document.querySelectorAll('#link-container, .my-name, OL');

یافتن گروهی از المان‌ها بر اساس نوع آن‌ها:
 var result = document.querySelectorAll(
 'BUTTON[type="submit"], INPUT[type="submit"]'
);
در اینجا تمام المان‌های ورودی از نوع <"button type="submit> و <"input type="submit> را بازگشت می‌دهد.


جایگزین کردن $ جی‌کوئری با جاوا اسکریپت

تا اینجا حتما به شباهت کدهای خالص جاوا اسکریپت و jQuery دقت کرده‌اید. اگر بخواهیم برای $ جی‌کوئری، یک معادل جاوا اسکریپتی تهیه کنیم، به قطعه کد زیر خواهیم رسید:
window.$ = function(selector) {
     return document.querySelectorAll(selector);
};
و نحوه‌ی استفاده‌ی از آن نیز همانند قبل است:
  $('.some-class');
  $('#some-id');
  $('.some-parent > .some-child');
  $('UL LI:not(.active)');
مطالب
آموزش Backload (آپلود چندین فایل به طور همزمان با آجاکس )
یک پروژه‌ی جدید Asp.net MVC ایجاد کنید و .Net Framework آن را 4.5 و یا بالاتر انتخاب کنید. دلیل اینکار را در ادامه‌ی آموزش به شما توضیح خواهم داد.
برای شروع کار با Backload ابتدا به قسمت Nutget می‌رویم که در مسیر زیر است :
Tools/Library Package Manager/Manage Nutget Packages For Solution
در پنجره‌ی باز شده از کادر سمت چپ، بر روی قسمت Online کلیک می‌کنیم و سپس در کادر Search در گوشه‌ی بالا سمت راست، کلمه‌ی Backload را تایپ می‌کنیم در نتایج Search دو مورد زیر را Install می‌کنید :

JQuery File Upload Plugin  *

Backload. A Proffesional Full Featured Asp.Net FileUpload Controller  *

پس از نصب دو مورد بالا ، موارد زیر را که در دو عکس پایین می‌بینید، به پروژه‌ی شما اضافه خواهند شد:















تا اینجا ما ابزار Backload را بر روی پروژه‌ی خود نصب کردیم. همانطور که می‌بینید Backload یک Demo به پروژه اضافه کرده که شامل View ، Controller و ... می‌باشد. اکنون پروژه را اجرا کنید و با FileUpload کار کنید. البته توجه داشته باشید که  url صفحه‌ای که FileUpload در آن قرار دارد به این صورت است : localhost:PortNumber/BackloadDemo/Index

*نکاتی که باید بدانید:

عکس هایی که شما آپلود می‌کنید در پوشه‌ی Files موجود در ریشه‌ی پروژه‌ی شما قرار می‌گیرند این پوشه بوسیله‌ی خود ابزار Backload ساخته می‌شود.
چنانچه بخواهید در پوشه‌ی Files پوشه‌ای ایجاد کنید، ابتدا View آن‌را باز کنید. این View در پروژه، در مسیر Views/ BackloadDemo/Index قرار دارد .
در داخل تگ form یک Hidden Field با نام objectContext اضافه می‌کنید. Value ایی که شما به این Hidden Field نسبت می‌دهید، نام پوشه‌ی شما در پوشه‌ی Files خواهد بود. مانند تصویر زیر: در اینجا پوشه‌ای با نام 2014-02 در پوشه‌ی Files وقتی که فایلی را باFile Upload آپلود می‌کنیم، ایجاد می‌شود.

چنانچه بخواهید در پوشه‌ای که خودتان ایجاد کردید (که در این مثال 2014-02 می‌باشد) پوشه‌های متعدد دیگری هم داشته باشید Hidden Field ای با نام uploadContext ایجاد می‌کنید؛ مانند تصویر زیر :


اکنون اگر فایل جدیدی را آپلود کنید در مسیر 

ذخیره می‌شود . یعنی بین نام هر پوشه از سمی کولن ; در Value استفاده می‌کنید.

تا اینجا ما می‌توانیم بوسیله‌ی ابزار Backload عکس‌ها را آپلود ، حذف و مسیر آپلود عکس‌ها را تغییر دهیم.

اگر توجه کرده باشید دفعات بعد که پروژه را اجرا می‌کنید عکس‌های موجود در پوشه، در گرید ویو File Upload به شما نمایش داده خواهد شد. حال اگر بخوایم عکس‌های موجود در پوشه‌ی دیگری را نمایش دهیم باید چه کار کنیم؟!
گاهی اوقات نیاز داریم که محتویات پوشه‌ای خاص را در گرید ویو File Upload مان نمایش دهیم. برای این کار شما باید کنترلر FileUploadController که در فایل ضمیمه در آموزش هست را در پوشه‌ی Controller پروژه‌ی خود کپی کنید .اگر شما این کنترلر را باز کنید مواردی مانند کلمه کلیدی Task و async را مشاهده خواهید کرد. این موارد در .Net Framework 4 شناسایی نمی‌شود. برای همین در ابتدای آموزش تاکید کردم که .Net Framework 4.5 و یا بالاتر را برای پروژه‌ی خود انتخاب کنید .
در تابع Handler_GetFilesRequestStarted در این کنترلر باید مشخص کنید که فایل‌های موجود در کدام مسیر را برای شما نمایش دهد؛ آن هم با استفاده از دستور زیر :

اکنون قبل از آنکه پروژه را اجرا کنید فایل Backload.Demo.js که در مسیر Scripts/Fileupload هست را باز کنید و url موجود در آنرا مانند عکس زیر تغیییر دهید :

حالا پروژه را اجرا کنید. خواهید دید تمامی فایل‌های موجود در مسیری را که شما مشخص کرده‌اید، برایتان نمایش خواهد داد.


چنانچه بخواهید تعداد مثلا 5 فایل برای شما در گرید ویو مربوط به FileUpload نمایش داده شود، به تابع handler_GetFilesRequestFinished می‌روید. متغیر limit مشخص می‌کند که 5 فایل نمایش داده شود. می‌توانید این عدد را به دلخواه خود تغییر دهید.

نکته‌ی بسیار مهم دیگری که باید به آن توجه شود مربوط به Hidden Field نام پوشه‌ها می‌باشد. بار دیگر پروژه را اجرا کنید. با استفاده از ابزاری مثل FireBug کدهای html صفحه‌ی خود را ببینید. Hidden Field ایی با نام objectContext را پیدا کنید و Value آنرا به test تغییر دهید. فایلی را آپلود کنید حالا به پوشه‌ی Files موجود در ریشه‌ی پروژه بروید. می‌بینید که پوشه‌ای با نام testایجاد شده و فایلی هم که آپلود کردید در آن قرار دارد که این یک اشکال است. برای اینکه جلوی این گونه کارها را بگیریم به تابع handler_StoreFileRequestStartedAsync می‌رویم و کد زیر را می‌نویسیم :

دستور e.Context.PipelineControl.ExecutePipeline = false; باعث میشود که اجرای تابع متوقف شود.

فایل ضمیمه :

FileUploadController-462d551688cf48c68cb55343ac5464f3.zip

برای مشاهده مثال‌های دیگری درباره‌ی Backload به این لینک بروید.

موفق باشید.

این نوشتار اولین آموزش من در این سایت می‌باشد و جا دارد از دوست خوبم "محبوبه قربانی" که باعث شد من با MVC آشنا شوم تشکر کنم. 

نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت یازدهم - کار با فرم‌ها - قسمت دوم
با سلام و تشکر از مقالتون
من مقالاتتون در مورد انگولار 2 را پیگیری می‌کنم و مشکلی هم نداشتم ولی بعد از این مقالتون که نسخه انگولار rc1 شد به یک مشکلی بر خوردم. وقتی یکی از ts‌ها رو تغییر می‌دم . اپلیکیشن ارور زیر می‌ده:
 EXCEPTION: TypeError: Cannot read property 'isSkipSelf' of null
بعد از کلی تحقیق متوجه شدم و مقایسه با کدهای مقاله شما متوجه در جاوااسکریپتهای تولید شده جدید دو خط زیر تولید نمی‌شود:
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
لطفا اگر می‌توانید راهنمایی کنید.
مطالب
شروع به کار با EF Core 1.0 - قسمت 13 - بررسی سیستم ردیابی تغییرات
هر Context در EF Core، دارای خاصیتی است به نام ChangeTracker که وظیفه‌ی آن ردیابی تغییراتی است که نیاز است به بانک اطلاعاتی منعکس شوند. برای مثال زمانیکه توسط یک کوئری، شیءایی را باز می‌گردانید و سپس مقدار یکی از خواص آن‌را تغییر داده و متد SaveChanges را فراخوانی می‌کنید، این ChangeTracker است که به EF اعلام می‌کند، کوئری Update ایی را که قرار است تولید کنی، فقط نیاز است یک خاصیت را به روز رسانی کند؛ آن هم تنها با این مقدار تغییر یافته.

روش‌های مختلف اطلاع رسانی به سیستم ردیابی تغییرات

متد DbSet.Add کار اطلاع رسانی تبدیل وهله‌‌های ثبت شده را به کوئری‌های Insert رکوردهای جدید، انجام می‌دهد:
using (var db = new BloggingContext())
{
   var blog = new Blog { Url = "http://sample.com" };
   db.Blogs.Add(blog);
   db.SaveChanges();
}

سیستم ردیابی اطلاعات، اگر تغییراتی را در خواص اشیاء تحت نظر خود مشاهده کند، سبب تولید کوئری‌های Update می‌گردد. یک چنین اشیایی تحت نظر Context هستند:
الف) اشیایی که در طول عمر Context از دیتابیس کوئری گرفته شده‌اند.
ب) اشیایی که در طول عمر Context به آن اضافه شده‌اند (حالت قبل).
using (var db = new BloggingContext())
{
  var blog = db.Blogs.First();
  blog.Url = "http://sample.com/blog";
  db.SaveChanges();
}

و متد DbSet.Remove کار اطلاع رسانی تبدیل وهله‌های حذف شده را به کوئری‌های Delete معادل، انجام می‌دهد:
using (var db = new BloggingContext())
{
  var blog = db.Blogs.First();
  db.Blogs.Remove(blog);
  db.SaveChanges();
}
اگر شیء حذف شده پیشتر توسط متد DbSet.Add اضافه شده باشد، تنها این شیء از Context حذف می‌شود و کوئری در مورد آن تولید نخواهد شد.

به علاوه امکان ترکیب متدهای Add، Remove و همچنین به روز رسانی اشیاء در طی یک Context و با فراخوانی یک SaveChanges در انتهای کار نیز وجود دارد. از این جهت که یک Context، الگوی واحد کار را پیاده سازی می‌کند و بیانگر یک تراکنش است. در این حالت ترکیبی، یا کل تراکنش با موفقیت به پایان می‌رسد و یا در صورت بروز مشکلی، هیچکدام از تغییرات درخواستی، اعمال نخواهند شد.


عملیات ردیابی، بر روی هر نوع Projections صورت نمی‌گیرد

اگر توسط LINQ Projections، نتیجه‌ی نهایی کوئری را تغییر دادید، فقط در زمانی سیستم ردیابی بر روی آن فعال خواهد بود که projection نهایی حاوی اصل موجودیت مدنظر باشد. برای مثال در کوئری ذیل چون در Projection صورت گرفته‌ی در متد Select، هنوز در خاصیت Blog، به اصل موجودیت Blog اشاره می‌شود، نتیجه‌ی این کوئری نیز تحت نظر سیستم ردیابی خواهد بود:
using (var context = new BloggingContext())
{
   var blog = context.Blogs
      .Select(b =>
            new
            {
               Blog = b,
               Posts = b.Posts.Count()
            });
 }
اما در کوئری ذیل، خیر:
using (var context = new BloggingContext())
{
   var blog = context.Blogs
            .Select(b =>
                 new
                 {
                   Id = b.BlogId,
                   Url = b.Url
                 });
 }
در اینجا در Projection انجام شده، نتیجه‌ی نهایی، به هیچکدام از موجودیت‌های ممکن اشاره نمی‌کند. بنابراین نتیجه‌ی آن تحت نظر سیستم ردیابی قرار نمی‌گیرد.


لغو سیستم ردیابی تغییرات، در زمانیکه به آن نیازی نیست

سیستم ردیابی تغییرات بر اساس مفاهیم AOP و تولید پروکسی‌های آن کار می‌کند. این پروکسی‌ها، اشیایی شفاف هستند که اشیاء شما را احاطه می‌کنند و هر تغییری را که اعمال می‌کنید، ابتدا از این غشاء رد شده و در سیستم ردیابی EF ثبت می‌شوند. سپس به وهله‌ی اصلی شیء موجود اعمال خواهند شد.
بدیهی است تولید این پروکسی‌ها، دارای سربار است و اگر هدف شما صرفا کوئری گرفتن از اطلاعات، جهت نمایش آن‌ها است، نیازی به تولید خودکار این پروکسی‌ها را ندارید و این مساله سبب کاهش مصرف حافظه‌ی برنامه و بالا رفتن سرعت آن می‌شود.
در قسمت قبل عنوان شد که «یک چنین اشیایی تحت نظر Context هستند: الف) اشیایی که در طول عمر Context از دیتابیس کوئری گرفته شده‌اند.»
اگر می‌خواهید این حالت پیش فرض را لغو کنید، از متد AsNoTracking استفاده نمائید:
using (var context = new BloggingContext())
{
  var blogs = context.Blogs.AsNoTracking().ToList();
}
یک چنین کوئری‌هایی برای سناریوهای فقط خواندنی (گزارشگیری‌ها) مناسب هستند و بدیهی است هرگونه تغییری در لیست blogs حاصل، توسط context جاری ردیابی نشده و در نهایت به بانک اطلاعاتی (در صورت فراخوانی SaveChanges) اعمال نمی‌گردد.

اگر می‌خواهید متد AsNoTracking را به صورت خودکار به تمام کوئری‌های یک context خاص اعمال کنید، روش کار و تنظیم آن به صورت زیر است:
using (var context = new BloggingContext())
{
    context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;


نکات به روز رسانی ارجاعات موجودیت‌ها

دو حالت زیر را درنظر بگیرید که در اولی، blog از بانک اطلاعاتی واکشی شده‌است و post به صورت مستقیم وهله سازی شده‌است:
using (var context = new BloggingContext())
{
  var blog = context.Blogs.First();
  var post = new Post { Title = "Intro to EF Core" };

  blog.Posts.Add(post);
  context.SaveChanges();
}
و در دومی blog به صورت مستقیم وهله سازی گردیده‌است و post از بانک اطلاعاتی واکشی شده‌است:
using (var context = new BloggingContext())
{
  var blog = new Blog { Url = "http://blogs.msdn.com/visualstudio" };
  var post = context.Posts.First();

  blog.Posts.Add(post);
  context.SaveChanges();
}
در حالت اول، Post، ابتدا به بانک اطلاعاتی اضافه شده و سپس این مطلب جدید به لیست ارجاعات blog اضافه می‌شود (Post جدیدی اضافه شده و اولین Blog، جهت درج آن به روز رسانی می‌شود).
در حالت دوم، ابتدا blog در بانک اطلاعاتی ثبت می‌شود (چون برخلاف حالت اول، تحت نظر context نیست) و سپس این post (که تحت نظر context است) به مجموعه مطالب آن اضافه می‌شود (بلاگ جدیدی اضافه شده و ارجاع مطلب موجودی به آن اضافه می‌شود).


وارد کردن یک موجودیت به سیستم ردیابی اطلاعات

در مثال قبل مشاهده کردیم که اگر موجودیتی تحت نظر context نباشد (برای مثال توسط یک کوئری به context وارد نشده باشد)، در حین ذخیره سازی ارجاعات، با آن به صورت یک وهله‌ی جدید رفتار شده و حتما در بانک اطلاعاتی به صورت یک رکورد جدید ذخیره می‌شود؛ حتی اگر Id آن‌را دستی تنظیم کرده باشید که ندید گرفته خواهد شد.
اگر Id و سایر اطلاعات شیءایی را دارید، نیازی نیست تا حتما توسط یک کوئری ابتدا آن‌را از بانک اطلاعاتی دریافت و سپس به صورت خودکار وارد سیستم ردیابی کنید؛ متد Attach نیز یک چنین کاری را انجام می‌دهد:
 var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" };
 context.Blog.Attach(blog);
 context.SaveChanges();
در اینجا هرچند شیء Blog از بانک اطلاعاتی واکشی نشده‌است، اما چون توسط متد Attach به DbSet اضافه شده‌است، اکنون جزئی از اشیاء تحت نظر به حساب می‌آید؛ اما با یک شرط. حالت اولیه‌ی این شیء به EntityState.Unchanged تنظیم شده‌است. یعنی زمانیکه SaveChanges فراخوانی می‌شود، عملیات خاصی صورت نخواهد گرفت و هیچ اطلاعاتی در بانک اطلاعاتی درج نمی‌گردد.
علاوه بر متد Attach، متد AttachRange نیز برای افزودن لیستی از موجودیت‌ها در حالت EntityState.Unchanged، پیش بینی شده‌است.

روش دیگر انجام اینکار به صورت ذیل است:
در اینجا ابتدا یک وهله‌ی جدید از Blog ایجاد شده‌است و سپس توسط متد Entry به Context وارد شده و همچنین حالت آن به صورت صریح، به تغییر یافته، مشخص گردیده‌است:
 var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" };
 context.Entry(blog).State = EntityState.Modified ;
 context.SaveChanges();
و یا می‌توان این عملیات را به صورت زیر ساده کرد:
 var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" };
 context.Update(blog);
 context.SaveChanges();
در اینجا متد جدید Update، همان کار Attach و سپس تنظیم حالت را به EntityState.Modified انجام می‌دهد.
به علاوه متد UpdateRange نیز برای افزودن لیستی از موجودیت‌ها در حالت EntityState.Modified، پیش بینی شده‌است.

یک نکته: متدهای Attach و Update، هم بر روی یک DbSet و هم بر روی Context، قابل اجرا هستند. اگر بر روی Context اجرا شدند، نوع موجودیت دریافتی به نوع DbSet متناظر به صورت خودکار نگاشت شده و استفاده می‌شود (context.Set<T>().Attach(entity)). یعنی در حقیقت بین این دو حالت تفاوتی نیست و امکان فراخوانی این متدها بر روی Context، صرفا جهت سهولت کار درنظر گرفته شده‌است.


تفاوت رفتار context.Entry در EF Core با EF 6.x

متد  context.Entry در EF 6.x هم وجود دارد. اما در EF core سبب تغییر وضعیت گراف متصل به یک شیء نمی‌شود و ضعیت روابط آن‌را به روز رسانی نمی‌کند (برخلاف EF 6.x). اگر در EF Core نیاز به یک چنین به روز رسانی گراف مانندی را داشتید، باید از متد جدید context.ChangeTracker.TrackGraph به نحو ذیل استفاده نمائید:
 context.ChangeTracker.TrackGraph(blog, e => e.Entry.State = EntityState.Added);


کوئری گرفتن از سیستم ردیابی اطلاعات

این سناریوها را درنظر بگیرید:
 - می‌خواهم سیستمی شبیه به تریگرهای اس کیوال سرور را با EF داشته باشم.
 - می‌خواهم اطلاعات تمام رکوردهای ثبت شده، حذف شده و به روز رسانی شده را لاگ کنم.
 - می‌خواهم پس از ثبت رکوردی در هر جای برنامه، شبیه به مباحث SQL Server Service Broker و SqlDependency بلافاصله مطلع شده و توسط SignalR اطلاع رسانی کنم.

و در حالت کلی می‌خواهم پیش و یا پس از ثبت اطلاعات، بتوانم به تغییرات صورت گرفته دسترسی داشته باشم و عملیاتی را بر روی آن‌ها انجام دهم. تمام این موارد و سناریوها را با کوئری گرفتن از سیستم ردیابی اطلاعات EF می‌توان پیاده سازی کرد.
برای نمونه در مطلب قبل و قسمت «طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط»، یک کلاس پایه را که مقادیر پیش فرض خود را از SQL Server دریافت می‌کند، طراحی کردیم. در اینجا می‌خواهیم با استفاده از سیستم ردیابی EF، طراحی این کلاس پایه را عمومی کرده و سازگار با تمام بانک‌های اطلاعاتی موجود کنیم.
جهت یادآوری، کلاس پایه موجودیت‌ها، یک چنین شکلی را داشته:
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; }
}
اکنون به کلاس Context برنامه مراجعه کرده و متد SaveChanges آن‌را بازنویسی می‌کنیم:
    public class ApplicationDbContext : DbContext
    {
        // same as before 

        public override int SaveChanges()
        {
            this.ChangeTracker.DetectChanges();

            var modifiedEntries = this.ChangeTracker
                                      .Entries<BaseEntity>()
                                      .Where(x => x.State == EntityState.Modified);
            foreach (var modifiedEntry in modifiedEntries)
            {
                modifiedEntry.Entity.DateUpdated = DateTime.UtcNow;
            }
 
            var addedEntries = this.ChangeTracker
                                      .Entries<BaseEntity>()
                                      .Where(x => x.State == EntityState.Added);
            foreach (var addedEntry in addedEntries)
            {
                addedEntry.Entity.DateAdded = DateTime.UtcNow;
            }
 
            return base.SaveChanges();
        }
    }
این متد SaveChanges، نقطه‌ی مشترک تمام تغییرات برنامه است. به همین دلیل است که اینجا را می‌توان جهت اعمالی، پیش و پس از فراخوانی متد اصلی base.SaveChanges که کار نهایی درج تغییرات را به بانک اطلاعاتی انجام می‌دهد، مورد استفاده قرار داد.
در اینجا کار با کوئری گرفتن از خاصیت ChangeTracker شروع می‌شود. سپس باید مشخص کنیم چه نوع موجودیت‌هایی را مدنظر داریم. چون تمام موجودیت‌های ما از کلاس پایه‌ی BaseEntity مشتق می‌شوند، بنابراین کوئری گرفتن بر روی این نوع، به معنای دسترسی به تمام موجودیت‌های برنامه نیز هست. سپس در اینجا اگر حالتی EntityState.Modified بود، فقط مقدار خاصیت DateUpdated را به صورت خودکار مقدار دهی می‌کنیم و اگر حالتی EntityState.Added بود، تنها مقدار خاصیت DateAdded را به روز رسانی خواهیم کرد.
در یک چنین حالتی دیگر نیازی نیست تا مقادیر این خواص را در حین ثبت اطلاعات برنامه به صورت دستی مشخص کنیم.

یک نکته: اگر به ابتدای متد بازنویسی شده دقت کنید، فراخوانی متد this.ChangeTracker.DetectChanges در آن انجام شده‌است. علت اینجا است که این فراخوانی به صورت خودکار توسط متد base.SaveChanges انجام می‌شود، اما چون این مرحله را تا انتهای متد بازنویسی شده، به تاخیر انداخته‌ایم، نیاز است خودمان به صورت دستی سبب محاسبه‌ی مجدد تغییرات صورت گرفته شویم.

نکته‌ای در مورد بهبود کیفیت کدهای متد SaveChanges: استفاده‌ی Change Tracker به این صورت با بازنویسی متد SaveChanges بسیار مرسوم است. اما پس از مدتی به متد SaveChanges ایی خواهید رسید که کنترل آن از دست خارج می‌شود. به همین جهت برای EF 6.x پروژه‌هایی مانند EFHooks طراحی شده‌اند تا کپسوله سازی بهتری را بتوان ارائه داد. انتقال کدهای آن به EF Core کار مشکلی نیست و اصل آن، بازنویسی HookedDbContext آن است که نحوه‌ی مدیریت شکیل‌تر کوئری گرفتن از ChangeTracker را بیان می‌کند.


خواص سایه‌ای یا Shadow properties

EF Core به همراه مفهوم کاملا جدیدی است به نام خواص سایه‌ای. این نوع خواص در سمت کدهای ما و در کلاس‌های موجودیت‌های برنامه وجود خارجی نداشته، اما در سمت جداول بانک اطلاعاتی وجود دارند و اکنون امکان کوئری گرفتن و کار کردن با آن‌ها در EF Core میسر شده‌است.
برای تعریف آن‌ها، بجای افزودن خاصیتی به کلاس‌های برنامه، کار از متد OnModelCreating به نحو ذیل شروع می‌شود:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  modelBuilder.Entity<Blog>().Property<DateTime>("DateAdded");
در اینجا یک خاصیت جدید به نام DateAdded، از نوع DateTime که در کلاس Blog وجود خارجی ندارد، تعریف شده‌است. به این خاصیت، shadow property می‌گویند (سایه‌ای است از ستونی از جدول بلاگ).
سپس برای کار کردن و کوئری گرفتن از آن می‌توان از متد جدید EF.Property، به نحو ذیل استفاده کرد:
 var blogs = context.Blogs.OrderBy(b => EF.Property<DateTime>(b, "DateAdded"));
همچنین برای مقدار دهی آن تنها می‌توان توسط سیستم Change Tracker اقدام نمود:
 context.Entry(myBlog).Property("DateAdded").CurrentValue = DateTime.Now;
و یا در همان قطعه کد بازنویسی متد SaveChanges فوق، نحوه‌ی دسترسی به اینگونه خواص، به صورت زیر می‌باشد:
foreach (var addedEntry in addedEntries)
{
  addedEntry.Property("DateAdded").CurrentValue = DateTime.UtcNow;
}
مهم‌ترین دلیل وجودی این خواص، پیاده سازی روابطی مانند many-to-many، در نگارش‌های بعدی EF Core هستند. در حقیقت جدول واسطی که در اینجا به صورت خودکار تشکیل می‌شود، به همراه خواصی است که تاکنون امکان دسترسی به آن‌ها در کدهای EF وجود نداشت؛ اما Shadow properties این امر را میسر می‌کنند (فیلدهایی که در سمت بانک اطلاعاتی وجود دارند، اما در کدهای کلاس‌های ما، خیر).