مطالب
کار با اسناد در RavenDb 4، ثبت و ویرایش
اگر تا بحال با بانک‌های NoSql کار کرده و لذت برده‌اید، به شما پیشنهاد میکنم حتما RavenDb را هم امتحان کنید، تا لذت استفاده از NoSql را چندین برابر حس کنید! RavenDb یک بانک اطلاعاتی NoSql از نوع DocumentStore است که به‌صورت متن باز توسعه داده می‌شود و مخزن کد آن در Github موجود است. از ویژگی‌های بارز RavenDb نسبت به سایر DocumentStoreها، Transactional بودن میباشد و در نسخه‌ی 4 بصورت کامل از Net Core. پشتیبانی میکند. برای آشنایی بیشتر با NoSql میتوانید از مقالات موجود در گروه NoSql استفاده کنید و برای آشنایی با RavenDb از دوره ای که در سایت وجود دارد استفاده نمایید(دوره مربوط به نسخه‌ی 3.5 می‌باشد).
از بارز‌ترین ویژگی‌های NoSqlها توانایی آن‌ها در ذخیره‌ی اطلاعات، بدون توجه به اسکیمای آن هاست؛ پس هر نوع مدلی که ما برای ذخیره اطلاعات نرم افزار تعریف میکنیم، فقط برای درک بهتر ما هست و بس!

با این مقدمه مدل‌های زیر را برای شروع کار داریم:
Public Class User
{
        public string Id { get; set; }
        public string PhoneNumber { get; set; }
        public Dictionary<string, App> Apps { get; set; }
}
public class App
{
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string UserName { get; set; }
        public List<string> Roles { get; set; }
        public List<String> Messages { get; set; }
        public String AdressId { get; set; }
        public bool IsActive { get; set; } = true;
        [JsonIgnore]
        public string DisplayName => $"{FirstName} {LastName}";
}

در این مدل، هر کاربر با یک شماره تماس میتواند در چندین برنامه ثبت شود و اطلاعات او در هر برنامه هم میتواند متفاوت باشد.
برای اتصال به RavenDb، به DocumentStore و برای ارسال درخواست‌ها به سمت سرور، به DocumentSession نیاز داریم. نمونه سازی DocumentStore هزینه‌بر بوده و باید در طی اجرای نرم افزار فقط یکبار(Singleton) نمونه سازی شود. DocumentSession بسیار سبک بوده و باید به ازای هر درخواست که به سمت سرور RavenDb ارسال میگردد یک بار نمونه سازی شده و بعد از آن نابود شود. پس برای استفاده در ASP.NET Core به این پیاده سازی در Startup میرسیم:
services.AddSingleton<IDocumentStore>(serviceProvider =>
{
      var store = new DocumentStore()
      {
            Urls = new[] { "http://192.168.1.10:8080" },
            Database = "AccountingSystem",
      }.Initialize();
      return store;
});

services.AddScoped<IAsyncDocumentSession>(serviceProvider =>
{
      var store = serviceProvider.GetRequiredService<IDocumentStore>();
      return store.OpenAsyncSession();
});

حال در تمام بخش‌های نرم افزار می‌توانیم DocumentSession استفاده کنیم.
برای ذخیره سازی مدل در RavenDb از کد زیر استفاده می‌کنیم:
var user = new User
{
      PhoneNumber = user.PhoneNumber
};
user.Apps.Add(appCode, new ActiveApp
{
       FirstName = "عبدالصالح",
       LastName = "کاشانی",
       UserName = abdossaleh,
       IsActive = true,
       RolesId = new List<string>{"Admin"}
});
await _documentSession.StoreAsync(user);
await _documentSession.SaveChangesAsync()

این ساده‌ترین کاری هست که میتوانیم انجام دهیم. بلافاصله بعد از استفاده از متد StoreAsync و بدون رفت و برگشتی به سرور، ویژگی Id برای user مقداردهی می‌شود و توضیح این رفتار هم پیشتر گفته شده است. با فراخوانی متد SaveChangesAsync تغییرات اتفاق افتاده در DocumentSession برای ذخیره سازی به سمت سرور ارسال می‌شوند. بله! الگوی Repository و UnitOfWork.
حال برای دریافت همین مدل، در صورتیکه Id آن را در اختیار داشته باشیم، از متد LoadAsync استفاده میکنیم.
var user = await _documentSession.LoadAsync<User>("Users/131-A");
با لود شدن کاربر، این Entity تحت نظر قرار میگیرد و اگر تغییری در هر کدام از ویژگی‌های آن صورت گیرد و متد SaveChangesAsync فراخوانی شود، کل مدل برای به‌روزرسانی به سمت سرور ارسال میشود. کل مدل و این به معنای بار اضافی در شبکه هست. البته در مدل‌های کوچک بهتر است که همین کار را انجام دهیم. ولی در اینجا به عمد مدلی را انتخاب کرده‌ایم که اطلاعات زیادی را در خود نگهداری میکند و ارسال تمام آن به ازای یک تغییر کوچک به صرفه نیست! خوشبختانه RavenDb برای حل این مشکل امکانات جالبی را در اختیار ما قرار داده که در ادامه آن‌ها را بررسی می‌کنیم.

Patching
به معنای تغییر دادن قسمتی از سند که شامل تغییر مقادیر، اضافه یا حذف یک ویژگی، ایجاد تغییرات در لیست و ... می‌باشد. با استفاده از متدهای Patch سند، میتوانیم بدون نیاز به لود سند و تغییر و ذخیره آن، قسمتی از سند را ویرایش کنیم. عملیات Patch، سمت سرور اجرا می‌شوند. برای مثال برای تغییر شماره تماس، از متد زیر استفاده می‌کنیم:
_documentSession.Advanced.Patch<User, string>("Users/131-A",
      u => u.PhoneNumber
      , "09131110000");
که مدلی را که میخواهیم تغییر دهیم، به همراه نوع ویژگی مورد نظر برای تغییر، دریافت میکند و بعد از آن، به ترتیب Id سند مورد نظر، ویژگی مورد نظر برای اعمال تغییر و مقدار را میگیرد و با فراخوانی SaveChangesAsync این تغییرات اعمال می‌شوند. نکته‌ای که باید توجه کنید این است که اگر مدلی را لود کردید و در فیلدهای آن تغییری ایجاد نموده‌اید، دیگر نمیتوانید از Patch یا Defer (توضیح داده میشود) استفاده کنید. به عبارت دیگر در هر درخواست یا باید از سیستم Tracking خود RavenDb استفاده کنید و یا از Patching!
برای اضافه کردن یک آیتم به لیست،  از Patch بصورت زیر استفاده میکنیم:
_documentSession.Advanced.Patch<User, string>("Users/131-A",
      u => u.Apps["59"].RolesId
      , r => r.Add("Admin"));

