نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت اول - نصب پیشنیازها
به روز رسانی

در آخرین تغییرات وابستگی‌های AngularJS 2.0 این موارد لحاظ شده‌اند:
الف) در فایل package.json بجای es6-promise و es6-shim، فقط از core-js استفاده شده‌است:
    "dependencies": {
       // ...
        "core-js": "^2.4.0",
       // ...
    },
ب) فایل typings.json واقع در ریشه‌ی پروژه، جهت ذکر core-js به صورت زیر تغییر یافته است:
{
    "ambientDependencies": {
        "core-js": "registry:dt/core-js#0.0.0+20160317120654",
        "jasmine": "registry:dt/jasmine#2.2.0+20160505161446",
        "node": "registry:dt/node#4.0.0+20160509154515"
    }
}
ج) اینبار ابتدای فایل main.ts به صورت زیر تغییر می‌کند:
/// <reference path="../typings/browser/ambient/core-js/index.d.ts" />
/// <reference path="../typings/browser/ambient/node/index.d.ts" />
د) به علاوه محتوای فایل‌های systemjs.config.js و index.html نیز تغییر یافته‌اند.

یک نکته: اگر در پوشه‌ی typings قدیمی شما هنوز فایل‌های es6-shim موجود هستند، باید آن‌ها را حذف کنید. چون تعاریف آن، با تعاریف core-js جدید، تداخل می‌کنند و خطای «error TS2300: Duplicate identifier» را دریافت خواهید کرد.

پوشه‌ی typings جدید را از اینجا دریافت کنید: typings.zip
مطالب
طراحی افزونه پذیر با ASP.NET MVC 4.x/5.x - قسمت دوم
در مطلب «طراحی افزونه پذیر با ASP.NET MVC 4.x/5.x - قسمت اول» با ساختار کلی یک پروژه‌ی افزونه‌ی پذیر ASP.NET MVC آشنا شدیم. پس از راه اندازی آن و مدتی کار کردن با این نوع پروژه‌ها، این سؤال پیش خواهد آمد که ... خوب، اگر هر افزونه تصاویر یا فایل‌های CSS و JS اختصاصی خودش را بخواهد داشته باشد، چطور؟ موارد عمومی مانند بوت استرپ و جی‌کوئری را می‌توان در پروژه‌ی پایه قرار داد تا تمام افزونه‌ها به صورت یکسانی از آن‌ها استفاده کنند، اما هدف، ماژولار شدن برنامه است و جدا کردن فایل‌های ویژه‌ی هر پروژه، از پروژ‌ه‌ای دیگر و همچنین بالا بردن سهولت کار تیمی، با شکستن اجزای یک پروژه به صورت افزونه‌هایی مختلف، بین اعضای یک تیم. در این قسمت نحوه‌ی مدفون سازی انواع فایل‌های استاتیک افزونه‌ها را درون فایل‌های DLL آن‌ها بررسی خواهیم کرد. به این ترتیب دیگر نیازی به ارائه‌ی مجزای آن‌ها و یا کپی کردن آن‌ها در پوشه‌های پروژه‌ی اصلی نخواهد بود.


مدفون سازی فایل‌های CSS و JS هر افزونه درون فایل DLL آن

به solution جاری، یک class library جدید را به نام MvcPluginMasterApp.Common اضافه کنید. از آن جهت قرار دادن کلاس‌های عمومی و مشترک بین افزونه‌ها استفاده خواهیم کرد. برای مثال قصد نداریم کلاس‌های سفارشی و عمومی ذیل را هربار به صورت مستقیم در افزونه‌ای جدید کپی کنیم. کتابخانه‌ی Common، امکان استفاده‌ی مجدد از یک سری کدهای تکراری را در بین افزونه‌ها میسر می‌کند.
این پروژه برای کامپایل شدن نیاز به بسته‌ی نیوگت ذیل دارد:
 PM> install-package Microsoft.AspNet.Web.Optimization
همچنین باید به صورت دستی، در قسمت ارجاعات پروژه، ارجاعی را به اسمبلی استاندارد System.Web نیز به آن اضافه نمائید.
پس از این مقدمات، کلاس ذیل را به این پروژه‌ی class library جدید اضافه کنید:
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web.Optimization;
 
namespace MvcPluginMasterApp.Common.WebToolkit
{
    public class EmbeddedResourceTransform : IBundleTransform
    {
        private readonly IList<string> _resourceFiles;
        private readonly string _contentType;
        private readonly Assembly _assembly;
 
