مطالب
شروع به کار با EF Core 1.0 - قسمت 3 - انتقال مهاجرت‌ها به یک اسمبلی دیگر
در قسمت قبل، تغییرات Migrations، در EF Core 1.0 بررسی و گردش کاری آن به همراه مثال‌هایی ارائه شدند. در این قسمت یک سری از نکات تکمیلی EF Core Migrations را بررسی خواهیم کرد.


انتقال Context و Migrations به یک اسمبلی دیگر

تا اینجا اگر مثال بررسی شده را دنبال کرده باشید، دو پوشه‌ی Entities و Migrations را به همراه فایل‌‌های موجودیت‌ها، Context برنامه و Migrations آن‌ها، در همان پروژه‌ی اصلی برنامه، خواهید داشت:


در ادامه قصد داریم بانک اطلاعاتی آزمایشی برنامه را drop کرده، پوشه‌ی Migrations را حذف و صرفا دو فایل ApplicationDbContextSeedData و DBInitialization آن‌را نگه داریم.
کلاس Person را به اسمبلی جدید Entities و کلاس ApplicationDbContext را به اسمبلی جدید DataLayer منتقل می‌کنیم:


اسمبلی جدید Core1RtmEmptyTest.Entities از نوع NET Core Class Library. است و صرفا حاوی کلاس‌های موجودیت‌های برنامه‌است.
اسمبلی جدید Core1RtmEmptyTest.DataLayer نیز از نوع NET Core Class Library. بوده و حاوی تعاریف Context برنامه، به همراه Migrations و تنظیمات آن خواهد بود.

تا اینجا با این نقل و انتقالات، نیاز است وابستگی‌های DataLayer را اصلاح کنیم. بنابراین فایل project.json آن‌را گشوده و به نحو ذیل تکمیل نمائید:
{
  "version": "1.0.0-*",

    "dependencies": {
        "Core1RtmEmptyTest.Entities": "1.0.0-*",
        "Microsoft.EntityFrameworkCore": "1.0.0",
        "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
        "Microsoft.Extensions.Configuration.Abstractions": "1.0.0",
        "NETStandard.Library": "1.6.0"
    },

  "frameworks": {
    "netstandard1.6": {
      "imports": "dnxcore50"
    }
  }
}
به این صورت ارجاعی به اسمبلی Core1RtmEmptyTest.Entities به پروژه اضافه شده‌است (تا کلاس Person در ApplicationDbContext شناسایی شود) به همراه وابستگی‌های EF و SQL Server که مورد نیاز Context برنامه هستند.
وابستگی Microsoft.Extensions.Configuration.Abstractions برای کار با IConfigurationRoot اضافه شده‌است (دسترسی به تنظیمات برنامه از طریق تزریق وابستگی‌ها).

به علاوه اکنون به پروژه‌ی وب اصلی مراجعه کرده و فایل project.json آن‌را جهت افزودن ارجاعاتی به این دو اسمبلی جدید، ویرایش کنید:
{
    "dependencies": {
        // same as before
        "Core1RtmEmptyTest.Entities": "1.0.0-*",
        "Core1RtmEmptyTest.DataLayer": "1.0.0-*"
    }
}
به این ترتیب Startup برنامه می‌تواند محل جدید کلاس ApplicationDbContext را شناسایی کند و برنامه کامپایل شود.


فعال سازی Migrations و قرار دادن فایل‌های آن در اسمبلی Core1RtmEmptyTest.DataLayer

در ادامه اگر مانند قسمت قبل بخواهیم مهاجرت‌ها را اضافه کنیم، به خطای ذیل خواهیم رسید:
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest>dotnet ef migrations add InitialDatabase
Your target project 'Core1RtmEmptyTest' doesn't match your migrations assembly 'Core1RtmEmptyTest.DataLayer'.
Either change your target project or change your migrations assembly.
برای حل این مشکل، بجای اینکه دستور فوق را از مسیر src\Core1RtmEmptyTest صادر کنیم که همان ریشه‌ی اصلی پروژه‌ی وب است، اینبار باید دستور را از ریشه‌ی پروژه DataLayer صادر کنیم. اما اگر چنین کاری را انجام دهیم، پیام یافتن نشدن فایل اجرایی ابزارهای خط فرمان EF را دریافت می‌کنیم:
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest.DataLayer>dotnet ef migrations add InitialDatabase
No executable found matching command "dotnet-ef"
علت اینجا است که باید مجددا فایل Core1RtmEmptyTest.DataLayer\project.json را گشوده و این ابزارها را در آن فعال کنیم:
{
     // same as before

    "tools": {
        "Microsoft.EntityFrameworkCore.Tools": {
            "version": "1.0.0-preview2-final",
            "imports": [
                "portable-net45+win8"
            ]
        }
    },

     // same as before
}
پس از فعال سازی ابزارهای EF در پروژه‌ی DataLayer، اکنون باز هم موفق به اجرای دستور فوق نخواهیم شد:
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest.DataLayer>dotnet ef migrations add InitialDatabase
Could not invoke this command on the startup project 'Core1RtmEmptyTest.DataLayer'.
This preview of Entity Framework tools does not support commands on class library projects in ASP.NET Core and .NET Core applications.
عنوان می‌کند که پروژه‌ی startup را نمی‌تواند پیدا کند، برای حل این مشکل، دستور را به نحو ذیل ویرایش کنید:
 D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest.DataLayer>dotnet ef --startup-project ../Core1RtmEmptyTest/ migrations add InitialDatabase