برای اضافه کردن مقداری به یک مقدار عددی در RavenDb، از متد Increment بصورت زیر استفاده میکنیم:
 _documentSession.Advanced.Increment<User, int>("Users/131-A", x => x.TestProp, 10);
متد Patch برای کارهای ساده‌ی اینچنین بسیار کاربردی می‌باشد؛ ولی برای کارهای پیشرفته‌تر کارآیی ندارد. به همین دلیل متد Defer در کنار آن معرفی شده‌است که فوق العاده کاربردی ولی اصطلاحا non-typed است و تحت نظارت Compiler نیست. برای مثال اضافه کردن یک مقدار به Dictionary ما، از طریق Patch امکان ندارد. اما اینکار با استفاده از متد Defer و کدهای JavaScript به‌سادگی زیر می‌باشد:
_documentSession.Advanced.Defer(new PatchCommandData("Users/131-A", null,
                              new PatchRequest()
                              {
                                    Script = $@"this.Apps[args.appCode] = args.app",
                                    Values =
                                         {
                                              {"appCode", appCode},
                                              {"app", new ActiveApp
                                                   {
                                                        FirstName = "عبدالصالح",
                                                        LastName = "کاشانی",
                                                        UserName = abdossaleh,
                                                        RolesId = new List<string>{"Admin"}
                                                    }
                                              }
                                          }
                              }, null));
متد Defer شناسه‌ی سند مورد نظر را گرفته و اسکریپت ما را با آرگومان‌های ارسالی، بر روی سند اعمال میکند. Defer دسترسی کاملی را به ما برای تغییر در سند میدهد. برای نمونه میتوانیم آیتمی را به مکان خاصی از لیست اضافه کنیم (برای کوتاه‌تر شدن اسکریپت‌ها فقط بخش Script و Value را ذکر میکنم):
Script = "this.Apps[args.app].Roles.splice(args.index,0,args.role)",
Values =
        {
            {
                "index": 1 // مکانی که میخواهیم عملیات انجام شود
                "app", 59
                "role", "User"
            }
        }
this در اینجا به سند جاری اشاره میکند.
از همین روش میتوانیم برای ویرایش کردن یک آیتم هم استفاده کنیم. برای مثال اگر مقدار 0 را در متد splice به یک تغییر دهیم، عملیات ویرایش صورت می‌گیرد (در واقع حذف آیتم در مکان index و درج آیتم جدید در همان مکان):
splice(args.index,1,args.role)
و برای حذف تمام آیتم‌های لیست جز یک آیتم خاص، از کد زیر استفاده میکنیم:
Script = @"this.Roles= this.Apps[args.app].Roles.filter(role=> role != args.role);",
        Values =
        {
            {"app", 59}
            {"role", "User"}
        }
همانطور که مشاهده می‌کنید به راحتی می‌توانیم کدهای جاوا اسکریپتی خود را در Defer استفاده کنیم. اما این قدرت زیاد، امکان اشتباه در کدهای ما را زیاد میکند چرا که تحت کنترل کامپایلر نیست.
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطه‌ی One-to-Many
در جهت تکمیل بحث بارگذاری اطلاعات وابسته: اضافه شدن Lazy Loading به نگارش 2.1

برخلاف نگارش‌های پیشین EF، اینبار Lazy loading به صورت پیش‌فرض فعال نیست که در بسیاری از موارد یک مزیت مهم، در جهت بهبود کارآیی برنامه به حساب می‌آید؛ چون پیشتر مدام می‌بایستی توسط ابزارهای profiler، برنامه را بررسی می‌کردیم تا از وجود مشکلی به نام select n+1 مطلع می‌شدیم (lazy loading اشتباه، در جائی که نیازی به آن نبوده و رفت و برگشت بیش از اندازه‌ا‌ی را به بانک اطلاعاتی سبب شده‌است).
برای فعالسازی lazy loading در EF Core 2.1 (اگر واقعا به آن نیاز دارید البته) دو روش وجود دارد:
الف) فعالسازی Lazy loading توسط Proxyها
در این حالت ابتدا نیاز است بسته‌ی نیوگت Microsoft.EntityFrameworkCore.Proxies را نصب کنید. سپس در متد OnConfiguring مربوط به Context برنامه، متد UseLazyLoadingProxies را فراخوانی نمائید:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseLazyLoadingProxies()
        .UseSqlServer(myConnectionString);
و یا اینکار در فایل آغازین برنامه نیز میسر است:
    .AddDbContext<BloggingContext>(
        b => b.UseLazyLoadingProxies()
              .UseSqlServer(myConnectionString));
اکنون EF Core 2.1 خواص راهبری (navigation properties) را که قابل بازنویسی باشند (همان مباحث AOP و تشکیل پروکسی‌ها)، lazy load می‌کند.
این خواص نیز حتما باید به صورت virtual معرفی شوند تا قابلیت بازنویسی را داشته باشند؛ مانند:
public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public virtual Blog Blog { get; set; }
}
در این مثال با فعال بودن lazy loading، به محض لمس خاصیت Blog، اطلاعات مرتبط با آن از بانک اطلاعاتی واکشی خواهند شد و نه پیش از آن مانند eager loading که تمام اطلاعات وابسته‌ی به یک موجودیت را نیز واکشی می‌کند.
هرچند این قابلیت بارگذاری اطلاعات وابسته در آینده، جذاب به نظر می‌رسد اما در عمل در حین رندر یک گرید و یا بکارگیری حلقه‌ها، چون سبب رفت و برگشت بیش از اندازه‌ای به بانک اطلاعاتی خواهد شد، باید با دقت مورد استفاده قرار گیرد و اساسا استفاده‌ی از آن در برنامه‌های وب توصیه نمی‌شود (با بررسی‌های پروژه‌های بسیاری مشخص شده‌است که این قابلیت ضررش بیشتر از نفعش است).


ب) فعالسازی Lazy loading بدون استفاده از Proxyها
در این حالت نیازی به نصب بسته‌ی AOP جدید تشکیل پروکسی‌ها نیست. در اینجا در کلاس موجودیت خود باید سرویس ILazyLoader را تزریق کنید:
public class Blog
{
    private ICollection<Post> _posts;
public Blog()
    {
    }
private Blog(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }
private ILazyLoader LazyLoader { get; set; }
public int Id { get; set; }
    public string Name { get; set; }
public ICollection<Post> Posts
    {
        get => LazyLoader?.Load(this, ref _posts);
        set => _posts = value;
    }
}
public class Post
{
    private Blog _blog;
public Post()
    {
    }
private Post(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }
private ILazyLoader LazyLoader { get; set; }
public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
public Blog Blog
    {
        get => LazyLoader?.Load(this, ref _blog);
        set => _blog = value;
    }
}
در این روش نیازی به virtual معرفی کردن خواص راهبری نیست. اما در این حالت به علت استفاده‌ی از سرویس ILazyLoader، نیاز خواهید داشت تا بسته‌ی نیوگت Microsoft.EntityFrameworkCore.Abstractions را نیز نصب کنید.
مطالب
بررسی تغییرات Blazor 8x - قسمت پنجم - امکان تعریف جزیره‌های تعاملی Blazor Server
در Blazor 8x می‌توان صفحات SSR ای را به همراه Blazor server islands و یا Blazor WASM islands داشت؛ یعنی یک کامپوننت Blazor Server که داخل یک صفحه‌ی معمولی SSR قرار گرفته و با سرور، ارتباط SiganlR برقرار می‌کند و یا یک کامپوننت Blazor WASM که در قسمتی از صفحه‌ی SSR درج شده و درون مرورگر کاربر اجرا می‌شود. به هر کدام از این‌ها یک «جزیره‌ی تعاملی» گفته می‌شود (interactive island). در این قسمت، نکات مرتبط با جزایر تعاملی Blazor Server را بررسی می‌کنیم.