        public EmbeddedResourceTransform(IList<string> resourceFiles, string contentType, Assembly assembly)
        {
            _resourceFiles = resourceFiles;
            _contentType = contentType;
            _assembly = assembly;
        }
 
        public void Process(BundleContext context, BundleResponse response)
        {
            var result = new StringBuilder();
 
            foreach (var resource in _resourceFiles)
            {
                using (var stream = _assembly.GetManifestResourceStream(resource))
                {
                    if (stream == null)
                    {
                        throw new KeyNotFoundException(string.Format("Embedded resource key: '{0}' not found in the '{1}' assembly.", resource, _assembly.FullName));
                    }
 
                    using (var reader = new StreamReader(stream))
                    {
                        result.Append(reader.ReadToEnd());
                    }
                }
            }
 
            response.ContentType = _contentType;
            response.Content = result.ToString();
        }
    }
}
اگر با سیستم bundling & minification کار کرده باشید، با تعاریفی مانند ("new Bundle("~/Plugin1/Scripts آشنا هستید. سازنده‌ی کلاس Bundle، پارامتر دومی را نیز می‌پذیرد که از نوع IBundleTransform است. با پیاده سازی اینترفیس IBundleTransform می‌توان محل ارائه‌ی فایل‌های استاتیک CSS و JS را بجای فایل سیستم متداول و پیش فرض، به منابع مدفون شده‌ی در اسمبلی جاری هدایت و تنظیم کرد.
کلاس فوق در اسمبلی معرفی شده به آن، توسط متد GetManifestResourceStream به دنبال فایل‌ها و منابع مدفون شده گشته و سپس محتوای آن‌ها را بازگشت می‌دهد.
اکنون برای استفاده‌ی از آن، به پروژه‌ی MvcPluginMasterApp.Plugin1 مراجعه کرده و ارجاعی را به پروژه‌ی MvcPluginMasterApp.Common فوق اضافه نمائید. سپس در فایل Plugin1.cs، متد RegisterBundles آن‌را به نحو ذیل تکمیل کنید:
namespace MvcPluginMasterApp.Plugin1
{
    public class Plugin1 : IPlugin
    {
        public EfBootstrapper GetEfBootstrapper()
        {
            return null;
        }
 
        public MenuItem GetMenuItem(RequestContext requestContext)
        {
            return new MenuItem
            {
                Name = "Plugin 1",
                Url = new UrlHelper(requestContext).Action("Index", "Home", new { area = "NewsArea" })
            };
        }
 
        public void RegisterBundles(BundleCollection bundles)
        {
            var executingAssembly = Assembly.GetExecutingAssembly();
            // Mostly the default namespace and assembly name are the same
            var assemblyNameSpace = executingAssembly.GetName().Name;
            var scriptsBundle = new Bundle("~/Plugin1/Scripts",
                new EmbeddedResourceTransform(new List<string>
                {
                    assemblyNameSpace + ".Scripts.test1.js"
                }, "application/javascript", executingAssembly));
            if (!HttpContext.Current.IsDebuggingEnabled)
            {
                scriptsBundle.Transforms.Add(new JsMinify());
            }
            bundles.Add(scriptsBundle);
            var cssBundle = new Bundle("~/Plugin1/Content",
                new EmbeddedResourceTransform(new List<string>
                {
                    assemblyNameSpace + ".Content.test1.css"
                }, "text/css", executingAssembly));
            if (!HttpContext.Current.IsDebuggingEnabled)
            {
                cssBundle.Transforms.Add(new CssMinify());
            }
            bundles.Add(cssBundle);
            BundleTable.EnableOptimizations = true;
        }
 
        public void RegisterRoutes(RouteCollection routes)
        {
        }
 
        public void RegisterServices(IContainer container)
        {
        }
    }
}
در اینجا نحوه‌ی کار با کلاس سفارشی EmbeddedResourceTransform را مشاهده می‌کنید. ابتدا فایل‌های js و سپس فایل‌های css برنامه به سیستم Bundling برنامه اضافه شده‌اند.
این فایل‌ها به صورت ذیل در پروژه تعریف گردیده‌اند:


همانطور که مشاهده می‌کنید، باید به خواص هر کدام مراجعه کرد و سپس Build action آن‌ها را به embedded resource تغییر داد، تا در حین کامپایل، به صورت خودکار در قسمت منابع اسمبلی ذخیره شوند.

یک نکته‌ی مهم
اینبار برای مسیردهی منابع، باید بجای / فایل سیستم، از «نقطه» استفاده کرد. زیرا منابع با نام‌هایی مانند namespace.folder.name در قسمت resources یک اسمبلی ذخیره می‌شوند:



مدفون سازی تصاویر ثابت هر افزونه درون فایل DLL آن

مجددا به اسمبلی مشترک MvcPluginMasterApp.Common مراجعه کرده و اینبار کلاس جدید ذیل را به آن اضافه کنید:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Web;
using System.Web.Routing;
 
namespace MvcPluginMasterApp.Common.WebToolkit
{
    public class EmbeddedResourceRouteHandler : IRouteHandler
    {
        private readonly Assembly _assembly;
        private readonly string _resourcePath;
        private readonly TimeSpan _cacheDuration;
 
        public EmbeddedResourceRouteHandler(Assembly assembly, string resourcePath, TimeSpan cacheDuration)
        {
            _assembly = assembly;
            _resourcePath = resourcePath;
            _cacheDuration = cacheDuration;
        }
 
        IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
        {
            return new EmbeddedResourceHttpHandler(requestContext.RouteData, _assembly, _resourcePath, _cacheDuration);
        }
    }
 
    public class EmbeddedResourceHttpHandler : IHttpHandler
    {
        private readonly RouteData _routeData;
        private readonly Assembly _assembly;
        private readonly string _resourcePath;
        private readonly TimeSpan _cacheDuration;
 
        public EmbeddedResourceHttpHandler(
            RouteData routeData, Assembly assembly, string resourcePath, TimeSpan cacheDuration)
        {
            _routeData = routeData;
            _assembly = assembly;
            _resourcePath = resourcePath;
            _cacheDuration = cacheDuration;
        }
 
        public bool IsReusable
        {
            get { return false; }
        }
 
        public void ProcessRequest(HttpContext context)
        {
            var routeDataValues = _routeData.Values;
            var fileName = routeDataValues["file"].ToString();
            var fileExtension = routeDataValues["extension"].ToString();
 
            var manifestResourceName = string.Format("{0}.{1}.{2}", _resourcePath, fileName, fileExtension);
            var stream = _assembly.GetManifestResourceStream(manifestResourceName);
            if (stream == null)
            {
                throw new KeyNotFoundException(string.Format("Embedded resource key: '{0}' not found in the '{1}' assembly.", manifestResourceName, _assembly.FullName));
            }
 
            context.Response.Clear();
            context.Response.ContentType = "application/octet-stream";
            cacheIt(context.Response, _cacheDuration);
            stream.CopyTo(context.Response.OutputStream);
        }
 
        private static void cacheIt(HttpResponse response, TimeSpan duration)
        {
            var cache = response.Cache;
 
            var maxAgeField = cache.GetType().GetField("_maxAge", BindingFlags.Instance | BindingFlags.NonPublic);
            if (maxAgeField != null) maxAgeField.SetValue(cache, duration);
 
            cache.SetCacheability(HttpCacheability.Public);
            cache.SetExpires(DateTime.Now.Add(duration));
            cache.SetMaxAge(duration);
            cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
        }
    }
}
تصاویر پروژه‌ی افزونه نیز به صورت embedded resource در اسمبلی آن قرار خواهند گرفت. به همین جهت باید سیستم مسیریابی را پس درخواست رسیده‌ی جهت نمایش تصاویر، به منابع ذخیره شده‌ی در اسمبلی آن هدایت نمود. اینکار را با پیاده سازی یک IRouteHandler سفارشی، می‌توان به نحو فوق مدیریت کرد.
این IRouteHandler، نام و پسوند فایل را دریافت کرده و سپس به قسمت منابع اسمبلی رجوع، فایل مرتبط را استخراج و سپس بازگشت می‌دهد. همچنین برای کاهش سربار سیستم، امکان کش شدن منابع استاتیک نیز در آن درنظر گرفته شده‌است و هدرهای خاص caching را به صورت خودکار اضافه می‌کند.
سیستم bundling نیز هدرهای کش کردن را به صورت خودکار و توکار اضافه می‌کند.

اکنون به تعاریف Plugin1 مراجعه کنید و سپس این IRouteHandler سفارشی را به نحو ذیل به آن معرفی نمائید:
namespace MvcPluginMasterApp.Plugin1
{
    public class Plugin1 : IPlugin
    { 
        public void RegisterRoutes(RouteCollection routes)
        {
            //todo: add custom routes.
 
            var assembly = Assembly.GetExecutingAssembly();
            // Mostly the default namespace and assembly name are the same
            var nameSpace = assembly.GetName().Name;
            var resourcePath = string.Format("{0}.Images", nameSpace);
 
            routes.Insert(0,
                new Route("NewsArea/Images/{file}.{extension}",
                    new RouteValueDictionary(new { }),
                    new RouteValueDictionary(new { extension = "png|jpg" }),
                    new EmbeddedResourceRouteHandler(assembly, resourcePath, cacheDuration: TimeSpan.FromDays(30))
                ));
        } 
    }
}
در مسیریابی تعریف شده، تمام درخواست‌های رسیده‌ی به مسیر NewsArea/Images به EmbeddedResourceRouteHandler هدایت می‌شوند.
مطابق تعریف آن، file و extension به صورت خودکار جدا شده و توسط routeData.Values در متد ProcessRequest کلاس EmbeddedResourceHttpHandler قابل دسترسی خواهند شد.
پسوندهایی که توسط آن بررسی می‌شوند از نوع png یا jpg تعریف شده‌اند. همچنین مدت زمان کش کردن هر منبع استاتیک تصویری به یک ماه تنظیم شده‌است.


استفاده‌ی نهایی از تنظیمات فوق در یک View افزونه

پس از اینکه تصاویر و فایل‌های css و js را به صورت embedded resource تعریف کردیم و همچنین تنظیمات مسیریابی و bundling خاص آن‌ها را نیز مشخص نمودیم، اکنون نوبت به استفاده‌ی از آن‌ها در یک View است:
@{
    ViewBag.Title = "From Plugin 1";
}
@Styles.Render("~/Plugin1/Content")
 
<h2>@ViewBag.Message</h2>
 
<div class="row">
    Embedded image:
    <img src="@Url.Content("~/NewsArea/Images/chart.png")" alt="clock" />
</div>
 
@section scripts
{
    @Scripts.Render("~/Plugin1/Scripts")
}
در اینجا نحوه‌ی تعریف فایل‌های CSS و JS ارائه شده‌ی توسط سیستم Bundling را مشاهده می‌کنید.
همچنین مسیر تصویر مشخص شده‌ی در آن، اینبار یک NewsArea اضافه‌تر دارد. فایل اصلی تصویر، در مسیر Images/chart.png قرار گرفته‌است اما می‌خواهیم این درخواست‌ها را به مسیریابی جدید {NewsArea/Images/{file}.{extension هدایت کنیم. بنابراین نیاز است به این نکته نیز دقت داشت.

اینبار اگر برنامه را اجرا کنیم، می‌توان به سه نکته در آن دقت داشت:


الف) alert اجرا شده از فایل js مدفون شده خوانده شده‌است.
ب) رنگ قرمز متن (تگ h2) از فایل css مدفون شده، گرفته شده‌است.
ج) تصویر نمایش داده شده، همان تصویر مدفون شده‌ی در فایل DLL برنامه است.
و هیچکدام از این فایل‌ها، به پوشه‌های پروژه‌ی اصلی برنامه، کپی نشده‌اند.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
 MvcPluginMasterApp-Part2.zip