Done. To undo this action, use 'dotnet ef migrations remove'
در اینجا با ذکر صریح startup-project، عملیات تولید فایل‌های Migrations با موفقیت انجام شدند:



اعمال کلاس‌های Migrations تولید شده به بانک اطلاعاتی

پس از تولید موفقیت آمیز فایل‌های مهاجرت، برای اعمال آن‌ها به بانک اطلاعاتی، اینبار نیز دستور را از همان پوشه‌ی DataLayer با پارامتر پروژه‌ی آغازین اجرا می‌کنیم:
D:\Prog\1395\Core1RtmEmptyTest\src\Core1RtmEmptyTest.DataLayer>dotnet ef --startup-project ../Core1RtmEmptyTest/ database update
Applying migration '13950527070105_InitialDatabase'.
Done.
در اینجا نیز ذکر پارامتر startup-project جهت اجرای موفقیت آمیز دستور الزامی است.


بنابراین به صورت خلاصه

- ابتدا قسمت tools تنظیمات پروژه‌ی data layer را برای فعال سازی دستورات خط فرمان EF ویرایش کنید.
- سپس از طریق خط فرمان به پوشه‌ی data layer وارد شوید. اینبار باید دستورات EF را از ریشه‌ی این پوشه، بجای پوشه‌ی اصلی برنامه صادر کرد.
- در اینجا دستورات افزودن مهاجرت‌ها و به روز رسانی بانک اطلاعاتی، همانند قبل هستند. فقط ذکر محل واقع شدن پوشه‌ی آغازین برنامه توسط پارامتر startup-project الزامی است.
مطالب
ایجاد جداول بهینه سازی شده برای حافظه در SQL Server 2014
پس از نگاهی به مفاهیم مقدماتی OLTP درون حافظه‌ای در SQL Server 2014، در ادامه به نحوه‌ی انجام تنظیمات خاص جداول بهینه سازی شده برای حافظه خواهیم پرداخت.


ایجاد یک بانک اطلاعاتی با پشتیبانی از جداول بهینه سازی شده برای حافظه

برای ایجاد جداول بهینه سازی شده برای حافظه، ابتدا نیاز است تا تنظیمات خاصی را به بانک اطلاعاتی آن اعمال کنیم. برای اینکار می‌توان یک بانک اطلاعاتی جدید را به همراه یک filestream filegroup ایجاد کرد که جهت جداول بهینه سازی شده برای حافظه، ضروری است؛ یا اینکه با تغییر یک بانک اطلاعاتی موجود و افزودن filegroup یاد شده نیز می‌توان به این مقصود رسید.
در اینگونه جداول خاص، اطلاعات در حافظه‌ی سیستم ذخیره می‌شوند و برخلاف جداول مبتنی بر دیسک سخت، صفحات اطلاعات وجود نداشته و نیازی نیست تا به کش بافر وارد شوند. برای مقاصد ذخیره سازی نهایی اطلاعات جداول بهینه سازی شده برای حافظه، موتور OLTP درون حافظه‌ای آن، فایل‌های خاصی را به نام checkpoint در یک filestream filegroup ایجاد می‌کند که از آن‌ها جهت ردیابی اطلاعات استفاده خواهد کرد و نحوی ذخیره سازی اطلاعات در آن‌ها از شیوه‌ی با کارآیی بالایی به نام append only mode پیروی می‌کند.
با توجه به متفاوت بودن نحوه‌ی ذخیره سازی نهایی اطلاعات اینگونه جداول و دسترسی به آن‌ها از طریق استریم‌ها، توصیه شده‌است که filestream filegroup‌های تهیه شده را در یک SSD یا Solid State Drive قرار دهید.

پس از اینکه بانک اطلاعاتی خود را به روش‌های معمول ایجاد کردید، به برگه‌ی خواص آن در management studio مراجعه کنید. سپس صفحه‌ی file groups را در آن انتخاب کرده و در پایین برگه‌ی آن، در قسمت جدید memory optimized data، بر روی دکمه‌ی Add کلیک کنید. سپس نام دلخواهی را وارد نمائید.