بررسی یک مثال: تهیه یک برنامه‌ی Blazor 8x برای نمایش لیست محصولات، به همراه جزئیات آن‌ها

به لطف وجود SSR در Blazor 8x، می‌توان HTML نهایی کامپوننت‌ها و صفحات Blazor را همانند صفحات MVC و یا Razor pages، در سمت سرور تهیه و بازگشت داد. این خروجی در نهایت یک static HTML بیشتر نیست و گاهی از اوقات ما به بیش از یک خروجی ساده HTML ای نیاز داریم.
در این مثال که بر اساس قالب dotnet new blazor --interactivity Server تهیه می‌شود، قصد داریم موارد زیر را پیاده سازی کنیم:
- صفحه‌ای که یک لیست محصولات فرضی را نمایش می‌دهد : بر اساس SSR
- صفحه‌ای که جزئیات یک محصول را نمایش می‌دهد: بر اساس SSR
- دکمه‌ای در ذیل قسمت نمایش جزئیات یک محصول، برای دریافت و نمایش لیست محصولات مشابه و مرتبط: بر اساس Blazor server islands

یعنی تا جائیکه ممکن است قصد نداریم تمام صفحات و تمام قسمت‌های برنامه را با فعالسازی سراسری حالت تعاملی Blazor server که در قسمت‌های قبل در مورد آن توضیح داده شد، پیاده سازی کنیم. می‌خواهیم فقط قسمت کوچکی از این سناریو را که واقعا نیاز به یک چنین قابلیتی را دارد، توسط یک جزیره‌ی تعاملی Blazor server واقع شده‌ی در قسمتی از یک صفحه‌ی استاتیک SSR، مدیریت کنیم.


مدل برنامه: رکوردی برای ذخیره سازی اطلاعات یک محصول

namespace BlazorDemoApp.Models;

public record Product
{
    public int Id { get; set; }
    public required string Title { get; set; }
    public required string Description { get; set; }
    public decimal Price { get; set; }

    public List<int> Related { get; set; } = new();
}
در اینجا، هدف تعریف لیستی از محصولات فرضی است؛ به همراه خاصیتی که Id محصولات مشابه را نگهداری می‌کند (خاصیت Related).


سرویس برنامه: سرویسی برای بازگشت لیست محصولات

چون Blazor Server و SSR هر دو بر روی سرور اجرا می‌شوند، از لحاظ دسترسی به اطلاعات و کار با سرویس‌ها، هماهنگی کاملی وجود داشته و می‌توان کدهای یکسان و یکدستی را در اینجا بکار گرفت.
در ادامه کدهای کامل سرویس Services\ProductStore.cs را مشاهده می‌کنید:
using BlazorDemoApp.Models;

namespace BlazorDemoApp.Services;

public interface IProductStore
{
    IList<Product> GetAllProducts();
    Product GetProduct(int id);
    IList<Product> GetRelatedProducts(int productId);
}

public class ProductStore : IProductStore
{
    private static readonly List<Product> ProductsDataSource =
        new()
        {
            new Product
            {
                Id = 1, Title = "Smart speaker", Price = 22m,
                Description =
                    "This smart speaker delivers excellent sound quality and comes with built-in voice control, offering an impressive music listening experience.",
                Related = new List<int> { 2, 3 },
            },
            new Product
            {
                Id = 2, Title = "Regular speaker", Price = 89m,
                Description =
                    "Enjoy room-filling sound with this regular speaker. With its slick design, it perfectly fits into any room in your house.",
                Related = new List<int> { 1, 3 },
            },
            new Product
            {
                Id = 3, Title = "Speaker cable", Price = 12m,
                Description =
                    "This high-quality speaker cable ensures a reliable and clear audio connection for your sound system.",
            },
        };

    public IList<Product> GetAllProducts() => ProductsDataSource;

    public Product GetProduct(int id) => ProductsDataSource.Single(p => p.Id == id);

    public IList<Product> GetRelatedProducts(int productId)
    {
        var product = ProductsDataSource.Single(x => x.Id == productId);
        return ProductsDataSource.Where(p => product.Related.Contains(p.Id)).ToList();
    }
}
هدف از این سرویس، ارائه‌ی لیست تمام محصولات، دریافت اطلاعات یک محصول و همچنین یافتن لیست محصولات مشابه یک محصول خاص است.
این سرویس را باید در فایل Program.cs برنامه به صورت زیر معرفی کرد تا در فایل‌های razor برنامه‌ی جاری قابل دسترسی شود:
builder.Services.AddScoped<IProductStore, ProductStore>();


تکمیل صفحه‌ی نمایش لیست محصولات

قصد داریم زمانیکه کاربر برای مثال به آدرس فرضی http://localhost:5136/products مراجعه کرد، با تصویر لیستی از محصولات مواجه شود:


کدهای این صفحه را که در فایل Components\Pages\Store\ProductsList.razor قرار می‌گیرند، در ادامه مشاهده می‌کنید:

@page "/Products"
@using BlazorDemoApp.Models
@using BlazorDemoApp.Services

@inject IProductStore Store

@attribute [StreamRendering]

<h3>Products</h3>

@if (_products == null)
{
    <p>Loading...</p>
}
else
{
    @foreach (var item in _products)
    {
        <a href="/ProductDetails/@item.Id">
            <div>
                <div>
                    <h5>@item.Title</h5>
                </div>
                <div>
                    <h5>@item.Price.ToString("c")</h5>
                </div>
            </div>
        </a>
    }
}