مطالب
ویژگی Batching در EF Core
در EF 6.x به ازای هر عبارت insert/update/delete یکبار رفت و برگشت به بانک اطلاعاتی صورت می‌گیرد. به همین جهت کارآیی تعداد بالای ثبت، به روز رسانی و حذف رکوردها توسط آن پایین است. برای رفع این مشکل ویژگی Batching به EF Core اضافه شده‌است که توسط آن اینبار دسته‌ای از عبارات را به صورت یکجا و در طی یک رفت و برگشت، به سمت بانک اطلاعاتی ارسال می‌کند. به این ترتیب کارآیی و سرعت insert/update/delete آن به شدت افزایش خواهد یافت.


نحوه‌ی فعالسازی Batching در EF Core

Batching به صورت پیش فرض در EF Core بدون نیاز به هیچگونه تنظیم اضافه‌تری فعال است. اما اگر خواستید برای مثال، حالت پیش فرض EF 6.x را توسط آن شبیه سازی کنید، می‌توانید مقدار MaxBatchSize را به عدد 1 تنظیم نمائید (تا غیرفعال شود):
optionsBuilder.UseSqlServer(
   @"Server=(localdb)\mssqllocaldb;Database=Demo.Batching;Trusted_Connection=True;",
   options => options.MaxBatchSize(1)
);