پس از ایجاد یک گروه فایل جدید، به صفحه‌ی files خواص بانک اطلاعاتی مراجعه کرده و بر روی دکمه‌ی Add کلیک کنید. سپس File type این ردیف اضافه شده را از نوع file stream data و file group آن‌را همان گروه فایلی که پیشتر ایجاد کردیم، تنظیم کنید. در ادامه logical name دلخواهی را وارد کرده و در آخر بر روی دکمه‌ی Ok کلیک کنید تا تنظیمات مورد نیاز جهت تعاریف جدول بهینه سازی شده برای حافظه به پایان برسد.


این مراحل را توسط دو دستور T-SQL ذیل نیز می‌توان سریعتر انجام داد:
USE [master]
GO
ALTER DATABASE [testdb2] 
      ADD FILEGROUP [InMemory_InMemory] CONTAINS MEMORY_OPTIMIZED_DATA 
GO
ALTER DATABASE [testdb2] 
      ADD FILE ( NAME = N'InMemory_InMemory', FILENAME = N'D:\SQL_Data\MSSQL11.MSSQLSERVER\MSSQL\DATA\InMemory_InMemory' ) 
      TO FILEGROUP [InMemory_InMemory]
GO

ساختار گروه فایل بهینه سازی شده برای حافظه

گروه فایل بهینه سازی شده برای حافظه، دارای چندین دربرگیرنده است که هر کدام چندین فایل را در خود جای خواهند داد:
- Root File که در برگیرنده‌ی متادیتای اطلاعات است.
- Data File که شامل ردیف‌های اطلاعات ثبت شده در جداول بهینه سازی شده‌ی برای حافظه هستند. این ردیف‌ها همواره به انتهای data file اضافه می‌شوند و دسترسی به آن‌ها ترتیبی است. کارآیی IO این روش نسبت به روش دسترسی اتفاقی به مراتب بالاتر است. حداکثر اندازه این فایل 128 مگابایت است و پس از آن یک فایل جدید ساخته می‌شود.
- Delta File شامل ردیف‌هایی است که حذف شده‌اند. به ازای هر ردیف، حداقل اطلاعاتی از آن را در خود ذخیره خواهد کرد؛ شامل ID ردیف حذف شده و شماره تراکنش آن. همانطور که پیشتر نیز ذکر شد، این موتور جدید درون حافظه‌ای، برای یافتن راه چاره‌ای جهت به حداقل رسانی قفل گذاری بر روی اطلاعات، چندین نگارش از ردیف‌ها را به همراه timestamp آن‌ها در خود ذخیره می‌کند. به این ترتیب، هر به روز رسانی به همراه یک حذف و سپس ثبت جدید است. به این ترتیب دیگر بانک اطلاعاتی نیازی نخواهد داشت تا به دنبال رکورد موجود برگردد و سپس اطلاعات آن‌را به روز نماید. این موتور جدید فقط اطلاعات به روز شده را در انتهای رکوردهای موجود با فرمت خود ثبت می‌کند.


ایجاد جداول بهینه سازی شده برای حافظه

پس از آماده سازی بانک اطلاعاتی خود و افزودن گروه فایل استریم جدیدی به آن برای ذخیره سازی اطلاعات جداول بهینه سازی شده برای حافظه، اکنون می‌توانیم اینگونه جداول خاص را در کنار سایر جداول متداول موجود، تعریف و استفاده نمائیم:
-- It is not a Memory Optimized
CREATE TABLE tblNormal
(
   [CustomerID] int NOT NULL PRIMARY KEY NONCLUSTERED, 
   [Name] nvarchar(250) NOT NULL,
   CustomerSince DATETIME not NULL
      INDEX [ICustomerSince] NONCLUSTERED
)

--  DURABILITY = SCHEMA_AND_DATA
CREATE TABLE tblMemoryOptimized_Schema_And_Data
(
    [CustomerID] INT NOT NULL 
PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1000000),
    [Name] NVARCHAR(250) NOT NULL,
    [CustomerSince] DATETIME NOT NULL
INDEX [ICustomerSince] NONCLUSTERED
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA)


-- DURABILITY = SCHEMA_ONLY
CREATE TABLE tblMemoryOptimized_Schema_Only
(
    [CustomerID] INT NOT NULL 
PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1000000),
    [Name] NVARCHAR(250) NOT NULL,
    [CustomerSince] DATETIME NOT NULL
