مطالب
آموزش Linq - بخش ششم: عملگرهای پرس و جو قسمت چهارم
عملگر‌های تولید  Generation Operator

عملگر‌های تولید، برای ما توالی ایجاد می‌کنند و تفاوت‌های عمده‌ای با سایر عملگرهای پرس و جو دارند که در بخش زیر به آنها اشاره می‌کنیم:
 1- هیچ توالی ورودی را دریافت نمی‌کنند.
 2- این عملگر‌ها بصورت متد الحاقی پیاده سازی نشده‌اند و بصورت متد‌های استاتیک در کلاس Enumerable قرار گرفته‌اند.
امضاء زیر مربوط به متد Empty  می‌باشد:
 public static IEnumerable<TResult> Empty<TResult>()

Empty

عملگر Empty یک توالی بدون عنصر (Empty) را بر اساس نوع مشخص شده، ایجاد می‌کند.
در کد زیر نحوه ایجاد یک توالی خالی از نوع Ingredient نشان داده شده است.
IEnumerable<Ingredient> ingredients = Enumerable.Empty<Ingredient>();
Console.WriteLine(ingredients.Count());
خروجی کد بالا :
 0
پیاده سازی توسط عبارت‌های جستجو
معادل عملگر Empty، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.

Range
عملگر پرس و جوی Range، یک توالی از مقادیر صحیح متوالی را برای ما ایجاد می‌کند. اولین پارامتر این عملگر عنصر آغاز کننده توالی است و دومین پارامتر این عملگر تعداد کل عناصر توالی تولید شده، با احتساب عنصر اول خواهد بود.
مثال:
IEnumerable<int> fiveToTen = Enumerable.Range(5,6);
foreach (var num in fiveToTen)
{
   Console.WriteLine(num);
}
خروجی مثال بالا:
5
6
7
8
9
10
همانطور که ملاحظه کردید مجموعا 6 عنصر برای توالی تولید شدند و اولین عنصر، با عدد 5 آغاز شده است.

پیاده سازی توسط عبارت‌های جستجو

معادل عملگر Range، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.

Repeat
عملگر پرس و جوی Repeat یک عدد را به تعداد بار مشخصی در توالی خروجی تکرار می‌کند.
مثال:
IEnumerable<int> fiveToTen = Enumerable.Repeat(42, 6);
foreach (var num in fiveToTen)
{
    Console.WriteLine(num);
}
خروجی مثال بالا:
42
42
42
42
42
42
پیاده سازی توسط عبارت‌های جستجو
معادل عملگر Repeat، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگرهای  کمی (Quantifier Operators)
عملگرهای Quantifier یک توالی ورودی را گرفته، آن را ارزیابی کرده و یک مقدار منطقی را باز می‌گردانند.

عملگر Contains
عملگر Contains  عناصر یک توالی را ارزیابی می‌کند و در صورتیکه مقدار مورد نظر ما در توالی وجود داشته باشد، ارزش True باز می‌گرداند.
مثال:
int[] nums = {1, 2, 3};
bool isTowThere = nums.Contains(2);
bool isFiveThere = nums.Contains(5);

Console.WriteLine(isTowThere);
Console.WriteLine(isFiveThere);
خروجی مثال بالا :
True
False

پیاده سازی توسط عبارت‌های جستجو

معادل عملگر Contains، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.

عملگر Any
عملگر Any دو امضاء مختلف را دارد:
 1- اولین امضاء: در صورتیکه توالی شامل حداقل یک عنصر باشد، ارزش True بازگردانده می‌شود.
 2- دومین امضاء: یک عبارت پیش بینی را قبول می‌کند. در صورتیکه حداقل یکی از عناصر توالی، عبارت پیش بینی را تامین کند، ارزش صحیح باز گردانده می‌شود.
مثال: بررسی امضاء اول عملگر Any
int[] nums = { 1, 2, 3 };
IEnumerable<int> noNums = Enumerable.Empty<int>();

Console.WriteLine(nums.Any());
Console.WriteLine(noNums.Any());
خروجی مثال بالا:
True
False
مثال: بررسی امضاء دوم عملگر Any
int[] nums = { 1, 2, 3 };
bool areAnyEvenNumbers = nums.Any(x => x % 2 == 0);
Console.WriteLine(areAnyEvenNumbers);
خروجی مثال بالا:
 True
در مثال بالا، عبارت پیش بینی مشخص می‌کند که اعداد زوج در توالی وجود داشته باشند یا خیر.

پیاده سازی توسط عبارت‌های جستجو
معادل عملگر Any، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.

عملگر All
عملگر پرس و جوی All، یک عبارت پیش بینی را دریافت می‌کند و عناصر توالی ورودی را بر مبنای آن ارزیابی می‌کند تا مشخص شود همه عناصر، شرط پیش بینی را تامین می‌کنند.
در کد زیر بررسی می‌کنیم که آیا همه عناصر توالی مواد غذایی، جزء مواد غذایی کم چرب می‌باشند یا خیر .
Ingredient[] ingredients =
{
new Ingredient { Name = "Sugar", Calories = 500 },
new Ingredient { Name = "Egg", Calories = 100 },
new Ingredient { Name = "Milk", Calories = 150 },
new Ingredient { Name = "Flour", Calories = 50 },
new Ingredient { Name = "Butter", Calories = 400 }
};
bool isLowFatRecipe = ingredients.All(x => x.Calories < 200);
Console.WriteLine(isLowFatRecipe);
خروحی کد بالا :
False

نکته : عملگر All به محض پیدا کردن عنصری که شرط مشخص شده را نقض کند، ارزش False را باز می‌گرداند و ادامه بررسی عناصر باقی مانده را متوقف می‌کند.

پیاده سازی توسط عبارت‌های جستجو
معادل عملگر Any، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.