مقدار پیش فرض MaxBatchSize را در کلاس SqlServerModificationCommandBatch می‌توانید مشاهده کنید:
public class SqlServerModificationCommandBatch : AffectedCountModificationCommandBatch
    {
        private const int DefaultNetworkPacketSizeBytes = 4096;
        private const int MaxScriptLength = 65536 * DefaultNetworkPacketSizeBytes / 2;
        private const int MaxParameterCount = 2100;
        private const int MaxRowCount = 1000;
در اینجا MaxRowCount همان MaxBatchSize پیش فرض است که به عدد 1000 تنظیم شده‌است. بنابراین اگر تنظیم options => options.MaxBatchSize(1) را ذکر نکنید، به معنای ارسال 1000 تایی دستورات insert/update/delete در طی یک درخواست به سمت سرور است.


آیا محدودیتی هم در مورد عملیات Batching وجود دارد؟

SQL Server به ازای هر batch تنها 2100 پارامتر را پشتیبانی می‌کند. در این حالت EF Core به صورت خودکار یک چنین کوئری‌های حجیمی را به چند Batch جهت تنظیم این محدودیت تقسیم خواهد کرد و در نهایت برنامه به مشکلی بر نمی‌خورد.


یک آزمایش: Batching پیش فرض به چه صورتی کار می‌کند و چه اثری را دارد؟

کدهای کامل این آزمایش را از اینجا می‌توانید دریافت کنید: Batching.zip
در اینجا کلاس Blog را به همراه Context متناظر با آن مشاهده می‌کنید:
    public class Blog
    {
        public int BlogId { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
    }

    public class BloggingContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(
                @"Server=(localdb)\mssqllocaldb;Database=Demo.Batching;Trusted_Connection=True;"/*,
                options => options.MaxBatchSize(2)*/
                );
            optionsBuilder.EnableSensitiveDataLogging();
        }
    }