INDEX [ICustomerSince] NONCLUSTERED
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)
در اینجا سه جدول را مشاهده می‌کنید که در بانک اطلاعاتی آماده شده در مرحله‌ی قبل، ایجاد خواهند شد. مورد اول یک جدول معمولی است که از آن برای مقایسه سرعت ثبت اطلاعات با سایر جداول ایجاد شده، استفاده خواهد شد.
همانطور که مشخص است، دو جدول بهینه سازی شده برای حافظه، همان سه ستون جدول معمولی مبتنی بر دیسک سخت را دارا هستند؛ اما با این تفاوت‌ها:
- دارای ویژگی MEMORY_OPTIMIZED = ON می‌باشند. به این ترتیب اینگونه جداول نسبت به جداول متداول مبتنی به دیسک سخت متمایز خواهند شد.
- دارای ویژگی DURABILITY بوده و توسط مقدار SCHEMA_AND_DATA آن مشخص می‌کنیم که آیا قرار است اطلاعات و ساختار جدول، ذخیره شوند یا تنها قرار است ساختار جدول ذخیره گردد (حالت SCHEMA_ONLY).
- بر روی ستون Id آن‌ها یک hash index ایجاد شده‌است که وجود آن ضروری است و در کل بیش از 8 ایندکس را نمی‌توان تعریف کرد.
برخلاف ایندکس‌های B-tree جداول مبتنی بر سخت دیسک، ایندکس‌های جداول بهینه سازی شده برای حافظه، اطلاعات را تکرار نمی‌کنند. این‌ها صرفا اشاره‌گرهایی هستند به ردیف‌های اصلی اطلاعات. به این معنا که این ایندکس‌ها لاگ نشده و همچنین بر روی سخت دیسک ذخیره نمی‌شوند. کار بازسازی مجدد آن‌ها در اولین بار بازیابی بانک اطلاعاتی و آغاز آن به صورت خودکار انجام می‌شود. به همین جهت مباحثی مانند index fragmentation و نگهداری ایندکس‌ها دیگر در اینجا معنا پیدا نمی‌کنند.
دو نوع ایندکس را در اینجا می‌توان تعریف کرد. اولین آن‌ها hash index است و دومین آن‌ها range index. هش ایندکس‌ها برای حالاتی که در کوئری‌ها از عملگر تساوی استفاده می‌شود بسیار مناسب هستند. برای عملگرهای مقایسه‌ای از ایندکس‌های بازه‌ای استفاده می‌شود.
همچنین باید دقت داشت که پس از ایجاد ایندکس‌ها، دیگر امکان تغییر آن‌ها و یا تغییر ساختار جدول ایجاد شده نیست.
همچنین ایندکس‌های تعریف شده در جداول بهینه سازی شده برای حافظه، تنها بر روی ستون‌هایی غیرنال پذیر از نوع BIN2 collation مانند int و datetime قابل تعریف هستند. برای مثال اگر سعی کنیم بر روی ستون Name ایندکسی را تعریف کنیم، به این خطا خواهیم رسید:
 Indexes on character columns that do not use a *_BIN2 collation are not supported with indexes on memory optimized tables.
- در حین تعریف هش ایندکس‌ها، مقدار BUCKET_COUNT نیز باید تنظیم شود. هر bucket توسط مقداری که حاصل هش کردن یک ستون است مشخص می‌شود. کلیدهای منحصربفرد دارای هش‌های یکسان در bucketهای یکسانی ذخیره می‌شوند. به همین جهت توصیه شده‌است که حداقل مقدار bucket تعیین شده در اینجا مساوی یا بیشتر از مقدار تعداد کلیدهای منحصربفرد یک جدول باشد؛ مقدار پیش فرض 2 برابر توسط مایکروسافت توصیه شده‌است.
- نوع‌های قابل تعریف ستون‌ها نیز در اینجا به موارد ذیل محدود هستند و جمع طول آن‌ها از 8060 نباید بیشتر شود:
 bit, tinyint, smallint, int, bigint, money, smallmoney, float, real, datetime, smalldatetime, datetime2,
date, time, numberic, decimal, char(n),  varchar(n) ,nchar(n),  nvarchar(n), sysname, binary(n),
varbinary(n), and Uniqueidentifier


همچنین در management studio، گزینه‌ی جدید new -> memory optimized table نیز اضافه شده‌است و انتخاب آن سبب می‌شود تا قالب T-SQL ایی برای تهیه این نوع جداول، به صورت خودکار تولید گردد.


البته این گزینه تنها برای بانک‌های اطلاعاتی که دارای گروه فایل استریم مخصوص جداول بهینه سازی شده برای حافظه هستند، فعال می‌باشد.


ثبت اطلاعات در جداول معمولی و بهینه سازی شده برای حافظه و مقایسه کارآیی آن‌ها

در مثال زیر، 100 هزار رکورد را در سه جدولی که پیشتر ایجاد کردیم، ثبت کرده و سپس مدت زمان اجرای هر کدام از مجموعه عملیات را بر حسب میلی ثانیه بررسی می‌کنیم:
set statistics time off
SET STATISTICS IO Off
set nocount on
go
-----------------------------