عملگر SequenceEqual
عملگر SequenceEqual دو توالی را با هم مقایسه کرده و در صورتیکه عناصر هر دو توالی برابر و ترتیب قرار گیری آنها نیز یکسان باشند، ارزش True باز گردانده می‌شود.
مثال:
IEnumerable<int> sequence1 = new[] {1, 2, 3};
IEnumerable<int> sequence2 = new[] { 1, 2, 3 };
bool isSeqEqual = sequence1.SequenceEqual(sequence2);
Console.WriteLine(isSeqEqual);
خروجی مثال بالا:
 True

در صورتی که دو توالی عناصر یکسانی داشته باشند، ولی ترتیب قرار گیری عناصر با هم یکسان نباشند، عملگر ارزش False را باز می‌گرداند.
مثال :
IEnumerable<int> sequence1 = new[] { 1, 2, 3 };
IEnumerable<int> sequence2 = new[] { 3, 2, 1 };
bool isSeqEqual = sequence1.SequenceEqual(sequence2);
Console.WriteLine(isSeqEqual);
خروجی مثال بالا:
False
پیاده سازی توسط عبارت‌های جستجو
معادل عملگر SequenceEqual، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.
 
عملگر‌های تجمیع/تجمعی Aggregate Operators
عملگرهای Aggregate یک توالی ورودی را دریافت و یک مقدار عددی (Scalar Value) را باز می‌گردانند. مقدار بازگردانده شده، حاصل یک عملیات محاسباتی می‌باشد.
لیستی از عملگر‌های تجمیع ( Aggregate Operators ):
 • Count
 • LongCount
 • Sum
 • Min
 • Max
 • Average
 • Aggregate

عملگر Count
عملگر Count، تعداد عناصر توالی ورودی را باز می‌گرداند. عملگر Count، دو امضاء مختلف دارد. یکی از این امضاء‌ها یک عبارت پیش بینی را می‌پذیرد.
کد زیر، امضاء اول عملگر Count را نشان می‌دهد:
int[] nums = { 1, 2, 3 };
int numberOfElements = nums.Count();
Console.WriteLine(numberOfElements);
خروجی کد بالا:
 3

وقتی عبارت پیش بینی بکار گرفته می‌شود، عملگر Count تنها عناصری را که شرط را تامین کنند، شمارش می‌کند.
در کد زیر عملگر Count، همه عناصر زوج توالی ورودی را شمارش می‌کند:
int[] nums = { 1, 2, 3 };
int numberOfEvenElements = nums.Count(x => x % 2 == 0);
Console.WriteLine(numberOfEvenElements);
خروجی کد بالا :
1

پیاده سازی توسط عبارت‌های جستجو

معادل عملگر Count ، کلمه‌ی کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.

عملگر LongCount
این عملگر مثل عملگر Count عمل می‌کند، اما با این تفاوت که خروجی آن به جای نوع int از نوع long می‌باشد. این عملگر برای شمارش توالی‌های  ورودی بسیار بزرگ مورد استفاده قرار می‌گیرد.

پیاده سازی توسط عبارت‌های جستجو

معادل عملگر LongCount، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.

عملگر Sum

این عملگر  مجموع تمامی عناصر یک توالی را باز می‌گرداند.
در کد زیر جمع عناصر یک توالی از نوع int را مشاهده می‌کنید:
int[] nums = { 1, 2, 3 };
int total = nums.Sum();
Console.WriteLine(total);
خروجی کد بالا :
 6

عملگر Sum می‌تواند بر روی توالی‌هایی از نوع <IEnumerable<T و بر روی اعضای عددی آنها اعمال شود.
مثال:
Ingredient[] ingredients =
{
new Ingredient { Name = "Sugar", Calories = 500 },
new Ingredient { Name = "Egg", Calories = 100 },
new Ingredient { Name = "Milk", Calories = 150 },
new Ingredient { Name = "Flour", Calories = 50 },
new Ingredient { Name = "Butter", Calories = 400 }
};
int totalCalories = ingredients.Sum(x => x.Calories);
Console.WriteLine(totalCalories);
خروحی مثال بالا :
 1200

پیاده سازی توسط عبارت‌های جستجو

معادل عملگر Sum، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.

عملگر Average

این عملگر میانگین عناصر توالی‌های عددی را محاسبه می‌کند.
مثال:
int[] nums = { 1, 2, 3 };
var avg = nums.Average();
Console.WriteLine(avg);
خروجی مثلا بالا :
 2

همانطور که در کد بالا مشاهده می‌کنید، نوع متغیر avg صراحتا مشخص نشده و از نوع var استفاده شده است. تابع average بر اساس توالی ورودی، انواع مختلفی از نوع داده‌های عددی را به خروجی ارسال می‌کند (double,float,decimal).
همانند عملگر Sum، عملگر Average می‌تواند بر روی اعضای عددی توالی‌هایی که از نوع<IEnumarable<T هستند، اعمال شود.
مثال:
Ingredient[] ingredients =
{
new Ingredient { Name = "Sugar", Calories = 500 },
new Ingredient { Name = "Egg", Calories = 100 },
new Ingredient { Name = "Milk", Calories = 150 },
new Ingredient { Name = "Flour", Calories = 50 },
new Ingredient { Name = "Butter", Calories = 400 }
};
var avgCalories = ingredients.Average(x => x.Calories);
Console.WriteLine(avgCalories);
خروجی مثال بالا :
 240

پیاده سازی توسط عبارت‌های جستجو

معادل عملگر Average، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.

عملگر Min

عملگر Min کوچکترین عنصر توالی را باز می‌گرداند.
مثال:
int[] nums = { 3, 2, 1 };
var smallest = nums.Min();
Console.WriteLine(smallest);
خروجی مثال بالا:
 1
امضاء دیگر Min می‌تواند یک عبارت پیش بینی را بپذیرد:
مثال:
Ingredient[] ingredients =
{
new Ingredient { Name = "Sugar", Calories = 500 },
new Ingredient { Name = "Egg", Calories = 100 },
new Ingredient { Name = "Milk", Calories = 150 },
new Ingredient { Name = "Flour", Calories = 50 },
new Ingredient { Name = "Butter", Calories = 400 }
};
var smallestCalories = ingredients.Min(x => x.Calories);
Console.WriteLine(smallestCalories);