@code {
    private IList<Product>? _products;

    protected override Task OnInitializedAsync() => GetProductsAsync();

    private async Task GetProductsAsync()
    {
        await Task.Delay(1000); // Simulates asynchronous loading to demonstrate streaming rendering
        _products = Store.GetAllProducts();
    }

}
توضیحات:
- جهت دسترسی به سرویس لیست محصولات، ابتدا سرویس IProductStore به این صفحه تزریق شده‌است.
- سپس در روال رویدادگردان آغازین OnInitializedAsync، کار دریافت اطلاعات و انتساب آن به لیستی، صورت گرفته‌است.
- در این متد جهت شبیه سازی یک عملیات async از یک Task.Delay استفاده شده‌است.
- چون این صفحه، یک صفحه‌ی SSR عادی است، بدون تعریف ویژگی StreamRendering در آن، پس از اجرای برنامه، هیچگاه قسمت loading که در حالت products == null_ قرار است ظاهر شود، نمایش داده نمی‌شود؛ چون در این حالت (حذف نوع رندر)، صفحه‌ی نهایی که به کاربر ارائه خواهد شد، یک صفحه‌ی استاتیک کاملا رندر شده‌ی در سمت سرور است و کاربر باید تا زمان پایان این رندر در سمت سرور، منتظر بماند و سپس صفحه‌ی نهایی را دریافت و مشاهده کند. در حالت Streaming rendering، ابتدا می‌توان یک قالب HTML ای را بازگشت داد و سپس مابقی محتوای آن‌را به محض آماده شدن در طی چند مرحله بازگشت داد.
- لینک‌های نمایش داده شده‌ی در اینجا، به صفحه‌ی ProductDetails اشاره می‌کنند که در آن، جزئیات محصول انتخابی نمایش داده می‌شوند.


تکمیل صفحه‌ی نمایش جزئیات یک محصول


در صفحه‌ی کامپوننت Components\Pages\Store\ProductDetails.razor، کار نمایش جزئیات محصول انتخابی صورت می‌گیرد:

@page "/ProductDetails/{ProductId}"
@using BlazorDemoApp.Models
@using BlazorDemoApp.Services

@inject IProductStore Store

@attribute [StreamRendering]

@if (_product == null)
{
    <p>Loading...</p>
}
else
{
    <div>
        <div>
            <h5>
                @_product.Title (@_product.Price.ToString("C"))
            </h5>
            <p>
                @_product.Description
            </p>
        </div>
        @if (_product.Related.Count > 0)
        {
            <div>
                <RelatedProducts ProductId="Convert.ToInt32(ProductId)" />
            </div>
        }
    </div>
    <NavLink href="/Products">Back</NavLink>
}

@code {
    private Product? _product;

    [Parameter]
    public string? ProductId { get; set; }

    protected override Task OnInitializedAsync() => GetProductAsync();

    private async Task GetProductAsync()
    {
        await Task.Delay(1000); // Simulates asynchronous loading to demonstrate streaming rendering
        _product = Store.GetProduct(Convert.ToInt32(ProductId));
    }

}
توضیحات:
- باتوجه به نحوه‌ی تعریف مسیریابی این صفحه، پارامتر ProductId از طریق آدرسی مانند http://localhost:5136/ProductDetails/1 دریافت می‌شود.
- سپس این ProductId را در روال رخ‌دادگردان OnInitializedAsync، برای یافتن جزئیات محصول انتخابی از سرویس تزریقی IProductStore، بکار می‌گیریم.
- در اینجا نیز از Task.Delay برای شبیه سازی یک عملیات طولانی async مانند دریافت اطلاعات از یک بانک اطلاعاتی، کمک گرفته شده‌است.
- همچنین برای نمایش قسمت loading صفحه در حالت SSR، بازهم از StreamRendering استفاده کرده‌ایم.
- اگر دقت کرده باشید، ذیل تصویر اطلاعات محصول، دکمه‌ای نیز جهت بارگذاری اطلاعات محصولات مشابه، قرار دارد که ProductId محصول انتخابی را دریافت می‌کند:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" />
بنابراین در ادامه کامپوننت RelatedProducts فوق را تکمیل می‌کنیم.


تکمیل کامپوننت نمایش لیست محصولات مشابه و مرتبط

در فایل Components\Pages\Store\RelatedProducts.razor، کار نمایش یک دکمه و سپس نمایش لیستی از محصولات مشابه، صورت می‌گیرد:
@using BlazorDemoApp.Models
@using BlazorDemoApp.Services
@inject IProductStore Store

<button @onclick="LoadRelatedProducts">Related products</button>

@if (_loadRelatedProducts)
{
    @if (_relatedProducts == null)
    {
        <p>Loading...</p>
    }
    else
    {
        <div>
            @foreach (var item in _relatedProducts)
            {
                <a href="/ProductDetails/@item.Id">
                    <div>
                        <h5>@item.Title (@item.Price.ToString("C"))</h5>
                    </div>
                </a>
            }
        </div>
    }
}

@code{

    private IList<Product>? _relatedProducts;
    private bool _loadRelatedProducts;

    [Parameter]
    public int ProductId { get; set; }

    private async Task LoadRelatedProducts()
    {
        _loadRelatedProducts = true;
        await Task.Delay(1000); // Simulates asynchronous loading to demonstrate InteractiveServer mode
        _relatedProducts = Store.GetRelatedProducts(ProductId);
    }

}

تعاملی کردن کامپوننت نمایش لیست محصولات مشابه

مشکل! اگر در این حالت برنامه را اجرا کرده و بر روی دکمه‌ی related products کلیک کنیم، هیچ اتفاقی رخ نمی‌دهد! یعنی روال رویدادگران LoadRelatedProducts اصلا اجرا نمی‌شود. علت اینجا است که صفحات SSR، در نهایت یک static HTML بیشتر نیستند و فاقد قابلیت‌های تعاملی، مانند واکنش نشان دادن به کلیک بر روی یک دکمه هستند.
محدودیتی که به همراه صفحات SSR وجود دارد این است: این نوع کامپوننت‌ها و صفحات فقط یکبار رندر می‌شوند و نه بیشتر. بله می‌توان بر روی آن‌ها ده‌ها دکمه، نوارهای لغزان، دراپ‌داون و غیره را قرار داد، اما ... نمی‌توان هیچگونه تعاملی را با آن‌ها داشت. کامپوننت نهایی رندر شده و نمایش داده شده، دیگر در هیچ‌جائی اجرا نمی‌شود. در این حالت است که می‌توان تصمیم گرفت که نیاز است قسمتی از این صفحه، تعاملی شود.
به همین جهت باید نحوه‌ی رندر کامپوننت RelatedProducts را به صورت یک جزیره‌ی تعاملی Blazor server درآورد تا رویداد منتسب به دکمه‌ی related products موجود در آن، پردازش شود. بنابراین به صفحه‌ی ProductDetails.razor مراجعه کرده و rendermode@ این کامپوننت را به صورت زیر به حالت InteractiveServer تغییر می‌دهیم:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" @rendermode="@InteractiveServer"/>
اکنون اگر برنامه را مجددا اجرا کرده و بر روی دکمه‌ی نمایش محصولات مشابه قرار گرفته در ذیل جزئیات یک محصول کلیک کنیم، بدون مشکل کار می‌کند:


نحوه‌ی پردازش پشت صحنه‌ی این نوع صفحات هم جالب است. برای اینکار به برگه‌ی network مخصوص developer tools مرورگر مراجعه کرده و مراحل رسیدن به صفحه‌ی نمایش جزئیات محصول را طی می‌کنیم:


- اگر دقت کنید، جابجایی بین صفحات، با استفاده از fetch انجام شده؛ یعنی با اینکه این صفحات در اصل static HTML خالص هستند، اما ... کار full reload صفحه مانند ASP.NET Web forms قدیمی انجام نمی‌شود (و یا حتی برنامه‌های MVC و Razor pages) و نمایش صفحات، Ajax ای است و با fetch استاندارد آن صورت می‌گیرد تا هنوز هم حس و حال SPA بودن برنامه حفظ شود. همچنین اطلاعات DOM کل صفحه را هم به‌روز رسانی نمی‌کند؛ فقط موارد تغییر یافته در اینجا به روز رسانی خواهند شد.
این موارد توسط فایل blazor.web.js درج شده‌ی در کامپوننت آغازین App.razor، به صورت خودکار مدیریت می‌شوند:
<script src="_framework/blazor.web.js"></script>

به علاوه در این حالت ای‌جکسی fetch، کار دریافت مجدد فایل‌های استاتیک مرتبط یک صفحه، مانند فایل‌های js.، css.، تصاویر و غیره، مجددا انجام نمی‌شود که این مورد خود مزیتی است نسبت به حالت متداول برنامه‌های ASP.NET Core MVC و یا Razor pages. در حالت Blazor 8x SSR، فقط یک partial update از نوع Ajax ای انجام می‌شود.
به این قابلیت، enhanced navigation هم گفته می‌شود. برای مثال زمانیکه یک فرم SSR را در Blazor 8x به سمت سرور ارسال می‌کنیم، موقعیت scroll به صورت خودکار ذخیره و بازیابی می‌شود تا کاربر با یک full post back مواجه نشده و موقعیت جاری خود را در صفحه از دست ندهد (چنین ایده‌ای، یک زمانی در برنامه‌های ASP.NET Web forms هم برقرار بود و هست! به نظر مایکروسافت هنوز دلتنگ طراحی قدیمی ASP.NET Web forms است!).

- همچنین به محض نمایش صفحه‌ی جزئیات محصول، پس از پایان کار نمایش آن، یک اتصال وب‌سوکت هم برقرار شده که مرتبط با جزیره‌ی تعاملی Blazor server تعریف شده، یا همان کامپوننت RelatedProducts است.

- یک disconnect را هم در اینجا مشاهده می‌کنید. اگر به یک صفحه‌ی تعاملی مراجعه کنیم، همانطور که مشخص است، یک اتصال SignalR برقرار می‌شود (که به آن در اینجا circuit هم می‌گویند). اما اگر از این صفحه به سمت یک صفحه‌ی SSR حرکت کنیم، پس از نمایش آن صفحه، اتصال SignalR قبلی که دیگر نیازی به آن نیست، بسته خواهد شد تا منابع سمت سرور، رها شوند.


در حین disconnect، شماره ID اتصال SignalR ای که دیگر به آن نیازی نیست، به برنامه ارسال می‌شود تا به صورت خودکار در سمت سرور بسته شود. تمام این موارد توسط blazor.web.js فریم‌ورک، مدیریت می‌شوند.
در این تصویر ابتدا به آدرس http://localhost:5136/ProductDetails/1 مراجعه کرده‌ایم که سبب برقراری اتصال یک وب‌سوکت شده‌است. سپس با کلیک بر روی دکمه‌ی back، به صفحه‌ی SSR مشاهده‌ی لیست محصولات برگشته‌ایم. در این حالت، دستور قطع اتصال SignalR قبلی صادر شده‌است.


نحوه‌ی مدیریت Pre-rendering در جزایر تعاملی Blazor 8x

به صورت پیش‌فرض زمانیکه از حالت رندر InteractiveServer استفاده می‌کنیم، قابلیت pre-rendering آن نیز فعال است. یعنی ابتدا حداقل قالب و قسمت‌های ثابت کامپوننت، در سمت سرور پردازش و رندر شده و سپس به سمت کلاینت ارسال می‌شوند. در این حالت کاربر، تجربه‌ی کاربری روان‌تری را شاهد خواهد بود؛ چون برای مدتی نباید منتظر آماده شدن کل UI مرتبط باشد و حداقل، قسمت‌هایی از صفحه که تعاملی نیستند، قابل دسترسی و مشاهده هستند.
اگر به هر دلیلی نیاز به غیرفعال کردن این قابلیت را دارید، باید به صورت زیر عمل کرد:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" @rendermode="@(new InteractiveServerRenderMode(false))"/>
در این حالت اگر برنامه را اجرا کنید، در حین نمایش صفحه‌ی اصلی در برگیرنده‌ی از نوع SSR، فقط جای این کامپوننت در صفحه مشخص می‌شود و پس از برقراری اتصال با سرور از طریق اتصال SignalR، شاهد UI کامپوننت RelatedProducts خواهیم بود، که نسبت به قبل، وقفه‌ای را سبب خواهد شد.

نحوه‌ی تعریف خواص استاتیک InteractiveServer بکار گرفته شده و یا کلاس InteractiveServerRenderMode را در ادامه مشاهده می‌کنید. جهت سهولت تعریف این موارد، سطر زیر که یک using static است، به فایل Imports.razor_ اضافه شده‌است:
@using static Microsoft.AspNetCore.Components.Web.RenderMode

public static class RenderMode
  {
    public static InteractiveServerRenderMode InteractiveServer { get; } = new InteractiveServerRenderMode();

    public static InteractiveWebAssemblyRenderMode InteractiveWebAssembly { get; } = new InteractiveWebAssemblyRenderMode();

    public static InteractiveAutoRenderMode InteractiveAuto { get; } = new InteractiveAutoRenderMode();
  }


public class InteractiveServerRenderMode : IComponentRenderMode
  {
    public InteractiveServerRenderMode()
      : this(true)
    {
    }

    public InteractiveServerRenderMode(bool prerender) => this.Prerender = prerender;

    public bool Prerender { get; }
  }


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: Blazor8x-Server-Normal.zip  
نظرات مطالب
بازنویسی سطح دوم کش برای Entity framework 6
Include باید قبل از Cacheable و حتی قبل از FindAll شما فراخوانی شود. باید پس از ارجاعی به dbSet فراخوانی شود، تا EF بتواند چرخش کاری صحیح آن‌را تشکیل دهد. برای بار دوم فراخوانی، پس از Cacheable، شما یک لیست ساده‌ی LINQ to Objects را خواهید داشت که از Context منقطع است و صرفا از کش خوانده شده‌است. بنابراین فراخوانی متد Include بر روی آن مفهومی ندارد.
مطالب
ذخیره سازی تنظیمات برنامه‌های ASP.NET Core در بانک اطلاعاتی به کمک Entity Framework Core
در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config » با مقدمات کار با فایل‌های تنظیمات برنامه و تامین کننده‌های مختلف آن‌ها آشنا شدیم. در این مطلب قصد داریم یک نمونه‌ی سفارشی تامین کننده‌های تنظیمات برنامه را بر اساس دریافت و ذخیره سازی اطلاعات در بانک اطلاعاتی، تهیه کنیم.