در ابتدا MaxBatchSize را تنظیم نخواهیم کرد. یعنی از همان عدد 1000 پیش فرض استفاده می‌شود. تنظیم EnableSensitiveDataLogging نیز سبب می‌شود تا لاگ نهایی تهیه شده جهت نمایش، پرمحتواتر شود.
در این حالت اگر به روز رسانی‌ها (2 مورد) و ثبت‌های ذیل (6 مورد) را انجام دهیم:
            using (var db = new BloggingContext())
            {
                db.GetService<ILoggerFactory>().AddProvider(new MyLoggerProvider());

                // Modify some existing blogs
                var existing = db.Blogs.ToArray();
                existing[0].Url = "http://sample.com/blogs/dogs";
                existing[1].Url = "http://sample.com/blogs/cats";

                // Insert some new blogs
                db.Blogs.Add(new Blog { Name = "The Horse Blog", Url = "http://sample.com/blogs/horses" });
                db.Blogs.Add(new Blog { Name = "The Snake Blog", Url = "http://sample.com/blogs/snakes" });
                db.Blogs.Add(new Blog { Name = "The Fish Blog", Url = "http://sample.com/blogs/fish" });
                db.Blogs.Add(new Blog { Name = "The Koala Blog", Url = "http://sample.com/blogs/koalas" });
                db.Blogs.Add(new Blog { Name = "The Parrot Blog", Url = "http://sample.com/blogs/parrots" });
                db.Blogs.Add(new Blog { Name = "The Kangaroo Blog", Url = "http://sample.com/blogs/kangaroos" });

                db.SaveChanges();
            }