پیاده سازی توسط عبارت‌های جستجو
معادل عملگر Min ، کلمه کلیدی در عبارت‌های جستجو وجود ندارد.ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.

عملگر Max
عملگر Max بزرگترین عنصر توالی را باز می‌گرداند.
مثال:
int[] nums = { 1 ,3, 2 };
var largest = nums.Max();
Console.WriteLine(largest);
خروجی مثال بالا:
 3
همچون عملگر Min، عملگر Max نیز یک امضاء دارد که می‌توان از طریق آن یک عبارت پیش بینی را مشخص کرد.

پیاده سازی توسط عبارت‌های جستجو

معادل عملگر Max، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.

Aggregate
عملگر‌های تجمعی که  تا اینجا معرفی شدند، تنها یک کار را انجام می‌دادند. اما عملگر Aggregate امکان تعریف یک پرس و جوی تجمیع سفارشی و پیشرفته‌تر را که بر روی توالی ورودی اعمال می‌شود نیز مهیا می‌کند.
عملگر Aggregate  دو نسخه دارد:
 1- نسخه‌ای که اجازه استفاده از یک عدد را به عنوان مقدار Seed، به ما می‌دهد (مقدار آغازین یا Seed).
 2- نسخه‌ای که از عنصر ابتدایی توالی به عنوان مقدار Seed استفاده می‌کند.
هر دو نسخه این عملگر به یک تابع  انباره (accumulator function) جهت نگهداری نتیجه نیاز دارند.
کد زیر شبیه سازی عملگر Sum  توسط عملگر Aggregate می‌باشد:
int[] nums = {1, 2, 3};
var result = nums.Aggregate(0,
(currentElement, runningTotal) => runningTotal + currentElement);
Console.WriteLine(result);
خروجی قطعه کد بالا:
 6
در قطعه کد بالا، نسخه‌ای از عملگر aggregate استفاده شد که مقدار شروع آن با عدد صفر مقدار دهی اولیه شد‌ه‌است.
کد زیر شبیه سازی عملیات فاکتوریل را با در نظر گرفتن عنصر اول توالی، به عنوان مقدار Seed نشان می‌دهد:
 int[] nums = { 1, 2, 3 ,4,5};
var result = nums.Aggregate((runningProduct, nextfactor) => runningProduct * nextfactor);
Console.WriteLine(result);
خروجی کد بالا:
 120

پیاده سازی توسط عبارت‌های جستجو

معادل عملگر Aggregate، کلمه کلیدی در عبارت‌های جستجو وجود ندارد. ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.
نظرات مطالب
EF Code First #9
با سلام؛ من یک کلاس کلاس Person دارم که یک کلاس مشتق شده به نام Teacher داره(ساختار به صورت TPT) و مجددا کلاس Teacher لیستی از کلاسی به نام Expert داره .زمانی که میخوام یک Teacher رو حذف کنم با خطای وجود وابستگی با Expert  مواجه میشم و زمانی که در TypeConfiguration  تنظیمات مربوط به WillCascadeOnDelete   رو برای Teacher و Expert  مقدار True میدم ارتباط این دو جدول همچنان NoAction هست و فکر میکنم این امر بخاطر ارتباط NoAction والد Teacher  یعنی Person باشه که با CaseCade کردن آن مشکل حل شود.سوال اصلی ؟! در ساختار TPT راهی برای CaseCade کردن روابط جداول که براساس ارث بری ساخته میشن وجود داره؟
مطالب
MongoDb در سی شارپ (بخش دهم)

ابتدا بسته زیر را از طریق  nuget نصب نمایید:

dotnet add package MongoDB.Driver


سپس مدل‌های زیر را ایجاد نمایید:

public class BaseModel
{
    public BaseModel()
    {
        CreationDate=DateTime.Now;
    }
    public string Id { get; set; }
    public DateTime CreationDate { get; set; }
    public bool IsRemoved { get; set; }
    public DateTime? ModificationDate { get; set; }

}


 این مدل شامل یک کلاس پایه برای id,CreationDate,ModificationDate,IsRemoved میباشد که بسیار شبیه مدل‌هایی است که عموما در EntityFramework تعریف می‌کنیم.

برای اینکه فیلد Id به صورت objectId ایجاد شود ولی به صورت رشته‌ای استفاده شود ابتدا ویژگی BsonId را در بالای آن تعریف کرده تا به عنوان شناسه یکتا سند شناخته شود و سپس با استفاده از ویژگی BsonRepresentation  اعلام میکنیم که کار تبدیل به رشته و بلعکس آن به صورت خودکار در پشت صحنه صورت بگیرد:

public class BaseModel
    {
        [BsonId]
        [BsonRepresentation((BsonType.ObjectId))]
        public string Id { get; set; }
    }

 البته این حالت برای زمانی مناسب است که ما در استفاده از ویژگی‌ها محدودیتی نداشته باشیم؛ ولی در بسیاری از نرم افزارها که از معماری‌های چند لایه مانند لایه پیازی استفاده میشود استفاده از این خصوصیت‌ها یعنی اعمال کارکرد کتابخانه بالاتر بر روی لایه‌های زیرین که هسته نرم افزار شناخته میشوند که صحیح نبوده و باید توسط لایه‌های بالاتر این تغییرات اعمال شوند که میتواند از طریق کلاس این کار را انجام دهید. به ازای هر مدل که نیاز به تغییرات دارد، یک حالت جدید تعریف شده و در ابتدای برنامه در فایل Program.cs یا قبل از دات نت 6 در Startup.cs صدا زده می‌شوند.

BsonClassMap.RegisterClassMap<BaseModel>(map =>
{
    map.SetIdMember(map.GetMemberMap(x=>x.Id));
    map.GetMemberMap(x => x.Id)
        .SetSerializer(new StringSerializer(BsonType.ObjectId));
});