Print 'insert into tblNormal'

DECLARE @start datetime = getdate()
declare @insertCount int = 100000
declare @startId int = 1
declare @customerID int = @startId

while @customerID < @startId + @insertCount
begin
    insert into tblNormal values (@customerID, 'Test', '2013-01-01T00:00:00')
    set @customerID +=1
end

Print DATEDIFF(ms,@start,getdate());
go
-----------------------------

Print 'insert into tblMemoryOptimized_Schema_And_Data'

DECLARE @start datetime = getdate()
declare @insertCount int = 100000
declare @startId int = 1
declare @customerID int = @startId

while @customerID < @startId + @insertCount
begin
    insert into tblMemoryOptimized_Schema_And_Data values (@customerID, 'Test', '2013-01-01T00:00:00')
    set @customerID +=1
end
Print DATEDIFF(ms,@start,getdate());
Go
-----------------------------

Print 'insert into tblMemoryOptimized_Schema_Only'

DECLARE @start datetime = getdate()
declare @insertCount int = 100000
declare @startId int = 1
declare @customerID int = @startId

while @customerID < @startId + @insertCount
begin
    insert into tblMemoryOptimized_Schema_Only values (@customerID, 'Test', '2013-01-01T00:00:00')
    set @customerID +=1
end
Print DATEDIFF(ms,@start,getdate());

Go
با این خروجی تقریبی که بر اساس توانمندی‌های سخت افزاری سیستم می‌تواند متفاوت باشد:
 insert into tblNormal
36423

insert into tblMemoryOptimized_Schema_And_Data
30516

insert into tblMemoryOptimized_Schema_Only
3176
و برای حالت select خواهیم داشت:
 set nocount on
print 'tblNormal'
set statistics time on
select count(CustomerID) from tblNormal
set statistics time off
go
print 'tblMemoryOptimized_Schema_And_Data'
set statistics time on
select count(CustomerID) from tblMemoryOptimized_Schema_And_Data
set statistics time off
go
print 'tblMemoryOptimized_Schema_Only'
set statistics time on
select count(CustomerID) from tblMemoryOptimized_Schema_Only
set statistics time off
go
با این خروجی
 tblNormal
 SQL Server Execution Times:
CPU time = 46 ms,  elapsed time = 52 ms.

tblMemoryOptimized_Schema_And_Data
 SQL Server Execution Times:
CPU time = 32 ms,  elapsed time = 33 ms.

tblMemoryOptimized_Schema_Only
 SQL Server Execution Times:
CPU time = 31 ms,  elapsed time = 30 ms.
تاثیر جداول بهینه سازی شده برای حافظه را در 350K inserts بهتر می‌توان با نمونه‌های متداول مبتنی بر دیسک مقایسه کرد.


برای مطالعه بیشتر

Getting started with SQL Server 2014 In-Memory OLTP
Introduction to SQL Server 2014 CTP1 Memory-Optimized Tables
Overcoming storage speed limitations with Memory-Optimized Tables for SQL Server
Memory-optimized Table – Day 1 Test
Memory-Optimized Tables – Insert Test
Memory Optimized Table – Insert Test …Again
مطالب
Fluent Linq to Sql

نگارش بعدی یا چهارم entity framework چیزی است شبیه به Fluent NHibernate . یعنی اگر مقاله‌ای را در این زمینه مطالعه کنید و عنوان آن حذف شود، نمی‌توان تشخیص داد که این مقاله مربوط به entity framework است یا Fluent NHibernate. هر چند entity framework حداقل دو نگارش دیگر لازم دارد تا NHibernate را کاملا پشت سر بگذارد.
از آن طرف محبوبیت Linq to SQL هم هنوز پابرجا است و برای مثال سایت پر ترافیکی مثل stack overflow از آن استفاده می‌کند و بسیار هم موفق بوده و کارش را به خوبی انجام می‌دهد.
پروژه مکملی به نام Fluent Linq to Sql با الهام گیری از Fluent NHibernate در سایت codeplex موجود است که این نوع نگاشت‌ها را برای Linq to Sql نیز میسر می‌سازد. به این صورت دیگر نیازی به استفاده از attributes و یا فایل‌های xml نگاشت‌های Linq to Sql نخواهد بود. همچنین مدل کاری اول کد بعد دیتابیس نیز به این صورت محقق می‌شود.



مسیرراه‌ها
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework
لطفا علاوه بر مطالب، نظرات نیز مطالعه شوند.