یک چنین خروجی SQL ایی تولید می‌شود:
Executed DbCommand (41ms) [Parameters=[@p1='57', @p0='http://sample.com/blogs/dogs' (Size = 4000), @p3='58', @p2='http://sample.com/blogs/cats' (Size = 4000), @p4='The Horse Blog' (Size = 4000), @p5='http://sample.com/blogs/horses' (Size = 4000), @p6='The Snake Blog' (Size = 4000), @p7='http://sample.com/blogs/snakes' (Size = 4000), @p8='The Fish Blog' (Size = 4000), @p9='http://sample.com/blogs/fish' (Size = 4000), @p10='The Koala Blog' (Size = 4000), @p11='http://sample.com/blogs/koalas' (Size = 4000), @p12='The Parrot Blog' (Size = 4000), @p13='http://sample.com/blogs/parrots' (Size = 4000), @p14='The Kangaroo Blog' (Size = 4000), @p15='http://sample.com/blogs/kangaroos' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [Blogs] SET [Url] = @p0
WHERE [BlogId] = @p1;
SELECT @@ROWCOUNT;

UPDATE [Blogs] SET [Url] = @p2
WHERE [BlogId] = @p3;
SELECT @@ROWCOUNT;

DECLARE @inserted2 TABLE ([BlogId] int, [_Position] [int]);
MERGE [Blogs] USING (
VALUES (@p4, @p5, 0),
(@p6, @p7, 1),
(@p8, @p9, 2),
(@p10, @p11, 3),
(@p12, @p13, 4),
(@p14, @p15, 5)) AS i ([Name], [Url], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Name], [Url])
VALUES (i.[Name], i.[Url])
OUTPUT INSERTED.[BlogId], i._Position
INTO @inserted2;

SELECT [t].[BlogId] FROM [Blogs] t
INNER JOIN @inserted2 i ON ([t].[BlogId] = [i].[BlogId])
ORDER BY [i].[_Position];
در این دستورات موارد ذیل قابل توجه هستند:
- فقط یکبار Executed DbCommand مشاهده می‌شود.
- کل دستورات update و insert در طی یک درخواست و یک تراکنش به سمت بانک اطلاعاتی ارسال شده‌اند.
- ثبت دسته‌ای توسط merge using انجام شده‌است.
- در آخر نیز طبق معمول کار EF، شماره Idهای رکوردهای ثبت شده به سمت کلاینت بازگشت داده می‌شود.

در ادامه MaxBatchSize را به عدد 2 تنظیم می‌کنیم:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
   optionsBuilder.UseSqlServer(
     @"Server=(localdb)\mssqllocaldb;Database=Demo.Batching;Trusted_Connection=True;",
     options => options.MaxBatchSize(2)
    );
    optionsBuilder.EnableSensitiveDataLogging();
}
در این حالت اگر برنامه را اجرا کنیم، یک چنین خروجی قابل مشاهده خواهد بود:
Executed DbCommand (17ms) [Parameters=[@p1='65', @p0='http://sample.com/blogs/dogs' (Size = 4000), @p3='66', @p2='http://sample.com/blogs/cats' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [Blogs] SET [Url] = @p0
WHERE [BlogId] = @p1;
SELECT @@ROWCOUNT;

UPDATE [Blogs] SET [Url] = @p2
WHERE [BlogId] = @p3;
SELECT @@ROWCOUNT;

Executed DbCommand (18ms) [Parameters=[@p0='The Horse Blog' (Size = 4000), @p1='http://sample.com/blogs/horses' (Size = 4000), @p2='The Snake Blog' (Size = 4000), @p3='http://sample.com/blogs/snakes' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([BlogId] int, [_Position] [int]);
MERGE [Blogs] USING (
VALUES (@p0, @p1, 0),
(@p2, @p3, 1)) AS i ([Name], [Url], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Name], [Url])
VALUES (i.[Name], i.[Url])
OUTPUT INSERTED.[BlogId], i._Position
INTO @inserted0;

SELECT [t].[BlogId] FROM [Blogs] t
INNER JOIN @inserted0 i ON ([t].[BlogId] = [i].[BlogId])
ORDER BY [i].[_Position];

Executed DbCommand (34ms) [Parameters=[@p0='The Fish Blog' (Size = 4000), @p1='http://sample.com/blogs/fish' (Size = 4000), @p2='The Koala Blog' (Size = 4000), @p3='http://sample.com/blogs/koalas' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([BlogId] int, [_Position] [int]);
MERGE [Blogs] USING (
VALUES (@p0, @p1, 0),
(@p2, @p3, 1)) AS i ([Name], [Url], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Name], [Url])
VALUES (i.[Name], i.[Url])
OUTPUT INSERTED.[BlogId], i._Position
INTO @inserted0;

SELECT [t].[BlogId] FROM [Blogs] t
INNER JOIN @inserted0 i ON ([t].[BlogId] = [i].[BlogId])
ORDER BY [i].[_Position];

Executed DbCommand (15ms) [Parameters=[@p0='The Parrot Blog' (Size = 4000), @p1='http://sample.com/blogs/parrots' (Size = 4000), @p2='The Kangaroo Blog' (Size = 4000), @p3='http://sample.com/blogs/kangaroos' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([BlogId] int, [_Position] [int]);
MERGE [Blogs] USING (
VALUES (@p0, @p1, 0),
(@p2, @p3, 1)) AS i ([Name], [Url], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Name], [Url])
VALUES (i.[Name], i.[Url])
OUTPUT INSERTED.[BlogId], i._Position
INTO @inserted0;

SELECT [t].[BlogId] FROM [Blogs] t
INNER JOIN @inserted0 i ON ([t].[BlogId] = [i].[BlogId])
ORDER BY [i].[_Position];
در این دستورات موارد ذیل قابل توجه هستند:
- اینبار تعداد 4 دستور Executed DbCommand مشاهده می‌شود ( برای انجام 2 به روز رسانی و 6 ثبت).
- هر batch بر اساس تنظیم MaxBatchSize به 2 دستور T-SQL محدود شده‌است که البته در انتها در حالت‌های insert، یک select هم برای بازگشت Idها به سمت کلاینت وجود دارد.
بنابراین اینبار بجای یکبار رفت و برگشت حالت قبل (استفاده از مقدار پیش فرض 1000 برای MaxBatchSize)، 4 بار رفت و برگشت به سمت بانک اطلاعاتی صورت گرفته‌است.

زمان کل انجام عملیات در حالت اول 41 میلی ثانیه و در حالت دوم 84 میلی ثانیه است که سرعت آن 51 درصد نسبت به حالت اول کاهش یافته‌است.
اشتراک‌ها
معانی نگارش‌های مختلف زبان #C
LangVersion Meaning
LatestMajor Latest supported major C# language version
Preview    Latest available preview C# language version
Latest     Latest supported C# language version (including minor version)
ISO-1      C# 1.0/1.2
ISO-2      C# 2.0
3          C# 3.0
4          C# 4.0
5          C# 5.0
6          C# 6.0
7          C# 7.0
7.1        C# 7.1
7.2        C# 7.2
7.3        C# 7.3
8.0        C# 8.0
معانی نگارش‌های مختلف زبان #C
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 14 - لایه بندی و تزریق وابستگی‌ها
با عرض سلام و تشکر
1. در پروژه برای کلاس Product و Category، مثالی برای اعمال CRUD در سطح کنترلر ارائه نشده، یا من پیدا نکردم؟ یا چون در بحث Identity نبوده تکمیل نشده این قسمت.
در این پروژه آیا برای انجام عملیات CRUD، به روشی که در پروژه «اعتبار سنجی مبتنی بر Jwt در ASP.net Core 2.0 بدون استفاده از سیستم Identity» معرفی کردین عمل کنم ؟

2. تفاوت طراحی این دو پروژه در قسمتهای تزریق وابستگی، و دقیقا انجام عملیات CRUD در کنترلرهای سطح پروژه وب، چیست ؟

نظرات مطالب
تنظیمات مورد نیاز جهت شروع به کار با C# 9.0
یک نکته‌ی تکمیلی: شماره نگارش زبان پیش‌فرض نگارش‌های مختلف NET.
Target framework       version         C# language version default
.NET                   5.x             C# 9.0
.NET Core              3.x             C# 8.0
.NET Core              2.x             C# 7.3
.NET Standard          2.1             C# 8.0
.NET Standard          2.0             C# 7.3
.NET Standard          1.x             C# 7.3
.NET Framework         all             C# 7.3
نظرات مطالب
آشنایی با Window Function ها در SQL Server بخش دوم
سلام
اگر سئوال شما رو درست متوجه شده باشم،با یک مثال مفهوم Range رو بررسی می‌کنیم:
CREATE TABLE #Transactions
(
AccountId INTEGER,
TranDate DATE,
TranAmt NUMERIC(8, 2)
);
INSERT INTO #Transactions
SELECT *
FROM ( VALUES ( 1, '2011-01-15', 50),( 1, '2011-01-17', 500),( 1, '2011-01-17', 500),
  ( 1, '2011-01-16', 500),( 1, '2011-01-24', 75),( 1, '2011-01-26', 125),
  ( 1, '2011-02-28', 500),( 2, '2011-01-01', 500),( 2, '2011-01-15', 50),
              ( 2, '2011-01-22', 25),( 2, '2011-01-23', 125),( 2, '2011-01-26', 200),
              ( 2, '2011-01-29', 250),( 3, '2011-01-01', 500),( 3, '2011-01-15', 50 ),
              ( 3, '2011-01-22', 5000),( 3, '2011-01-25', 550),( 3, '2011-01-27', 95 ),
              ( 3, '2011-01-30', 2500)
) dt (AccountId, TranDate, TranAmt);
روی جدول فوق دو نوع Script اجرا می‌کنیم، مثال اول، براساس AccountID جدول را گروه بندی می‌نماییم. سپس هر گروه را براساس تاریخ Sort می‌کنیم، و در هر گروه مقدار Sum آن را بدست می‌آوریم:
SELECT  
    AccountId,   
    TranDate,
  TranAmt, 
    Sum(TranAmt) OVER(partition by Accountid ORDER BY TranDate RANGE UNBOUNDED PRECEDING) AS SumAmt  
FROM  #Transactions 
GO
خروجی : 

مطابق شکل Sort براساس TranDate است، که چهار مقدار 500  در سه بازه تاریخی دیده می‌شود، حال محاسبه جمع هر سطر بصورت زیر است:
سطر دوم با وجود اینکه مقدار آن 500 است و در بازه تاریخی 16-01-2011 قرار دارد: مقدار آن برابر است با 550=50 + 500 
سطر سوم و چهارم که در بازه تاریخی 17-01-2011 می‌باشد(به عبارتی در یک محدوده می‌باشند): برابر است با : 1550=50+500+500+500 
در اینجا چیزی حذف نشده، حاصل جمع سطر سوم و چهارم ، چون در یک محدوده (Range) می‌باشد، برابر است با حاصل جمع سطر‌های ما قبل یعنی سطر اول و دوم (550=50+500) + حاصل جمع تمامی سطرهای آن محدوده(Range)، یعنی سطر سوم و چهارم (1000=500+500)
مثال دیگر، در این حالت Sort روی فیلد TranAMT انجام می‌شود، و جدول همچنان روی فیلد Accountid گروه بندی می‌شود، بنابراین خواهیم داشت:
SELECT  
    AccountId,   
    TranDate,
  TranAmt, 
    Sum(TranAmt) OVER(partition by Accountid ORDER BY TranAmt RANGE UNBOUNDED PRECEDING) AS SumAmt  
FROM  #Transactions 
GO
خروجی :

در شکل، مقدار جمع هیچ سطری حذف نشده است، و Top ی هم در کار نیست.
حال اگر مثال فوق را روی میانگین در نظر بگیرید، باز هم تمام مقادیر، در محاسبه میانگین تاثیر گذار میباشند.
نظرات مطالب
EF Code First #1
- رشته اتصالی به SQL Server حالت‌های مختلفی می‌تواند داشته باشد. اطلاعات بیشتر
Data Source آن معمولا نام کامپیوتر جاری است یا IP Server. چون در تصویر شما instance name خالی است، از همان وهله‌ی پیش فرض استفاده می‌شود. اگر مقدار داشت می‌شد computer_name/instance_name
Initial Catalog نام بانک اطلاعاتی مدنظر است که قرار است به آن متصل شوید (یا در اینجا به صورت خودکار ساخته شود).
Integrated Security = true به معنای استفاده از اعتبارسنجی ویندوزی است برای اتصال به SQL Server. یعنی کاربر جاری لاگین کرده به سیستم باید دسترسی لازم را برای کار با SQL Server داشته باشد.
- برای فراگیری یک فناوری جدید از برنامه‌های کنسول استفاده کنید و نه ASP.NET. این مباحث عمومی است بین فناوری‌های مختلف استفاده کننده از آن. در یک برنامه‌ی کنسول آغاز کار از متد Main است؛ در یک برنامه‌ی وب از متد Application_Start فایل global.asax.cs خواهد بود.
نظرات مطالب
مقایسه امنیت Oracle11g و SQL server 2008 از دید آمار در سال 2009
یه موضوعی که تو این آمار بهش اشاره نشده تعداد و میزان حرفه ای بودن پروژه هایی هست که دارن از این دیتابیس ها استفاده میکنن. ممکنه این آمار اینجوری بدست اومده باشه که مثلا 10 تا سرور اوراکل تپل رو اومده مقایسه کرده باشه با 2تا سرور اس کیو ال معمولی و در این حالت باگ ها رو شمرده باشه. طبیعتا باگ های امنیتی اونی که در پروژه های بیشتری استفاده شده و سطح پروژه هم اینترپرایزتر هست ملموس تر هست. مثله قضیه ویندوز و لینوکس منظورم هست که اگه فراگیری ویندوز رو لینوکس هم داشت مطمئنا مورد حمله های بیشتری به نسبت ویندوز قرار می گرفت.
نظرات نظرسنجی‌ها
کدام یک از فریمورک‌های زیر را جهت تولید نرم افزارهای وب پایه SPA مناسب می‌دانید؟
موافقم. البته در مقایسه منصفانه پارامترهای دیگری نظیر فراگیری فریم ورک (محبوبیت) و بدنبال اون پشتیبانی شرکتهای ثالث (third party) نیز نقش مهمی در انتخاب یک فریم ورک دارند که متاسفانه فعلا انگولار در کسب موفقیت در این پارامتر‌ها فاصله داره. بطور مثال React جدا از خود facebook توسط hellosign netflix و چندی دیگر از کمپانی‌های صاحب نام مورد استفاده قرار گرفته هرچند React از نظر ساختاری بیشتر ابزاره تا فریمورک کاملی چون انگولار ولی انتخاب منطقی‌تر توسط شرکتهای بزرگ معمولا انتخاب گزینه ای با ریسک کمتر بوده..
به نظر من انگولار با عرضه نسخه نهایی این قابلیت رو داره که در مدت کمی تغییرات قابل توجهی رو در توسعه نرم افزارهای تحت web بده