یک نکته بسیار مهم: کلاس و متد BsonClassMap . RegisterClassMap قادر به اعمال تغییرات بر روی خصوصیت‌های کلاس والد نیستند و آن خصوصیات حتما باید در آن کلاسی که آن را کانفیگ میکنید، تعریف شده باشند؛ یعنی چنین چیزی  که در کد زیر میبینید در زمان اجرا با یک خطا مواجه خواهد شد:

public class Employee : BaseModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
//=================
BsonClassMap.RegisterClassMap<Employee >(map =>
{
    map.SetIdMember(map.GetMemberMap(x=>x.Id));
    map.GetMemberMap(x => x.Id)
        .SetSerializer(new StringSerializer(BsonType.ObjectId));
});


روش استفاده از مونگو در asp.net core  به صورت زیر بسیار متداول میباشد که در قسمت‌های پیشین هم در این مورد نوشته بودیم:

MongoDbContext

  public interface IMongoDbContext
    {
        IMongoCollection<TEntity> GetCollection<TEntity>();
    }

  public class MongoDbContext : IMongoDbContext
    {

        private readonly IMongoClient _client;
        private readonly IMongoDatabase _database;

        public MongoDbContext(string databaseName,string connectionString)
        {
            var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString));
            _client = new MongoClient(settings);
            _database = _client.GetDatabase(databaseName);
        }

        public IMongoCollection<TEntity> GetCollection<TEntity>()
        {
            return _database.GetCollection<TEntity>(typeof(TEntity).Name.ToLower() + "s");
        }
    }

سپس از طریق کد زیر IMongoDbContext را به سیستم تزریق وابستگی‌ها معرفی میکنیم. الگوی استفاده شده‌ی در اینجا بر خلاف نسخه‌های sql که عموما به صورت AddScoped تعریف میشدند، در اینجا به صورت AddSingleton تعریف کردیم و نحوه پیاده سازی آن را نیز در طرف سمت راست به صورت صریح اعلام کردیم:

public static class MongoDbContextService
{
    public static void AddMongoDbContext(this IServiceCollection services,string databaseName,string connectionString)
    {
        services.AddSingleton<IMongoDbContext>(serviceProvider => new MongoDbContext(databaseName, connectionString));
    }
}

//===============
Program.cs

builder.Services.AddMongoDbContext("bookstore", "mongodb://localhost:27017");


پیاده سازی SoftDelete در مونگو

در مونگو چیزی تحت عنوان Global Query Filter نداریم که تمام کوئری هایی که به سمت دیتابیس ارسال میشوند، توسط کانتکس اطلاح شوند؛ بدین جهت برای پیاده سازی این خصوصیت میتوان اینترفیسی با نام <IRepository<T را به شکل زیر طراحی نماییم:

public interface IRepository<T> where T : BaseModel
{

    IMongoCollection<T> GetCollection();
    IMongoQueryable<T> GetFilteredCollection();
}

public class Repository<T> : IRepository<T> where T:BaseModel
{
    private IMongoDbContext _mongoDbContext;

    public Repository(IMongoDbContext mongoDbContext)
    {
        _mongoDbContext = mongoDbContext;
    }

    public IMongoCollection<T> GetCollection()
    {
        return _mongoDbContext.GetCollection<T>();
    }
    
    public IMongoQueryable<T> GetFilteredCollection()
    {
        var query= _mongoDbContext.GetCollection<T>().AsQueryable();
        
        //================= Global Query Filters ====================
        
        //Filter 1
        query=query.Where(x => x.RemovedAt.HasValue == false);
        
        //==============================================================
        
        return query;
    }
}

این کلاس یا اینترفیس شامل دو متد هستند که کلاس جنریک آنها باید از BaseModel ارث بری کرده باشد و اولین متد، تنها یک کالکشن بدون هیچگونه فیلتری است که میتواند نقش متد IgnoreQueryFilters  را بازی کند و دیگری GetFilteredCollection است که در این متد ابتدا کالکشنی دریافت شده و سپس آن را به حالت کوئری تغییر داده و فیلترهای مورد نظر، مانند حذف منطقی را پیاده سازی میکنیم:

public interface IRepository<T> where T : BaseModel
{

    IMongoCollection<T> GetCollection();
    IMongoQueryable<T> GetFilteredCollection();
}

public class Repository<T> : IRepository<T> where T:BaseModel
{
    private IMongoDbContext _mongoDbContext;

    public Repository(IMongoDbContext mongoDbContext)
    {
        _mongoDbContext = mongoDbContext;
    }

    public IMongoCollection<T> GetCollection()
    {
        return _mongoDbContext.GetCollection<T>();
    }
    
    public IMongoQueryable<T> GetFilteredCollection()
    {
        var query= _mongoDbContext.GetCollection<T>().AsQueryable();
        
        //================= Global Query Filters ====================
        
        //Filter 1
        query=query.Where(x => x.RemovedAt.HasValue == false);
        
        //==============================================================
        
        return query;
    }
}


اصلاح تاریخ ویرایش در مدل

در EF به لطف dbset و همچنین ChangeTracking امکان شناسایی حالت‌ها وجود دارد و میتوانید در متدی مانند saveChanges مقدار تاریخ ویرایش را تنظیم نمود. برای مدل‌های منگو چنین چیزی وجود ندارد و به همین دلیل چند روش زیر پیشنهاد میگردد:

یک. استفاده از اینترفیس INotifyPropertyChanged یا جهت حذف کدهای تکراری نیز از الگوی AOP بهره بگیرید.

دو. استفاده از یک <Repository<T همانند بالا که شامل متدهای داخلی Update و Delete هستند که در آنجا میتوانید این مقادیر را به صورت مستقیم تغییر دهید.