قسمت اول چرا Xamarin Forms؟
قسمت دوم نصب و راه اندازی
قسمت سوم اجرا و دیباگ پروژه مثال روی UWP و راه اندازی Simulator و Edit & Continue برای C# - XAML
قسمت چهارم اجرا و دیباگ پروژه مثال روی Android + راه اندازی Emulator و Device + نکات بهبود Performance در Android
قسمت پنجم اجرا و دیباگ پروژه مثال روی iOS + راه اندازی Virtual Mac OS + تست بر روی Emulator و Device
قسمت ششم بررسی کلیات پروژه مثال و بررسی ساختار Page - Layout - Control
قسمت هفتم بررسی جزئیات Layout شامل Unit - Padding -Margin و ...
قسمت هشتم بررسی نقش View Model در MVVM و طرز کار Navigation
قسمت نهم Binding & Commanding & x:DataType
قسمت دهم Triggers & Value Converters
قسمت یازدهم List View & Syncfusion Controls
قسمت دوازدهم چند زبانه سازی و Right To Left
قسمت سیزدهم بهبود Performance با Compressed Layout و FF Image Loading
قسمت چهاردهم مدیریت خطاها و استفاده از App Center برای لاگ کردن آنها
قسمت پانزدهم زدن کدهای Platform Specific و Custom Renderers
قسمت هجدهم ارتباط با Rest Api
قسمت نوزدهم نوشتن Component و Partial View
قسمت بیستم Entity Framework Core & Sqlite
قسمت بیست و یکم آشنایی با پروژه ToDoStand
قسمت بیست و دوم ارتباط با پروژه ای که سمت سرور آن با Bit توسعه داده شده است و مزیت‌های کار با Bit در سمت سرور
قسمت بیست و سوم آشنایی با پروژه ToDoLine
نظرات مطالب
ساخت یک اپلیکیشن ساده ToDo با ASP.NET Identity
سلام
اگر پروژه ای داشته باشیم با ساختار زیر
MyProject
MyProject.Service
MyProject.Model
MyProject.Data (Entity framework code first)
اگر بخوایم تو لایه MyProject از asp .net identity  استفاده کنیم، به چه نحوی باید این کار رو انجام داد که ساختار دیتابیس که در لایه دیتا دچار مشکل نشه و جداول مورد نیاز asp .net identity به دیتا بیس اضافه بشه؟
مطالب
Angular Material 6x - قسمت پنجم - کار با Data Tables
در این قسمت قصد داریم اطلاعات یادداشت‌های کاربران را توسط کامپوننت mat-table نمایش دهیم که به همراه قابلیت‌هایی مانند صفحه بندی، مرتب سازی و فیلتر کردن داده‌ها است.


کامپوننت mat-table

کار کامپوننت mat-table نمایش اطلاعات در ردیف‌ها و ستون‌ها است. به همراه آن mat-paginator برای نمایش UI صفحه بندی اطلاعات، دایرکتیو matSort و mat-sort-header برای افزودن رابط کاربری مرتب سازی اطلاعات و امکان تغییر منبع داده آن برای فیلتر کردن داده‌ها، نیز وجود دارند.


افزودن کامپوننت جدید notes برای نمایش یادداشت‌های کاربران

برای نمایش لیست یادداشت‌های هر شخص، کامپوننت جدید Notes را به صورت زیر در پوشه‌ی components ایجاد می‌کنیم:
 ng g c contact-manager/components/notes --no-spec
علت اینجا است که نمی‌خواهیم کامپوننت نمایش جزئیات شخص را بیش از اندازه شلوغ کنیم. بنابراین به قالب کامپوننت main-content (فایل main-content.component.html) مراجعه کرده و selector این کامپوننت را در آنجا درج می‌کنیم:
      <mat-tab-group>
        <mat-tab label="Bio">
          <p>
            {{user.bio}}
          </p>
        </mat-tab>
        <mat-tab label="Notes">
          <app-notes [notes]="user.userNotes"></app-notes>
        </mat-tab>
      </mat-tab-group>
همانطور که ملاحظه می‌کنید app-notes در برگه‌ی دوم کامپوننت mat-tab-group درج شده‌است. همچنین قصد داریم لیست userNotes جاری را به خاصیت notes آن نیز ارسال کنیم. به همین جهت به کامپوننت notes مراجعه کرده و این ورودی را ایجاد می‌کنیم:
import { Component, Input, OnInit } from "@angular/core";
import { UserNote } from "../../models/user-note";

@Component({
  selector: "app-notes",
  templateUrl: "./notes.component.html",
  styleUrls: ["./notes.component.css"]
})
export class NotesComponent implements OnInit {