ساختار موجودیت تنظیمات برنامه

تنظیمات برنامه با هر قالبی که تهیه شوند، دست آخر به صورت یک <Dictionary<string,string در برنامه پردازش شده و قابل دسترسی می‌شوند. بنابراین موجودیت معادل این Dictionary را به صورت زیر تعریف می‌کنیم:
namespace DbConfig.Web.DomainClasses
{
    public class ConfigurationValue
    {
        public int Id { get; set; }
        public string Key { get; set; }
        public string Value { get; set; }
    }
}


ساختار Context برنامه و مقدار دهی اولیه‌ی آن

پس از تعریف موجودیت تنظیمات برنامه، آن‌را به صورت زیر به Context برنامه معرفی می‌کنیم:
    public class MyAppContext : DbContext, IUnitOfWork
    {
        public MyAppContext(DbContextOptions options) : base(options)
        { }

        public virtual DbSet<ConfigurationValue> Configurations { set; get; }
همچنین، برای مقدار دهی مقادیر اولیه‌ی تنظیمات برنامه نیز اینبار می‌توان به کمک متد HasData، به صورت زیر عمل کرد:
        protected override void OnModelCreating(ModelBuilder builder)
        {
            // it should be placed here, otherwise it will rewrite the following settings!
            base.OnModelCreating(builder);

            // Custom application mappings
            builder.Entity<ConfigurationValue>(entity =>
            {
                entity.Property(e => e.Key).HasMaxLength(450).IsRequired();
                entity.HasIndex(e => e.Key).IsUnique();
                entity.Property(e => e.Value).IsRequired();
                entity.HasData(new ConfigurationValue
                {
                    Id = 1,
                    Key = "key-1",
                    Value = "value_from_ef_1"
                });
                entity.HasData(new ConfigurationValue
                {
                    Id = 2,
                    Key = "key-2",
                    Value = "value_from_ef_2"
                });
            });
        }

ایجاد یک IConfigurationSource سفارشی مبتنی بر بانک اطلاعاتی

انواع و اقسام تامین کننده‌های تنظیمات برنامه در پروژه‌های ASP.NET Core، در حقیقت یک پیاده سازی سفارشی از اینترفیس IConfigurationSource هستند. به همین جهت در ادامه یک نمونه‌ی مبتنی بر EF Core آن را تهیه می‌کنیم:
    public class EFConfigurationSource : IConfigurationSource
    {
        private readonly IServiceProvider _serviceProvider;

        public EFConfigurationSource(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            return new EFConfigurationProvider(_serviceProvider);
        }
    }
در اینجا چون می‌خواهیم به IUnitOfWork دسترسی پیدا کنیم، IServiceProvider را به سازنده‌ی این تامین کننده تزریق کرده‌ایم. کار اصلی ساخت آن نیز در متد Build، با ارائه‌ی یک IConfigurationProvider سفارشی انجام می‌شود. اینجا است که اطلاعات را از بانک اطلاعاتی خوانده و در اختیار سیستم تنظیمات برنامه قرار می‌دهیم:
    public class EFConfigurationProvider : ConfigurationProvider
    {
        private readonly IServiceProvider _serviceProvider;

        public EFConfigurationProvider(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
            ensureDatabaseIsCreated();
        }

        public override void Load()
        {
            using (var scope = _serviceProvider.CreateScope())
            {
                var uow = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
                this.Data?.Clear();
                this.Data = uow.Set<ConfigurationValue>()
                               .AsNoTracking()
                               .ToList()
                               .ToDictionary(c => c.Key, c => c.Value);
            }
        }

        private void ensureDatabaseIsCreated()
        {
            using (var scope = _serviceProvider.CreateScope())
            {
                var uow = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
                uow.Migrate();
            }
        }
    }
در ConfigurationProvider فوق، متد Load، در آغاز برنامه فراخوانی شده و در اینجا فرصت داریم تا خاصیت this.Data آن‌را که از نوع <Dictionary<string,string است، مقدار دهی کنیم. بنابراین از serviceProvider تزریق شده‌ی در سازنده‌ی کلاس استفاده کرده و به وهله‌ای از IUnitOfWork دسترسی پیدا می‌کنیم. سپس بر این اساس تمام رکوردهای جدول متناظر با ConfigurationValue را دریافت و توسط متد ToDictionary، تبدیل به ساختار مدنظر خاصیت this.Data می‌کنیم.
در اینجا فراخوانی متد ensureDatabaseIsCreated را نیز مشاهده می‌کنید. کلاس EFConfigurationProvider در آغاز برنامه و پیش از هر عمل دیگری وهله سازی شده و سپس متد Load آن فراخوانی می‌شود. به همین جهت نیاز است یا پیشتر، بانک اطلاعاتی را توسط دستورات Migration ایجاد کرده باشید و یا متد ensureDatabaseIsCreated، اطلاعات Migration موجود را به بانک اطلاعاتی برنامه اعمال می‌کند.


معرفی EFConfigurationSource به برنامه

جهت معرفی ساده‌تر EFConfigurationSource تهیه شده، ابتدا یک متد الحاقی را بر اساس آن تهیه می‌کنیم:
    public static class EFExtensions
    {
        public static IConfigurationBuilder AddEFConfig(this IConfigurationBuilder builder,
            IServiceProvider serviceProvider)
        {
            return builder.Add(new EFConfigurationSource(serviceProvider));
        }
    }
سپس می‌توان این متد AddEFConfig را به صورت زیر به تنظیمات برنامه در کلاس Startup اضافه و معرفی کرد:
namespace DbConfig.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IUnitOfWork, MyAppContext>();
            services.AddScoped<IConfigurationValuesService, ConfigurationValuesService>();

            var connectionString = Configuration.GetConnectionString("SqlServerConnection")
                     .Replace("|DataDirectory|", Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data"));
            services.AddDbContext<MyAppContext>(options =>
                    {
                        options.UseSqlServer(
                            connectionString,
                            dbOptions =>
                                {
                                    var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                                    dbOptions.CommandTimeout(minutes);
                                    dbOptions.EnableRetryOnFailure();
                                });
                    });

            var serviceProvider = services.BuildServiceProvider();
            var configuration = new ConfigurationBuilder()
                                       .AddConfiguration(Configuration) // Adds all of the existing configurations
                                       .AddEFConfig(serviceProvider)
                                       .Build();
            services.AddSingleton<IConfigurationRoot>(sp => configuration); // Replace
            services.AddSingleton<IConfiguration>(sp => configuration); // Replace
در اینجا ابتدا نیاز است یک ConfigurationBuilder جدید را ایجاد کنیم تا بتوان AddEFConfig را بر روی آن فراخوانی کرد. در این بین، خود برنامه نیز تعدادی تامین کننده‌ی تنظیمات پیش‌فرض را نیز دارد که قصد نداریم سبب پاک شدن آن‌ها شویم. به همین جهت آن‌ها را توسط متد AddConfiguration، افزوده‌ایم. پس از تعریف این ConfigurationBuilder جدید، نیاز است آن‌را جایگزین IConfiguration و IConfigurationRoot پیش‌فرض برنامه کنیم که روش آن‌را در دو متد services.AddSingleton ملاحظه می‌کنید.
همچنین روش دسترسی به serviceProvider مورد نیاز AddEFConfig، توسط متد services.BuildServiceProvider نیز در کدهای فوق مشخص است. به همین جهت مجبور شدیم این تعریف را در اینجا قرار دهیم و گرنه می‌شد از کلاس Program و یا حتی سازنده‌ی کلاس Startup نیز استفاده کرد. مشکل این دو مکان عدم دسترسی به سرویس IUnitOfWork و سایر تنظیمات برنامه است.


آزمایش برنامه

اگر به قسمت «ساختار Context برنامه و مقدار دهی اولیه‌ی آن» مطلب جاری دقت کرده باشید، دو کلید پیش‌فرض در اینجا ثبت شده‌اند. به همین جهت در ادامه با تزریق سرویس IConfiguration به سازنده‌ی یک کنترلر، سعی در خواندن مقادیر آن‌ها خواهیم کرد:
namespace DbConfig.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IConfiguration _configuration;

        public HomeController(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public IActionResult Index()
        {
            return Json(
                new
                {
                    key1 = _configuration["key-1"],
                    key2 = _configuration["key-2"]
                });
        }
با این خروجی:



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

فرض کنید توسط سرویسی، اطلاعات جدول ConfigurationValue را تغییر داده‌اید. نکته‌ی مهم اینجا است که اینکار سبب فراخوانی مجدد متد Load کلاس EFConfigurationProvider نخواهد شد و عملا این تغییرات در سراسر برنامه توسط تزریق اینترفیس IConfiguration قابل دسترسی نخواهند بود (مگر اینکه برنامه مجددا ری‌استارت شود). نکته‌ی به روز رسانی این اطلاعات به صورت زیر است:
    public class ConfigurationValuesService : IConfigurationValuesService
    {
        private readonly IConfiguration _configuration;

        public ConfigurationValuesService(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        private void reloadEFConfigurationProvider()
        {
            ((IConfigurationRoot)_configuration).Reload();
        }
در جائیکه نیاز است پس از به روز رسانی بانک اطلاعاتی، تنظیمات برنامه را نیز بارگذاری مجدد کنید، ابتدا اینترفیس IConfiguration را به سازنده‌ی آن تزریق کرده و سپس به نحو فوق، متد Reload را فراخوانی کنید. اینکار سبب می‌شود تا یکبار دیگری متد Load کلاس EFConfigurationProvider نیز فراخوانی شود که باعث بارگذاری مجدد تنظیمات برنامه خواهد شد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: EFCoreDbConfig.zip
نظرات مطالب
مقاومت اتصال و اتصالات بهبودپذیر در Entity framework 6
نکته: این روش محدودیت هایی هم دارد:
using (var db = new BloggingContext()) 
{ 
    using (var trn = db.Database.BeginTransaction()) 
    { 
        db.Blogs.Add(new Site { Url = "http://msdn.com/data/ef" }); 
        db.Blogs.Add(new Site { Url = "http://blogs.msdn.com/adonet" }); 
        db.SaveChanges(); 
 
        db.Blogs.Add(new Site { Url = "http://twitter.com/efmagicunicorns" }); 
        db.SaveChanges(); 
 
        trn.Commit(); 
    } 
}
امکان اجرای تراکنش هایی به صورت بالا وجود ندارد.
مطالب
نکاتی در باب T-SQL
در این مطلب قصد دارم به نکاتی ساده  در T-SQL بپردازم، امیدوارم مفید واقع شود.

1- تفاوت (*)Count و (Count(column
در (*)Count ، همه Row‌ها و مقادیرشان مورد جستجو قرار می‌گیرد، اما در  (Count(column فقط مقادیر غیر Null ستون مورد نظر،مورد جستجو قرار می‌گیرد.
مثال:
Create Table Test(ID int,Firstname varchar(20));

Insert Into Test (ID,Firstname) Values(1,'K');
Insert Into Test (ID,Firstname) Values(2,'B');
Insert Into Test (ID) Values(3);
با اجرای Query زیر خواهیم داشت:
Select COUNT(*) From Test
خروجی آن 3 می‌باشد.
اما با اجرای Query زیر خواهیم داشت:
Select COUNT(Firstname) From Test
خروجی آن 2 میباشد، چون با توجه به سه رکوردی که در جدول Test درج شده بود، رکورد سوم برای فیلد Firstname با مقدار Null پر شده است.
  • هرگاه در اجرای Count ،هدفتان بدست آوردن تعداد ستون خاصی است، از (Count(column استفاده نمایید.
2- بوسیله Script‌های زیر می‌توانیم عدد صفر تولید نماییم.
select count(cast(null as int))
select count(*) where 'a'='b'
select €
select ¥
select £
Select $ 
select count(*)-count(*)
select Ascii('A')-Ascii('A')
select LEN('')
3- با یک Select ساده می‌توان نام ستون و مقدار را کنار هم نوشت و مشاهده نمود.

در مثال بالا مقادیر ستونها، عددی در نظر گرفته شده است، چنانچه تمایل به نمایش حروف داشته باشید، کافیست کاراکترهای حرفی را بین سنگل کوتیشن قرار دهید، همانند شکل زیر:

4- دو روش پیشنهادی برای تشخیص عدد بزرگتر از بین دو عدد
  • روش اول 
      ابتدا به فرمول زیر توجه نمایید:
(a+b)+ABS(a-b)
      حاصل جواب فرمول،برابر است با دو برابر عدد بزرگتر،بنابراین اگر حاصل فوق را ضربدر عدد 0/5 نماییم،عدد بزرگتر بدست خواهد آمد. در نتیجه خواهیم داشت:
0.5(a+b)+ABS(a-b)
 با اجرای Script زیر خروجی عدد 90.34 می‌باشد:
DECLARE @Value1 DECIMAL(5,2) = 80.22
DECLARE @Value2 DECIMAL(5,2) =90.34
SELECT (0.5 * ((@Value1 + @Value2) + ABS(@Value1 - @Value2))) AS MaxColumn  
اشکال در این روش این است که، اگر مقدار یکی از اعداد Null باشد،ماکزیمم بین دو عدد Null نمایش داده خواهد شد.
  • روش دوم
DECLARE @Value1 DECIMAL(5,2) = 9.22
DECLARE @Value2 DECIMAL(5,2) = 8.34
SELECT CASE WHEN @Value1 > @Value2 THEN @Value1 ELSE @Value2 END AS MaxColumn

در این روش اگر مقدار یکی از اعداد Null باشد،ماکزیمم بین دو عدد، عدد غیر Null می‌باشد.

5- مشاهده مشخصات کلیه دیتابیس‌های موجود در SQL Server با استفاده از Sys.Databases .
Select * From sys.databases
خروجی بصورت زیر خواهد بود:



6- بوسیله دستور OUTPUT می‌توان خروجی Query‌های Delete،Update و Insert را مشاهده نمود:

مثال اول برای Query Delete :

در شکل، تک رکورد حذف شده را مشاهده می‌نمایید.

مثال دوم برای Query Update بصورت زیر میباشد:

در شکل، مقدار A، مقدار جدیدی است که بروز رسانی شده است و مقدار B مقداری است که مربوط به قبل از بروز رسانی می‌باشد.

مثال سوم برای Query Insert بصورت زیر میباشد:

موفق باشید.

مطالب
لینک‌های هفته‌ی سوم بهمن

وبلاگ‌ها ، سایت‌ها و مقالات ایرانی (داخل و خارج از ایران)

Visual Studio

ASP. Net


طراحی و توسعه وب

PHP

اس‌کیوال سرور

سی شارپ

عمومی دات نت

ویندوز

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

متفرقه
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها
تنظیم یک Sequence به صورت زیر
modelBuilder.HasSequence<int>("OrderNumbers", schema: "shared")
           .StartsAt(1000).IncrementsBy(5);
که از عدد 1000 شروع شده و واحد افزایش آن 5 است، سبب تولید یک CREATE SEQUENCE جدید در بانک اطلاعاتی می‌شود و به صورت مستقل با کوئری select next value for shared.OrderNumbers، قابل بازیابی است. هر بار فراخوانی این کوئری سبب تولید اعداد 1000، 1005 و ... می‌شود. یعنی قبل از insert، بازه‌ی مورد استفاده و اعداد آن کاملا مشخص است. بعد یکی از روش‌های استفاده از این Sequence، مقدار دهی Id جدول است:
modelBuilder.Entity<PrimaryKeyWithSequence>(entity =>
     {
       entity.Property(e => e.PrimaryKeyWithSequenceId).HasDefaultValueSql("NEXT VALUE FOR [PrimaryKeyWithSequenceSequence]");
     });
و به صورت زیر جهت تعریف جدول مورد استفاده قرار می‌گیرد:
CREATE TABLE PrimaryKeyWithSequence
(
    ID int PRIMARY KEY CLUSTERED
        DEFAULT (NEXT VALUE FOR [PrimaryKeyWithSequenceSequence]),
    Field1 nvarchar(300) NULL
) ;
این یک روش مقدار دهی Id در سمت دیتابیس با یک سری اعداد مشخص است.
اما برای حالت batch و مشخص بودن Id آن‌ها، از همین روش با متد خاص ForSqlServerUseSequenceHiLo استفاده می‌شود:
modelbuilder.HasSequence<int>("DBSequenceHiLo")
                  .StartsAt(1000).IncrementsBy(5);
modelbuilder.ForSqlServerUseSequenceHiLo("DBSequenceHiLo");
که یک چنین SEQUENCE ای را ایجاد می‌کند:
CREATE SEQUENCE [dbo].[DBSequenceHiLo] 
 AS [int]
 START WITH 1000
 INCREMENT BY 5
 MINVALUE -2147483648
 MAXVALUE 2147483647
 CACHE 
GO
بعد در این حالت با داشتن یک چنین insertهایی :
using (var dataContext = new SampleDBContext())
{
    dataContext.Categories.Add(new Category() { CategoryName = "Clothing" });
    dataContext.Categories.Add(new Category() { CategoryName = "Footwear" });
    dataContext.Categories.Add(new Category() { CategoryName = "Accessories" });
    dataContext.SaveChanges();
    dataContext.Products.Add(new Product() { ProductName = "TShirts" });
    dataContext.Products.Add(new Product() { ProductName = "Shirts" });
    dataContext.Products.Add(new Product() { ProductName = "Causal Shoes" });
    dataContext.SaveChanges();
}
ابتدا یکبار Idهای مورد نیاز سه insert اول، دریافت می‌شوند:

و سپس کوئری insert اصلی، دارای idهای از پیش تعیین شده‌ای می‌شود که اینبار نیازی نیست تا یکی یکی رکوردها insert شوند و id بعدی محاسبه شود:

نظرات مطالب
مفهوم READ_COMMITTED_SNAPSHOT در EF 6
بحث اصلی هم همین نحوه و محل ذخیره سازی snapshot است.
- Sanpshot مطابق واژه نامه مایکروسافت معنای «نگارش» را می‌دهد. در این حالت کلیه کوئری‌های داخل یک تراکنش، یک نگارش یا snapshot از دیتابیس را مشاهده خواهند کرد. این نگارش یا Row version، در tempdb نگه داری می‌شود. با فعال سازی SNAPSHOT isolation، هر زمانیکه یک ردیف به روز رسانی می‌شود، موتور SQL Server یک نسخه از اطلاعات اولیه این ردیف را در tempdb ذخیره می‌کند (اینجا بود که عنوان شد با یک کپی فقط خواندنی از اطلاعات در حین واکشی اطلاعات سر و کار خواهید داشت).
خلاصه الگوریتم کاری آن :
الف) با آغاز یک تراکنش، یک عدد متوالی منحصربفرد تراکنش (شماره نگارش) ایجاد شده و به آن نسبت داده می‌شود.
ب) در حین این تراکنش، موتور SQL Server، به tempdb مراجعه کرده و شماره نگارشی نزدیک و کمتر از شماره نگارش تراکنش جاری را پیدا می‌کند. همچنین SQL Server بررسی می‌کند که این شماره یافت شده حتما جزو تراکنش‌های پایان یافته سیستم باشد.
ج) بر اساس این شماره یافت شده، نگارش معتبری از اطلاعات از tempdb استخراج می‌شود.
به این ترتیب یک تراکنش، کلیه اطلاعات موجود در ابتدای کار خود را بدون قرار دادن قفلی بر روی جداول مرتبط، دریافت خواهد کرد.
اطلاعات بیشتر

- در متن ذکر گردید که از SQL Server 2005 به بعد قابلیت فوق اضافه شده.
- همچنین SQL Server 2000 دیگر پشتیبانی رسمی ندارد و استفاده از آن حداقل از لحاظ امنیتی معقول نیست.