مطالب
معرفی DNTProfiler
پیشاپیش فرا رسیدن سال نو را به تمام همراهان گرامی سایت net tips. تبریک عرض می‌کنم. به امید سالی پر از سلامتی و رونق، به همراه اشتیاق روز افزون جستجوگری و کشف زوایای پنهان دنیای برنامه نویسی!
هد‌یه‌ی نوروزی سایت net tips. پروژه‌ی پروفایلر سورس بازی است که با EF 6.x و همچنین NHibernate 4.x سازگار است. این پروژه از دو قسمت کلاینت و سرور تشکیل می‌شود.


نصب کلاینت EF برنامه‌ی DNTProfiler

تفاوتی نمی‌کند که برنامه‌ی شما وبی است یا ویندوزی؛ برای هر دو حالت ابتدا دستور ذیل را در کنسول پاورشل نیوگت اجرا کنید:
 PM> Install-Package DNTProfiler.EntityFramework.Core
با اینکار interceptor‌های پروفایلر نصب می‌شوند. برای فعال سازی آن نیاز است چند سطر ذیل را به فایل app.config/web.config پروژه‌ی خود اضافه کنید:
<configuration>
  <entityFramework>
    <interceptors>
      <interceptor type="DNTProfiler.EntityFramework.Core.DatabaseLogger, DNTProfiler.EntityFramework.Core">
        <parameters>
          <parameter value="http://localhost:8080" />
          <parameter value="|DataDirectory|\ErrorsLog.Log" />
        </parameters>
      </interceptor>
    </interceptors>
  </entityFramework>
</configuration>


دریافت و راه اندازی برنامه‌ی DNTProfiler

آخرین نگارش برنامه‌ی DNTProfiler را از برگه‌ی releases مخزن کد آن می‌توانید دریافت کنید:
https://github.com/VahidN/DNTProfiler/releases

این برنامه برای دات نت 4 نوشته شده‌است. بنابراین اگر هنوز از ویندوز XP استفاده می‌کنید، امکان کار کردن با آن‌را خواهید داشت.
البته بسته‌ی نیوگت DNTProfiler.EntityFramework.Core آن برای دات نت 4 و 4.5 تهیه شده‌است و به صورت خودکار بر اساس ساختار پروژه‌ی شما، یکی از آن‌ها نصب خواهد شد.

تا اینجا کار راه اندازی این برنامه به پایان می‌رسد. برای استفاده‌ی از آن باید ابتدا برنامه‌ی DNTProfiler را اجرا کنید. این برنامه به پیام‌های رسیده‌ی از برنامه‌ی اصلی شما (ارسال شده توسط DNTProfiler.EntityFramework.Core) گوش فرا می‌دهد و سپس شروع به آنالیز آن‌ها خواهد کرد. ساختار این تبادل اطلاعات هم بر اساس تهیه‌ی یک ASP.NET Self host Web API است.


این برنامه به صورت ماژولار تهیه شده‌است و تمام آنالیز کننده‌های آن در حقیقت یک پلاگین هستند (در حال حاضر دارای 32 پلاگین است). این پلاگین‌ها در گروه‌های Alerts (برای مثال یافتن جوین‌های تکراری و یک سری موارد بهینه سازی سرعت)، Loggers (طبقه بندی خام اطلاعات رسیده)، Visualizers (نمایش بصری اطلاعات رسیده) قرار می‌گیرند.


نظرات، پیشنهادات و همکاری


لطفا برای طرح سؤالات و ارائه‌ی پیشنهادات خود در زمینه‌ی این پروژه، به قسمت اختصاصی آن در سایت مراجعه نمائید:
https://www.dntips.ir/projects/details/21
مطالب
مروری بر طراحی Schema less بانک اطلاعاتی SisoDb
اس کیوال سرور، از سال 2005 به بعد، به صورت توکار امکان تعریف و ذخیره سازی اطلاعات schema less و یا schema free را به کمک فیلدهایی از نوع XML ارائه داده است؛ به همراه یکپارچگی آن با زبان XQuery برای تهیه کوئری‌های سریع سمت سرور. در فیلدهای XML می‌توان اطلاعات انواع و اقسام اشیاء را بدون اینکه نیازی به تعریف تک تک فیلدهای مورد نیاز، در بانک اطلاعاتی وجود داشته باشد، ذخیره کرد. یک نمونه از کاربرد چنین امکانی، نوشتن برنامه‌های «فرم ساز» است. برنامه‌هایی که کاربران آن می‌توانند فیلد اضافه و کم کرده و نهایتا اطلاعات را ذخیره و از آن‌ها کوئری بگیرند.
خوب، این فیلد کمتر بحث شده XML، فقط در اس کیوال سرور و نگارش‌های اخیر آن وجود دارد. اگر نیاز به کار با بانک‌های اطلاعاتی سبک‌تری وجود داشت چطور؟ یک راه حل عمومی برای این مساله مراجعه به روش‌های NoSQL است. یعنی بطور کلی بانک‌های اطلاعاتی رابطه‌ای کنار گذاشته شده و به یک سکوی کاری دیگر سوئیچ کرد. در این بین، SisoDb راه حل میانه‌ای را ارائه داده است. با کمک SisoDb می‌توان اطلاعات را به صورت schema less و بدون نیاز به تعریف فیلدهای متناظر آن‌ها، در انواع و اقسام بانک‌های اطلاعاتی SQL Server با فرمت JSON ذخیره و بازیابی کرد. این انواع و اقسام، شامل SQL Server CE نیز می‌شود.


دریافت و نصب SisoDb

دریافت و نصب SisoDb بسیار ساده است. به کمک package manager و امکانات NuGet، کلمه Sisodb را جستجو کنید. در بین مداخل ظاهر شده، پروایدر مورد علاقه خود را انتخاب و نصب نمائید. برای مثال اگر قصد دارید با SQL Server CE کار کنید، SisoDb.SqlCe4 را انتخاب و یا اگر SQL Server 2008 مدنظر شما است، SisoDb.Sql2008 را انتخاب و نصب نمائید.