  @Input() notes: UserNote[];
فعلا جهت بررسی صحت عملکرد آن به قالب این کامپوننت (فایل notes.component.html) مراجعه کرده و آن‌را به صورت json نمایش می‌دهیم:
 <p>
  {{notes | json}}
</p>



تکمیل کامپوننت Notes توسط یک data table

در ادامه قصد داریم این اطلاعات خام را توسط یک data table نمایش دهیم. به همین جهت ابتدا به مستندات mat-table مراجعه کرده و همانند قبل، مثالی را پیدا می‌کنیم که به منظور ما نزدیک‌تر باشد. سپس کدهای آن‌را به برنامه اضافه کرده و سفارشی سازی می‌کنیم. در ابتدا مثال basic آن‌را دقیقا به همان نحوی که هست کپی کرده و سپس آن‌را تغییر می‌دهیم:
محتوای فایل notes.component.ts
import { Component, Input, OnInit } from "@angular/core";
import { MatTableDataSource } from "@angular/material";

import { UserNote } from "../../models/user-note";

@Component({
  selector: "app-notes",
  templateUrl: "./notes.component.html",
  styleUrls: ["./notes.component.css"]
})
export class NotesComponent implements OnInit {

  @Input() notes: UserNote[];

  displayedColumns = ["position", "title", "date"];
  dataSource: MatTableDataSource<UserNote>;

  constructor() { }