ثبت و بازیابی اطلاعات به کمک SisoDb

کار با SisoDb بسیار روان است. نیازی به تعاریف نگاشت‌ها و ORM خاصی نیست. یک مثال مقدماتی آن‌را در ادامه ملاحظه می‌کنید:
using SisoDb.Sql2008;

namespace SisoDbTests
{
    public class Customer 
    {
        public int Id { get; set; } 
        public int CustomerNo { get; set; }
        public string Name { get; set; } 
    }

    class Program
    {
        static void Main(string[] args)
        {
            /*var cnInfo = new SqlCe4ConnectionInfo(@"Data source=sisodb2013.sdf;");
            var db = new SqlCe4DbFactory().CreateDatabase(cnInfo);
            db.EnsureNewDatabase();*/

            var cnInfo = new Sql2008ConnectionInfo(@"Data Source=(local);Initial Catalog=sisodb2013;Integrated Security = true");            
            var db = new Sql2008DbFactory().CreateDatabase(cnInfo);            
            db.EnsureNewDatabase();

            var customer = new Customer 
            {
                CustomerNo = 20,
                Name = "Vahid" 
            };
            db.UseOnceTo().Insert(customer);

            using (var session = db.BeginSession()) 
            {
                var info = session.Query<Customer>().Where(c => c.CustomerNo == 20).FirstOrDefault();

                var info2 = session.Query<Customer>().Where(c => c.CustomerNo == 20 && c.Name=="Vahid").FirstOrDefault();
            }
        }
    }
}
در این مثال، ابتدا اتصال به بانک اطلاعاتی برقرار شده و سپس بانک اطلاعاتی جدید تهیه می‌شود. سپس یک وهله از شیء مشتری ایجاد و ذخیره می‌گردد. در ادامه دو کوئری بر روی بانک اطلاعاتی انجام شده است.


ساختار داخلی SisoDb

SisoDb به ازای هر کلاس، حداقل 9 جدول را ایجاد می‌کند. در ادامه نحوه ذخیره سازی شیء مشتری ایجاد شده و مقادیر خواص آن‌را نیز مشاهده می‌نمائید:


ذخیره سازی جداگانه خواص عددی

ذخیره سازی جداگانه خواص رشته‌ای

ذخیره سازی کلی شیء مشتری با فرمت JSON به صورت یک رشته


همانطور که ملاحظه می‌کنید، یک جدول کلی SisoDbIdentities ایجاد شده است که اطلاعات نام اشیاء را در خود نگهداری می‌کند. سپس اطلاعات خواص اشیاء یکبار به صورت JSON ذخیره می‌شوند؛ با تمام اطلاعات تو در توی ذخیره شده در آن‌ها و همچنین یکبار هم هر خاصیت را به صورت یک رکورد جداگانه، بر اساس نوع کلی آن‌ها، در جداول رشته‌ای، عددی و امثال آن ذخیره می‌کند.
شاید بپرسید که چرا به همان فیلد رشته‌ای JSON اکتفاء نشده است؟ از این جهت که پردازشگر سمت بانک اطلاعاتی آن همانند فیلدهای XML در SQL Server و نگارش‌های مختلف آن وجود ندارد (برای مثال به کمک زبان T-SQL می‌توان از زبان XQuery در خود بانک اطلاعاتی، بدون نیاز به واکشی کل اطلاعات در سمت کلاینت، به صورت یکپارچه استفاده کرد). به همین جهت برای کوئری گرفتن و یا تهیه ایندکس، نیاز است این موارد جداگانه ذخیره شوند.
به این ترتیب زمانیکه کوئری تهیه می‌شود، برای مثال:
 var info = session.Query<Customer>().Where(c => c.CustomerNo == 20).FirstOrDefault();
به کوئری زیر ترجمه می‌گردد:
SELECT DISTINCT TOP(1) (s.[StructureId]),
                           s.[Json]
                    FROM   [CustomerStructure] s
                           LEFT JOIN [CustomerIntegers] mem0
                                ON  mem0.[StructureId] = s.[StructureId]
                                AND mem0.[MemberPath] = 'CustomerNo'
                    WHERE  (mem0.[Value] = 20);
و یا کوئری ذیل:
var info2 = session.Query<Customer>().Where(c => c.CustomerNo == 20 && c.Name=="Vahid").FirstOrDefault();
معادل زیر را خواهد داشت:
SELECT DISTINCT TOP(1) (s.[StructureId]),
                           s.[Json]
                    FROM   [CustomerStructure] s
                           LEFT JOIN [CustomerIntegers] mem0
                                ON  mem0.[StructureId] = s.[StructureId]
                                AND mem0.[MemberPath] = 'CustomerNo'
                           LEFT JOIN [CustomerStrings] mem1
                                ON  mem1.[StructureId] = s.[StructureId]
                                AND mem1.[MemberPath] = 'Name'
                    WHERE  ((mem0.[Value] = 20) AND (mem1.[Value] = 'Vahid'));
در هر دو حالت از جداول کمکی تعریف شده برای تهیه کوئری استفاده کرده و نهایتا فیلد JSON اصلی را برای نگاشت نهایی به اشیاء تعریف شده در برنامه بازگشت می‌دهد.

در کل این هم یک روش تفکر و طراحی Schema less است که با بسیاری از بانک‌های اطلاعاتی موجود سازگاری دارد.
برای مشاهده اطلاعات بیشتری در مورد جزئیات این روش می‌توان به Wiki آن مراجعه کرد.
مطالب
مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first
در مورد طراحی Self Referencing Entities پیشتر مطلبی را در این سایت مطالعه کرده‌اید .
یک مثال دیگر آن می‌تواند نظرات چند سطحی در یک سایت باشند. نحوه تعریف آن با مطالبی که در قسمت هشتم عنوان شود تفاوتی نمی‌کند؛ اما ... زمانیکه نوبت به نمایش آن فرا می‌رسد، چند نکته اضافی را باید درنظر گرفت. ابتدا مثال کامل زیر را در نظر بگیرید:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;