  ngOnInit() {
    this.dataSource = new MatTableDataSource<UserNote>(this.notes);
  }

}
در اینجا برای نمایش یک mat-table، نیاز به یک منبع داده وجود دارد که روش تعریف آن‌را توسط MatTableDataSource از نوع UserNote مشاهده می‌کنید.
سپس این منبع داده در قسمت ngOnInit بر اساس ورودی آرایه‌ی notes که از کامپوننت main-content مقدار دهی می‌شود، تامین خواهد شد.
displayedColumns نیز لیست ستون‌ها را مشخص می‌کند.

محتوای فایل notes.component.html
<div class="example-container mat-elevation-z8" fxLayout="column">
  <mat-table #table [dataSource]="dataSource">
    <ng-container matColumnDef="position">
      <mat-header-cell *matHeaderCellDef> No. </mat-header-cell>
      <mat-cell *matCellDef="let note"> {{note.id}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="title">
      <mat-header-cell *matHeaderCellDef> Title </mat-header-cell>
      <mat-cell *matCellDef="let note"> {{note.title}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="date">
      <mat-header-cell *matHeaderCellDef> Date </mat-header-cell>
      <mat-cell *matCellDef="let note"> {{note.date | date:'yyyy-MM-dd'}} </mat-cell>
    </ng-container>

    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
  </mat-table>
</div>
در اینجا ترتیب ردیف‌ها بر اساس mat-row انتهای جدول مشخص می‌شود. بنابراین مهم نیست که ng-container matColumnDef‌ها چه ترتیبی دارند.
سپس به ازای هر ستون، یک ng-container اضافه شده‌است. matColumnDef معادل نام‌های displayedColumns خواهد بود. matCellDef نیز بر اساس متغیر حلقه‌ای که بر روی منبع داده تشکیل می‌شود، تعریف خواهد شد. این تعریف امکان دسترسی به مقدار آن‌را در ادامه میسر می‌کند.
در این حالت اگر برنامه را اجرا کنیم، خروجی زیر قابل مشاهده خواهد بود:


افزودن صفحه بندی به mat-table یادداشت‌های یک کاربر

اگر مجددا به مستندات mat-table مراجعه کنیم، مثالی در مورد mat-paginator نیز دارد که جهت نمایش رابط کاربری صفحه بندی مورد استفاده قرار می‌گیرد. بنابراین از مثال آن جهت تکمیل این قسمت ایده می‌گیریم:
  </mat-table>

  <mat-paginator #paginator [pageSize]="2" [pageSizeOptions]="[2, 4, 6]">
  </mat-paginator>
</div>
پس از بسته شدن تگ mat-table، کامپوننت mat-paginator به صفحه اضافه می‌شود که pageSize آن تعداد ردیف‌های در هر صفحه را مشخص می‌کند و pageSizeOptions سبب نمایش یک دراپ داون برای انتخاب تعداد ردیف‌های هر صفحه توسط کاربر خواهد شد.
در ادامه به کدهای کامپوننت مراجعه کرده و توسط ViewChild به template reference variable ایی به نام paginator دسترسی پیدا می‌کنیم:
export class NotesComponent implements OnInit, AfterViewInit {

  dataSource: MatTableDataSource<UserNote>;
  
  @ViewChild(MatPaginator) paginator: MatPaginator;

  ngAfterViewInit() {
    this.dataSource.paginator = this.paginator;
  }

}
سپس مطابق مستندات آن، این کامپوننت باید به خاصیت paginator منبع داده‌ی data table در رخ‌داد ngAfterViewInit، متصل شود.
اکنون اگر برنامه را اجرا کنیم، صفحه بندی فعال شده‌است:



افزودن جستجو و فیلتر کردن اطلاعات به mat-table یادداشت‌های یک کاربر

مستندات mat-table به همراه مثال filtering نیز هست که از آن جهت تکمیل این قسمت به نحو ذیل ایده خواهیم گرفت:
ابتدا فیلد ورود اطلاعات جستجو، پیش از Mat-table به قالب کامپوننت اضافه می‌شود:
<div class="example-container mat-elevation-z8" fxLayout="column">
  <div class="example-header">
    <mat-form-field>
      <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
    </mat-form-field>
  </div>
سپس متد applyFilter که به ازای هر keyup فعال می‌شود، در کدهای کامپوننت به نحو زیر تکمیل خواهد شد:
  applyFilter(filterValue: string) {
    this.dataSource.filter = filterValue.trim().toLowerCase(); // MatTableDataSource defaults to lowercase matches
  }
همین اندازه تنظیم سبب فعالسازی جستجو بر روی جدول می‌شود:



افزودن مرتب سازی اطلاعات به mat-table یادداشت‌های یک کاربر

مستندات mat-table به همراه مثال sorting نیز هست که از آن جهت تکمیل این قسمت به نحو ذیل ایده خواهیم گرفت:
برای فعالسازی مرتب سازی اطلاعات، در قالب کامپوننت، به mat-table، دایرکتیو matSort و به هر ستونی که نیاز است مرتب سازی شود، دایرکتیو mat-sort-header را به mat-header‌ها اضافه می‌کنیم:
  <mat-table #table [dataSource]="dataSource" matSort>
    <ng-container matColumnDef="position">
      <mat-header-cell *matHeaderCellDef mat-sort-header> No. </mat-header-cell>
در کدهای کامپوننت نیز ابتدا توسط ViewChild به matSort دسترسی پیدا می‌کنیم و سپس آن‌را به خاصیت sort منبع داده در رخ‌داد ngAfterViewInit، متصل خواهیم کرد:
export class NotesComponent implements OnInit, AfterViewInit {

  dataSource: MatTableDataSource<UserNote>;
  
  @ViewChild(MatSort) sort: MatSort;

  ngAfterViewInit() {
    this.dataSource.sort = this.sort;
  }
}
نتیجه‌ی این تغییرات را در تصویر زیر با فعالسازی مرتب سازی بر روی ستون Title مشاهده می‌کنید:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MaterialAngularClient-04.zip
برای اجرای آن:
الف) ابتدا به پوشه‌ی src\MaterialAngularClient وارد شده و فایل‌های restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشه‌ی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایل‌های restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
اشتراک‌ها
ساخت یک محیط توسعه زیبا در اندروید استودیو
 این مطلب برنامه نویسان اندروید را از تنظیمات متعدد و سفارشی سازی محیط کاری  دور خواهدکرد و در واقع با انجام تنظیمات ذیل به محیط کاری که در عکس مشاهده میکنند خواهند رسید .
  • ترکیب رنگی زیبا و طراحی متریال محیط در عکس زیر قابل مشاهده می‌باشد 


۱. تنظیمات GUI Theme :
از منوی File بر روی Settings کلیک می‌کنیم و بر روی Plugin کلیک کرده در قسمت جستجو عبارت مقابل ( Material Theme UI ) را  جستجو کرده و بر روی Install jetBrains Plugin کلیک می‌کنیم که Plugin مورد نظر ما نصب خواهد شد که بعد نصب اندروید استودیو را Restart می‌کنیم . (تنظیمات طبق عکس زیر انجام گردد) 
دسترسی به مخزن گیت هاب ‍   Material Theme UI for jetBrains  
⌘⇧a → "Plugins" → ↩ → ⌃⌥b → <search> → "Material Theme UI" → [Install plugin] → ⌃⌥c → ⎋ → <restart>

۲. تنظیمات Editor Scheme :
از منوی File بر روی Settings کلیک کرده و از قسمت Editor بر روی Color Scheme کلیک کرده و از قسمت Scheme که chroMATERIAL را انتخاب کرده و بر روی Apply/OK  کلیک می‌کنیم.
⌘⇧a → "Color Scheme" → [3. ChroMATERIAL]
و از قسمت Color Scheme Font که Use color scheme font instead را انتخاب و فونت را Lucida Snas Typewriter انتخاب و Apply/ok می‌کنیم.

۳. تنظیمات HOLO Logcat :
به مسیر زیر مراجعه کرده Preferences → Editor → Colors & Fonts → Android Logcat و تنظیمات Logcat را طبق سلیقه خود مشخص می‌کنیم و در نهایت Apply/ok می‌کنیم .
⌘⇧a → "Android Logcat" → [Save as...] → "ChroMATERIAL + HOLO"¹ → ↩ → <set foreground colors as in the table ↑> → ⎋
Type   : Color
verbose: #BBB debug: #33B5E5 info: #9C0 assert: #A6C error: #F44 warning: #FB3 

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