namespace EFGeneral
{
    public class BlogComment
    {
        public int Id { set; get; }

        [MaxLength]
        public string Body { set; get; }

        public virtual BlogComment Reply { set; get; }
        public int? ReplyId { get; set; }
        public ICollection<BlogComment> Children { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<BlogComment> BlogComments { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // Self Referencing Entity
            modelBuilder.Entity<BlogComment>()
                        .HasOptional(x => x.Reply)
                        .WithMany(x => x.Children)
                        .HasForeignKey(x => x.ReplyId)
                        .WillCascadeOnDelete(false);

            base.OnModelCreating(modelBuilder);
        }
    }

    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            var comment1 = new BlogComment { Body = "نظر من این است که" };
            var comment12 = new BlogComment { Body = "پاسخی به نظر اول", Reply = comment1 };
            var comment121 = new BlogComment { Body = "پاسخی به پاسخ به نظر اول", Reply = comment12 };

            context.BlogComments.Add(comment121);
            base.Seed(context);
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());

            using (var ctx = new MyContext())
            {
                var list = ctx.BlogComments
                          //.where ...
                          .ToList() // fills the childs list too
                          .Where(x => x.Reply == null) // for TreeViewHelper                        
                          .ToList();

                if (list.Any())
                {

                }
            }
        }
    }
}

در مثال فوق کلاس نظرات به صورت خود ارجاع دهنده (خاصیت Reply به همین کلاس اشاره می‌کند) تعریف شده است تا کاربران بتوانند تا هر چند سطح لازم، به یک نظر خاص، پاسخ دهند.
در اینجا یک چنین جدولی با اطلاعاتی که ملاحظه می‌کنید تشکیل خواهند شد:


یک نظر ارائه شده و سپس دو نظر تو در توی دیگر برای این نظر ثبت شده است.

اولین نکته اضافه‌‌تری که نسبت به قسمت هشتم قابل ملاحظه است، تعریف خاصیت جدید Children به نحو زیر می‌باشد:
    public class BlogComment
    {
        // ... 
        public ICollection<BlogComment> Children { get; set; }
    }
این خاصیت تاثیری در نحوه تشکیل جدول ندارد. علت تعریف آن به توانمندی EF در پرکردن خودکار آن بر می‌گردد.
اگر به کوئری نوشته شده در متد RunTests دقت کنید، ابتدا یک ToList نوشته شده است. این مورد سبب می‌شود که تمام رکوردهای مرتبط دریافت شوند. مثلا در اینجا سه رکورد دریافت می‌شود. سپس برای اینکه حالت درختی آن حفظ شود، در مرحله بعد ریشه‌ها فیلتر می‌شوند (مواردی که reply آن‌ها null است). سپس این مورد تبدیل به list خواهد شد. اینبار اگر خروجی را بررسی کنیم، به ظاهر فقط یک رکورد است اما ... به نحو زیبایی توسط EF به شکل یک ساختار درختی، بدون نیاز به کدنویسی خاصی، منظم شده است:


سؤال:
برای نمایش این اطلاعات درختی و تو در تو در یک برنامه وب چکار باید کرد؟
تا اینجا که توانستیم اطلاعات را به نحو صحیحی توسط EF مرتب کنیم، برای نمایش آن‌ها در یک برنامه ASP.NET MVC می‌توان از یک TreeViewHelper سورس باز استفاده کرد.
البته کد آن در اصل برای استفاده از EF Code first طراحی نشده و نیاز به اندکی تغییر به نحو زیر دارد تا با EF هماهنگ شود (متد ToList و Count موجود در سورس اصلی آن باید به نحو زیر حذف و اصلاح شوند):
private void AppendChildren(TagBuilder parentTag, T parentItem, Func<T, IEnumerable<T>> childrenProperty)
        {
            var children = childrenProperty(parentItem);
            if (children == null || !children.Any())
            {
                return;
            }
//...

 
مطالب
پیاده سازی الگوی طراحی Memento

Memento یک الگوی طراحی مفید و ساده است که برای ذخیره و بازیابی state یک object استفاده می‌شود. در بعضی از مقالات از آن به عنوان snapshot نیز یاد شده است! اگر با git  کار کرده باشید، این مفهوم را می‌توان در git بسیار یافت؛ هر commit به عنوان یک snapshot میباشد که میتوان به صورت مکرر آن را undo کرد و یا مثال خیلی ساده‌تر میتوان به ctrl+z در سیستم عامل اشاره کرد.

به مثال زیر توجه کنید:

Int temp;
Int a=1;
temp=a;
a=2;
.
.
a=temp;

شما قطعا در برنامه نویسی با کد بالا زیاد برخورد داشته‌اید و آن‌را به صورت مکرر انجام داده‌اید. کد بالا را در قالب یک object بیان میکنیم. به مثال زیر توجه کنید:

int main()
{
  MyClass One = new MyClass();
  MyClass Temp = new MyClass();
  // Set an initial value.
  One.Value = 10;
  One.Name = "Ten";
  // Save the state of the value.
  Temp.Value = One.Value;
  Temp.Name = One.Name;
  // Change the value.
  One.Value = 99;
  One.Name = "Ninety Nine";
  // Undo and restore the state.
  One.Value = Temp.Value;
  One.Name = Temp.Name;
}

در کد بالا با استفاده از یک temp، شیء مورد نظر را ذخیره کرده و در آخر مجدد داده‌ها را درون شیء، restore  میکنیم.


 از مشکلات کد بالا میتوان گفت :

۱- برای هر object باید یک شیء temp ایجاد کنیم.

۲- ممکن است بخواهیم که حالات یک object را بر روی هارد ذخیره کنیم. با روش فوق کدها خیلی پیچیده‌تر خواهند شد.

۳- نوشتن کد به این سبک برای پروژه‌های بزرگ، پیچیده و مدیریت آن سخت‌تر می‌شود.


پیاده سازی memento

ما این مثال را در قالب یک پروژه NET Core  onsole. ایجاد میکنیم. برای این کار یک پوشه‌ی جدید را ایجاد و درون ترمینال دستور زیر را وارد کنید:

dotnet new console

روش‌های زیادی برای پیاده سازی memento وجود دارند. برای پیاده سازی memento ابتدا یک abstract class را به شکل زیر ایجاد میکنیم: 

abstract class MementoBase
{
  protected Guid mementoKey = Guid.NewGuid();
  abstract public void SaveMemento(Memento memento);
  abstract public void RestoreMemento(Memento memento);
}

اگر به کلاس بالا دقت کنید، این کلاس قرار است parent کلاس‌های دیگری باشد که داری دو متد SaveMemento و RestoreMemento برای ذخیره و بازیابی و همچنین یک Guid برای نگهداری state‌های مختلف میباشد.

ورودی متدها از نوع memento میباشد. پس کلاس memento را به شکل زیر ایجاد می‌کنیم:

class Memento
{
    private Dictionary<Guid, object> stateList = new Dictionary<Guid, object>();
    public object GetState(Guid key)
    {
        return stateList[key];
    }
    public void SetState(Guid key, object newState)
    {
        stateList[key] = newState;
    }
    public Memento()
    {
    }
}

در کد بالا با یک Dictionary می‌توان هر object را با کلیدش ذخیره کنیم. توجه کنید که value دیکشنری از نوع object میباشد و چون object پدر تمام object‌های دیگر است پس می‌توانیم هر نوع داده‌ای را در آن ذخیره کنیم. تا اینجا، Memento پیاده سازی شده است. میتوان این کار را با جنریک‌ها نیز پیاده سازی کرد.

در ادامه می‌خواهیم یک کلاس بسازیم و حالت‌های مختلف را در آن بررسی کنیم. کلاس زیر را ایجاد کنید:

class ConcreteOriginator : MementoBase
{
  private int value = 0;
  public ConcreteOriginator(int newValue)
  {
    SetData(newValue);
  }
  public void SetData(int newValue)
  {
    value = newValue;
  }
  public void Speak()
  {
    Console.WriteLine("My value is " + value.ToString());
  }
  public override void SaveMemento(Memento memento)
  {
    memento.SetState(mementoKey, value);
  }
  public override void RestoreMemento(Memento memento)
  {
    int restoredValue = (int)memento.GetState(mementoKey);
    SetData(restoredValue);
  }
}

کلاس ConcreteOriginator از کلاس MementoBase ارث بری کرده و دو متد RestoreMemento و SaveMemento را پیاده سازی میکند و همچنین دارای یک مشخصه value می‌باشد. برای خروجی گرفتن، متد main را به صورت زیر پیاده سازی می‌کنیم:

static void Main(string[] args)
{
  Memento memento = new Memento();
  // Create an originator, which will hold our state data.
  ConcreteOriginator myOriginator = new ConcreteOriginator("Hello World!", StateType.ONE);
  ConcreteOriginator anotherOriginator = new ConcreteOriginator("Hola!", StateType.ONE);
  ConcreteOriginator2 thirdOriginator = new ConcreteOriginator2(7);
  // Set some state data.
  myOriginator.Speak();
  anotherOriginator.Speak();
  thirdOriginator.Speak();
  // Save the states into our memento.
  myOriginator.SaveMemento(memento);
  anotherOriginator.SaveMemento(memento);
  thirdOriginator.SaveMemento(memento);
  // Now change our originators' states.
  myOriginator.SetData("Goodbye!", StateType.TWO);
  anotherOriginator.SetData("Adios!", StateType.TWO);
  thirdOriginator.SetData(99);
  myOriginator.Speak();
  anotherOriginator.Speak();
  thirdOriginator.Speak();
  // Restore our originator's state.
  myOriginator.RestoreMemento(memento);
  anotherOriginator.RestoreMemento(memento);
  thirdOriginator.RestoreMemento(memento);
  myOriginator.Speak();
  anotherOriginator.Speak();
  thirdOriginator.Speak();
  Console.ReadKey();
}
تا خط ۱۲، مراحل عادی کد نویسی را پیش رفته‌ایم. در خطوط ۱۳ تا ۱۵، داده را در Memento ذخیره میکنیم. در خطوط ۱۷ تا ۱۹، داده‌های اشیاء را با استفاده از متد SetData عوض میکنیم. در خطوط ۲۰ تا ۲۲ با متد Speak، مقدار value را نمایش میدهیم و در خطوط ۲۴ تا ۲۶، داده‌ها را Restore میکنیم و در آخر دوباره مقدار value را نمایش میدهیم.
برنامه را اجرا کنید .خروجی به شکل زیر خواهد بود:
Hello World! I'm in state ONE
Hola! I'm in state ONE
My value is 7
Goodbye! I'm in state TWO
Adios! I'm in state TWO
My value is 99
Hello World! I'm in state ONE
Hola! I'm in state ONE
My value is 7
نظرات مطالب
معرفی System.Text.Json در NET Core 3.0.
با سلام؛ من با reactjs وقتی فرم را به سمت سرور ارسال میکنم، اگر input با نوع number را در قسمت HTML داشته باشم، آن را به صورت string میفرستد و من مجبورم در سمت کلاینت اون رو تبدیل به int کنم وبعد بفرستم و میخواستم با همین روشی که گفتید، یک کلاس بنویسم که اون رو بالای فیلدم تعریف کنم و نیاز به اون تبدیله نباشه ...
   public class IntConverter : JsonConverter<int> {
        public override int Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
            var value = reader.GetString ();
            if (int.TryParse (value, out _))
                return int.Parse (value);
            throw new NotSupportedException ($"`{value}` can't be converted to `int`.");
        }

        public override void Write (Utf8JsonWriter writer, int value, JsonSerializerOptions options) {
            writer.WriteNumberValue (value);
        }
    }