پیشنیازهای کار با کش توزیع شدهی مبتنی بر SQL Server
برای کار با کش توزیع شدهی با قابلیت ذخیره سازی در یک بانک اطلاعاتی SQL Server، نیاز است دو بستهی ذیل را به فایل project.json برنامه اضافه کرد:
{ "dependencies": { "Microsoft.Extensions.Caching.SqlServer": "1.1.0" }, "tools": { "Microsoft.Extensions.Caching.SqlConfig.Tools": "1.1.0-preview4-final" } }
ایجاد جدول ذخیره سازی اطلاعات کش توزیع شده به کمک ابزار sql-cache
پس از افزودن و بازیابی ارجاعات فوق، با استفاده از خط فرمان، به پوشهی جاری برنامه وارد شده و دستور ذیل را صادر کنید:
dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=sql_cache;Integrated Security=True;" "dbo" "AppSqlCache"
- در اینجا میتوان هر نوع رشتهی اتصالی معتبری را به انواع و اقسام بانکهای SQL Server ذکر کرد. برای نمونه در مثال فوق این رشتهی اتصالی به یک بانک اطلاعاتی از پیش ایجاد شدهی LocalDB اشاره میکند. نام دلخواه این بانک اطلاعاتی در اینجا sql_cache ذکر گردیده و نام دلخواه جدولی که قرار است این اطلاعات را ثبت کند AppSqlCache تنظیم شدهاست و dbo، نام اسکیمای جدول است:
در اینجا تصویر ساختار جدولی را که توسط ابزار dotnet sql-cache ایجاد شدهاست، مشاهده میکنید. اگر خواستید این جدول را خودتان دستی ایجاد کنید، یک چنین کوئری را باید بر روی دیتابیس مدنظرتان اجرا نمائید:
CREATE TABLE AppSqlCache ( Id NVARCHAR (449) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, Value VARBINARY (MAX) NOT NULL, ExpiresAtTime DATETIMEOFFSET NOT NULL, SlidingExpirationInSeconds BIGINT NULL, AbsoluteExpiration DATETIMEOFFSET NULL, CONSTRAINT pk_Id PRIMARY KEY (Id) ); CREATE NONCLUSTERED INDEX Index_ExpiresAtTime ON AppSqlCache(ExpiresAtTime);
ایجاد جدول ذخیره سازی اطلاعات کش توزیع شده به کمک ابزار Migrations در EF Core
زیر ساخت کش توزیع شدهی مبتنی بر SQL Server هیچگونه وابستگی به EF Core ندارد و تمام اجزای آن توسط Async ADO.NET نوشته شدهاند. اما اگر خواستید قسمت ایجاد جدول مورد نیاز آنرا به ابزار Migrations در EF Core واگذار کنید، روش کار به صورت زیر است:
- ابتدا یک کلاس دلخواه جدید را با محتوای ذیل ایجاد کنید:
public class AppSqlCache { public string Id { get; set; } public byte[] Value { get; set; } public DateTimeOffset ExpiresAtTime { get; set; } public long? SlidingExpirationInSeconds { get; set; } public DateTimeOffset? AbsoluteExpiration { get; set; } }
public class MyDBDataContext : DbContext { public virtual DbSet<AppSqlCache> AppSqlCache { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<AppSqlCache>(entity => { entity.ToTable(name: "AppSqlCache", schema: "dbo"); entity.HasIndex(e => e.ExpiresAtTime).HasName("Index_ExpiresAtTime"); entity.Property(e => e.Id).HasMaxLength(449); entity.Property(e => e.Value).IsRequired(); }); } }
البته این مورد به شرطی است که بخواهید از یک دیتابیس، هم برای برنامه و هم برای ذخیره سازی اطلاعات کش استفاده کنید.
معرفی تنظیمات رشتهی اتصالی و نام جدول ذخیره سازی اطلاعات کش به برنامه
پس از ایجاد جدول مورد نیاز جهت ذخیره سازی اطلاعات کش، اکنون نیاز است این اطلاعات را به برنامه معرفی کرد. برای این منظور به کلاس آغازین برنامه مراجعه کرده و متد الحاقی AddDistributedSqlServerCache را بر روی مجموعهی سرویسهای موجود فراخوانی کنید؛ تا سرویسهای این کش توزیع شده نیز به برنامه معرفی شوند:
public void ConfigureServices(IServiceCollection services) { services.AddDistributedSqlServerCache(options => { options.ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=sql_cache;Integrated Security=True;"; options.SchemaName = "dbo"; options.TableName = "AppSqlCache"; });
آزمایش کش توزیع شدهی تنظیمی با فعال سازی سشنها
سشنها را همانند نکات ذکر شدهی در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 16 - کار با Sessions» فعال کنید و سپس مقداری را در آن بنویسید:
public IActionResult Index() { HttpContext.Session.SetString("User", "VahidN"); return Json(true); } public IActionResult About() { var userContent = HttpContext.Session.GetString("User"); return Json(userContent); }
همانطور که مشاهده میکنید، سیستم سشن اینبار بجای حافظه، به صورت خودکار از جدول بانک اطلاعاتی SQL Server تنظیم شده، برای ذخیره سازی اطلاعات خود استفاده کردهاست.
کار با کش توزیع شده از طریق برنامه نویسی
همانطور که در مقدمهی بحث نیز عنوان شد، استفادهی از زیر ساخت کش توزیع شده منحصر به استفادهی از آن جهت ذخیره سازی اطلاعات سشنها نیست و از آن میتوان جهت انواع و اقسام سناریوهای مختلف مورد نیاز استفاده کرد. در این حالت روش دسترسی به این زیر ساخت، از طریق اینترفیس IDistributedCache است. زمانیکه متد AddDistributedSqlServerCache را فراخوانی میکنیم، در حقیقت کار ثبت یک چنین سرویسی به صورت خودکار انجام خواهد شد:
services.Add(ServiceDescriptor.Singleton<IDistributedCache, SqlServerCache>());
در اینجا یک نمونه از این تزریق وابستگی و سپس استفادهی از متدهای Set و Get اینترفیس IDistributedCache را مشاهده میکنید:
using System; using System.Text; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Distributed; namespace Core1RtmEmptyTest.Controllers { public class CacheTestController : Controller { readonly IDistributedCache _cache; public CacheTestController(IDistributedCache cache) { _cache = cache; } public IActionResult SetCacheData() { var time = DateTime.Now.ToLocalTime().ToString(); var cacheOptions = new DistributedCacheEntryOptions { AbsoluteExpiration = DateTime.Now.AddYears(1) }; _cache.Set("Time", Encoding.UTF8.GetBytes(time), cacheOptions); return View(); } public IActionResult GetCacheData() { var time = Encoding.UTF8.GetString(_cache.Get("Time")); ViewBag.data = time; return View(); } public bool RemoveCacheData() { _cache.Remove("Time"); return true; } } }
Value VARBINARY (MAX) NOT NULL,
public byte[] Value { get; set; }
در این حالت اگر برنامه را اجرا و مسیر http://localhost:7742/CacheTest/SetCacheData را فراخوانی کنیم، اطلاعات ذخیره شدهی با کلید Test را میتوان در بانک اطلاعاتی مشاهده کرد:
Tag helper مخصوص کش توزیع شده
در ASP.NET Core، میتوان از یک Tag Helper جدید به نام distributed-cache برای کش سمت سرور توزیع شدهی محتوای قسمتی از یک View به نحو ذیل استفاده کرد:
<distributed-cache name="MyCacheItem2" expires-sliding="TimeSpan.FromMinutes(30)"> <p>From distributed-cache</p> @DateTime.Now.ToString() </distributed-cache>
در اینجا name به صورت هش شده به صورت کلید کش مورد استفاده قرار میگیرد. سپس محتوای تگ distributed-cache رندر شده، تبدیل به آرایهای از بایتها گردیده و در بانک اطلاعاتی ذخیره میگردد.
ذکر name در اینجا اجباری است و باید دقت داشت که چون به عنوان کلید بازیابی کش مورد استفاده قرار خواهد گرفت، نباید به اشتباه در قسمتهای دیگر برنامه با همین نام وارد شود. در غیر اینصورت دو قسمتی که name یکسانی داشته باشند، یک محتوا را نمایش خواهند داد.
EF Code First #4
آشنایی با Code first migrations
ویژگی Code first migrations برای اولین بار در EF 4.3 ارائه شد و هدف آن سهولت هماهنگ سازی کلاسهای مدل برنامه با بانک اطلاعاتی است؛ به صورت خودکار یا با تنظیمات دقیق دستی.
همانطور که در قسمتهای قبل نیز به آن اشاره شد، تا پیش از EF 4.3، پنج روال جهت آغاز به کار با بانک اطلاعاتی در EF code first وجود داشت و دارد:
1) در اولین بار اجرای برنامه، در صورتیکه بانک اطلاعاتی اشاره شده در رشته اتصالی وجود خارجی نداشته باشد، نسبت به ایجاد خودکار آن اقدام میگردد. اینکار پس از وهله سازی اولین DbContext و همچنین صدور یک کوئری به بانک اطلاعاتی انجام خواهد شد.
2) DropCreateDatabaseAlways : همواره پس از شروع برنامه، ابتدا بانک اطلاعاتی را drop کرده و سپس نمونه جدیدی را ایجاد میکند.
3) DropCreateDatabaseIfModelChanges : اگر EF Code first تشخیص دهد که تعاریف مدلهای شما با بانک اطلاعاتی مشخص شده توسط رشته اتصالی، هماهنگ نیست، آنرا drop کرده و نمونه جدیدی را تولید میکند.
4) با مقدار دهی پارامتر متد System.Data.Entity.Database.SetInitializer به نال، میتوان فرآیند آغاز خودکار بانک اطلاعاتی را غیرفعال کرد. در این حالت شخص میتواند تغییرات انجام شده در کلاسهای مدل برنامه را به صورت دستی به بانک اطلاعاتی اعمال کند.
5) میتوان با پیاده سازی اینترفیس IDatabaseInitializer، یک آغاز کننده بانک اطلاعاتی سفارشی را نیز تولید کرد.
اکثر این روشها در حین توسعه یک برنامه یا خصوصا جهت سهولت انجام آزمونهای خودکار بسیار مناسب هستند، اما به درد محیط کاری نمیخورند؛ زیرا drop یک بانک اطلاعاتی به معنای از دست دادن تمام اطلاعات ثبت شده در آن است. برای رفع این مشکل مهم، مفهومی به نام «Migrations» در EF 4.3 ارائه شده است تا بتوان بانک اطلاعاتی را بدون تخریب آن، بر اساس اطلاعات تغییر کردهی کلاسهای مدل برنامه، تغییر داد. البته بدیهی است زمانیکه توسط NuGet نسبت به دریافت و نصب EF اقدام میشود، همواره آخرین نگارش پایدار که حاوی اطلاعات و فایلهای مورد نیاز جهت کار با «Migrations» است را نیز دریافت خواهیم کرد.
تنظیمات ابتدایی Code first migrations
در اینجا قصد داریم همان مثال قسمت قبل را ادامه دهیم. در آن مثال از یک نمونه سفارشی سازی شده DropCreateDatabaseAlways استفاده شد.
نیاز است از منوی Tools در ویژوال استودیو، گزینه Library package manager آن، گزینه package manager console را انتخاب کرد تا کنسول پاورشل NuGet ظاهر شود.
اطلاعات مرتبط با پاورشل EF، به صورت خودکار توسط NuGet نصب میشود. برای مثال جهت مشاهده آنها به مسیر packages\EntityFramework.4.3.1\tools در کنار پوشه پروژه خود مراجعه نمائید.
در ادامه در پایین صفحه، زمانیکه کنسول پاورشل NuGet ظاهر میشود، ابتدا باید دقت داشت که قرار است فرامین را بر روی چه پروژهای اجرا کنیم. برای مثال اگر تعاریف DbContext را به یک اسمبلی و پروژه class library مجزا انتقال دادهاید، گزینه Default project را در این قسمت باید به این پروژه مجزا، تغییر دهید.
سپس در خط فرمان پاور شل، دستور enable-migrations را وارد کرده و دکمه enter را فشار دهید.
پس از اجرای این دستور، یک سری اتفاقات رخ خواهد داد:
الف) پوشهای به نام Migrations به پروژه پیش فرض مشخص شده در کنسول پاورشل، اضافه میشود.
ب) دو کلاس جدید نیز در آن پوشه تعریف خواهند شد به نامهای Configuration.cs و یک نام خودکار مانند number_InitialCreate.cs
ج) در کنسول پاور شل، پیغام زیر ظاهر میگردد:
Detected database created with a database initializer. Scaffolded migration '201205050805256_InitialCreate'
corresponding to current database schema. To use an automatic migration instead, delete the Migrations
folder and re-run Enable-Migrations specifying the -EnableAutomaticMigrations parameter.
با توجه به اینکه در مثال قسمت سوم، از آغاز کننده سفارشی سازی شده DropCreateDatabaseAlways استفاده شده بود، اطلاعات آن در جدول سیستمی dbo.__MigrationHistory در بانک اطلاعاتی برنامه موجود است (تصویری از آنرا در قسمت اول این سری مشاهده کردید). سپس با توجه به ساختار بانک اطلاعاتی جاری، دو کلاس خودکار زیر را ایجاد کرده است:
namespace EF_Sample02.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<EF_Sample02.Sample2Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(EF_Sample02.Sample2Context context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
}
namespace EF_Sample02.Migrations
{
using System.Data.Entity.Migrations;
public partial class InitialCreate : DbMigration
{
public override void Up()
{
CreateTable(
"Users",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
LastName = c.String(),
Email = c.String(),
Description = c.String(),
Photo = c.Binary(),
RowVersion = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
Interests_Interest1 = c.String(maxLength: 450),
Interests_Interest2 = c.String(maxLength: 450),
AddDate = c.DateTime(nullable: false),
})
.PrimaryKey(t => t.Id);
CreateTable(
"Projects",
c => new
{
Id = c.Int(nullable: false, identity: true),
Title = c.String(maxLength: 50),
Description = c.String(),
RowVesrion = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
AddDate = c.DateTime(nullable: false),
AdminUser_Id = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("Users", t => t.AdminUser_Id)
.Index(t => t.AdminUser_Id);
}
public override void Down()
{
DropIndex("Projects", new[] { "AdminUser_Id" });
DropForeignKey("Projects", "AdminUser_Id", "Users");
DropTable("Projects");
DropTable("Users");
}
}
}
در این کلاس خودکار، نحوه ایجاد جداول بانک اطلاعاتی تعریف شدهاند. در متد تحریف شده Up، کار ایجاد بانک اطلاعاتی و در متد تحریف شده Down، دستورات حذف جداول و قیود ذکر شدهاند.
به علاوه اینبار متد Seed را در کلاس مشتق شده از DbMigrationsConfiguration، میتوان تحریف و مقدار دهی کرد.
علاوه بر اینها جدول سیستمی dbo.__MigrationHistory نیز با اطلاعات جاری مقدار دهی میگردد.
فعال سازی گزینههای مهاجرت خودکار
برای استفاده از این کلاسها، ابتدا به فایل Configuration.cs مراجعه کرده و خاصیت AutomaticMigrationsEnabled را true کنید:
internal sealed class Configuration : DbMigrationsConfiguration<EF_Sample02.Sample2Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
}
پس از آن EF به صورت خودکار کار استفاده و مدیریت «Migrations» را عهدهدار خواهد شد. البته برای این منظور باید نوع آغاز کننده بانک اطلاعاتی را از DropCreateDatabaseAlways قبلی به نمونه جدید MigrateDatabaseToLatestVersion نیز تغییر دهیم:
//Database.SetInitializer(new Sample2DbInitializer());
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample2Context, Migrations.Configuration>());
یک نکته:
کلاس Migrations.Configuration که باید در حین وهله سازی از MigrateDatabaseToLatestVersion قید شود (همانند کدهای فوق)، از نوع internal sealed معرفی شده است. بنابراین اگر این کلاس را در یک اسمبلی جداگانه قرار دادهاید، نیاز است فایل را ویرایش کرده و internal sealed آنرا به public تغییر دهید.
روش دیگر معرفی کلاسهای Context و Migrations.Configuration، حذف متد Database.SetInitializer و استفاده از فایل app.config یا web.config است به نحو زیر ( در اینجا حرف ` اصطلاحا back tick نام دارد. فشردن دکمه ~ در حین تایپ انگلیسی):
<entityFramework>
<contexts>
<context type="EF_Sample02.Sample2Context, EF_Sample02">
<databaseInitializer
type="System.Data.Entity.MigrateDatabaseToLatestVersion`2[[EF_Sample02.Sample2Context, EF_Sample02],
[EF_Sample02.Migrations.Configuration, EF_Sample02]], EntityFramework"
/>
</context>
</contexts>
</entityFramework>
آزمودن ویژگی مهاجرت خودکار
اکنون برای آزمایش این موارد، یک خاصیت دلخواه را به کلاس Project به نام public string SomeProp اضافه کنید. سپس برنامه را اجرا نمائید.
در ادامه به بانک اطلاعاتی مراجعه کرده و فیلدهای جدول Projects را بررسی کنید:
CREATE TABLE [dbo].[Projects](
---...
[SomeProp] [nvarchar](max) NULL,
---...
بله. اینبار فیلد SomeProp بدون از دست رفتن اطلاعات و drop بانک اطلاعاتی، به جدول پروژهها اضافه شده است.
عکس العمل ویژگی مهاجرت خودکار در مقابل از دست رفتن اطلاعات
در ادامه، خاصیت public string SomeProp را که در قسمت قبل به کلاس پروژه اضافه کردیم، حذف کنید. اکنون مجددا برنامه را اجرا نمائید. برنامه بلافاصله با استثنای زیر متوقف خواهد شد:
Automatic migration was not applied because it would result in data loss.
از آنجائیکه حذف یک خاصیت مساوی است با حذف یک ستون در جدول بانک اطلاعاتی، امکان از دست رفتن اطلاعات در این بین بسیار زیاد است. بنابراین ویژگی مهاجرت خودکار دیگر اعمال نخواهد شد و این مورد به نوعی یک محافظت خودکار است که درنظر گرفته شده است.
البته در EF Code first این مساله را نیز میتوان کنترل نمود. به کلاس Configuration اضافه شده توسط پاورشل مراجعه کرده و خاصیت AutomaticMigrationDataLossAllowed را به true تنظیم کنید:
internal sealed class Configuration : DbMigrationsConfiguration<EF_Sample02.Sample2Context>
{
public Configuration()
{
this.AutomaticMigrationsEnabled = true;
this.AutomaticMigrationDataLossAllowed = true;
}
این تغییر به این معنا است که خودمان صریحا مجوز حذف یک ستون و اطلاعات مرتبط به آنرا صادر کردهایم.
پس از این تغییر، مجددا برنامه را اجرا کنید. ستون SomeProp به صورت خودکار حذف خواهد شد، اما اطلاعات رکوردهای موجود تغییری نخواهند کرد.
استفاده از Code first migrations بر روی یک بانک اطلاعاتی موجود
تفاوت یک دیتابیس موجود با بانک اطلاعاتی تولید شده توسط EF Code first در نبود جدول سیستمی dbo.__MigrationHistory است.
به این ترتیب زمانیکه فرمان enable-migrations را در یک پروژه EF code first متصل به بانک اطلاعاتی قدیمی موجود اجرا میکنیم، پوشه Migration در آن ایجاد خواهد شد اما تنها حاوی فایل Configuration.cs است و نه فایلی شبیه به number_InitialCreate.cs .
بنابراین نیاز است به صورت صریح به EF اعلام کنیم که نیاز است تا جدول سیستمی dbo.__MigrationHistory و فایل number_InitialCreate.cs را نیز تولید کند. برای این منظور کافی است دستور زیر را در خط فرمان پاورشل NuGet پس از فراخوانی enable-migrations اولیه، اجرا کنیم:
add-migration Initial -IgnoreChanges
با بکارگیری پارامتر IgnoreChanges، متد Up در فایل number_InitialCreate.cs تولید نخواهد شد. به این ترتیب نگران نخواهیم بود که در اولین بار اجرای برنامه، تعاریف دیتابیس موجود ممکن است اندکی تغییر کند.
سپس دستور زیر را جهت به روز رسانی جدول سیستمی dbo.__MigrationHistory اجرا کنید:
update-database
پس از آن جهت سوئیچ به مهاجرت خودکار، خاصیت AutomaticMigrationsEnabled = true را در فایل Configuration.cs همانند قبل مقدار دهی کنید.
مشاهده دستوارت SQL به روز رسانی بانک اطلاعاتی
اگر علاقمند هستید که دستورات T-SQL به روز رسانی بانک اطلاعاتی را نیز مشاهده کنید، دستور Update-Database را با پارامتر Verbose آغاز نمائید:
Update-Database -Verbose
و اگر تنها نیاز به مشاهده اسکریپت تولیدی بدون اجرای آنها بر روی بانک اطلاعاتی مدنظر است، از پارامتر Script باید استفاده کرد:
update-database -Script
نکتهای در مورد جدول سیستمی dbo.__MigrationHistory
تنها دلیلی که این جدول در SQL Server البته (ونه برای مثال در SQL Server CE) به صورت سیستمی معرفی میشود این است که «جلوی چشم نباشد»! به این ترتیب در SQL Server management studio در بین سایر جداول معمولی بانک اطلاعاتی قرار نمیگیرد. اما برای EF تفاوتی نمیکند که این جدول سیستمی است یا خیر.
همین سیستمی بودن آن ممکن است بر اساس سطح دسترسی کاربر اتصالی به بانک اطلاعاتی مساله ساز شود. برای نمونه ممکن است schema کاربر متصل dbo نباشد. همینجا است که کار به روز رسانی این جدول متوقف خواهد شد.
بنابراین اگر قصد داشتید خواص سیستمی آنرا لغو کنید، تنها کافی است دستورات T-SQL زیر را در SQL Server اجرا نمائید:
SELECT * INTO [TempMigrationHistory]
FROM [__MigrationHistory]
DROP TABLE [__MigrationHistory]
EXEC sp_rename [TempMigrationHistory], [__MigrationHistory]
ساده سازی پروسه مهاجرت خودکار
کل پروسهای را که در این قسمت مشاهده کردید، به صورت ذیل نیز میتوان خلاصه کرد:
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.IO;
namespace EF_Sample02
{
public class Configuration<T> : DbMigrationsConfiguration<T> where T : DbContext
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
}
public class SimpleDbMigrations
{
public static void UpdateDatabaseSchema<T>(string SQLScriptPath = "script.sql") where T : DbContext
{
var configuration = new Configuration<T>();
var dbMigrator = new DbMigrator(configuration);
saveToFile(SQLScriptPath, dbMigrator);
dbMigrator.Update();
}
private static void saveToFile(string SQLScriptPath, DbMigrator dbMigrator)
{
if (string.IsNullOrWhiteSpace(SQLScriptPath)) return;
var scriptor = new MigratorScriptingDecorator(dbMigrator);
var script = scriptor.ScriptUpdate(sourceMigration: null, targetMigration: null);
File.WriteAllText(SQLScriptPath, script);
Console.WriteLine(script);
}
}
}
سپس برای استفاده از آن خواهیم داشت:
SimpleDbMigrations.UpdateDatabaseSchema<Sample2Context>();
در این کلاس ذخیره سازی اسکریپت تولیدی جهت به روز رسانی بانک اطلاعاتی جاری در یک فایل نیز درنظر گرفته شده است.
تا اینجا مهاجرت خودکار را بررسی کردیم. در قسمت بعدی Code-Based Migrations را ادامه خواهیم داد.
توسعه Asp.net Core و Asp.net Core Identity
public class ApplicationRoleStore : RoleStore<Role, ApplicationDbContext, int, UserRole, RoleClaim>, IApplicationRoleStore public class ApplicationUserStore : UserStore<User, Role, ApplicationDbContext, int, UserClaim, UserRole, UserLogin, UserToken, RoleClaim>, IApplicationUserStore
پ.ن.
این مورد بحث «بسیار» مفصلی هست که اینجا امکانش نیست و حتما نیاز به ارائهی یک solution کامل دارد تا قابل درک باشد: «سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیتهای پایه و DbContext برنامه »
مدل برنامه و نیازمندیهای اعتبارسنجی آن
namespace jqGrid08.Models { public class User { public int Id { set; get; } public string Name { set; get; } public string Email { set; get; } public string Password { set; get; } public string SiteUrl { set; get; } } }
- نام کاربر به صورت اجباری وارد شود و همچنین بین 3 تا 40 حرف باشد.
- همچنین نام کاربر نباید بر اساس اطلاعات موجود در بانک اطلاعاتی، تکراری وارد شود.
- ورود ایمیل شخص اجباری است؛ به علاوه فرمت آن نیز باید با یک ایمیل واقعی تطابق داشته باشد.
- ایمیل وارد شدهی یک کاربر جدید نیز نباید تکراری بوده و پیشتر توسط کاربر دیگری وارد شده باشد.
- ورود کلمهی عبور در حالت ثبت اطلاعات اجباری است؛ اما در حالت ویرایش اطلاعات خیر (از کلمهی عبور موجود در این حالت استفاده خواهد شد).
- ورود آدرس سایت کاربر اجباری بوده و همچنین فرمت آدرس وارد شده نیز باید معتبر باشد.
اعتبار سنجی سمت سرور و سمت کلاینت نام کاربر
colModel: [ { name: '@(StronglyTyped.PropertyName<User>(x => x.Name))', index: '@(StronglyTyped.PropertyName<User>(x => x.Name))', align: 'right', width: 150, editable: true, edittype: 'text', editoptions: { maxlength: 40 }, editrules: { required: true, custom: true, custom_func: function (value, colname) { if (!value) return [false, "لطفا نامی را وارد کنید"]; if (value.length < 3 || value.length > 40) return [false, colname + " باید بین 3 تا 40 حرف باشد"]; else return [true, ""]; } } }, ],
خروجی این متد یک آرایه دو عضوی است. اگر عضو اول آن true باشد، یعنی اعتبارسنجی موفقیت آمیز بودهاست؛ اگر خیر، عضو دوم آرایه، پیامی است که به کاربر نمایش داده خواهد شد.
تا اینجا کار اعتبارسنجی سمت کاربر به پایان میرسد. اما نیاز است در سمت سرور نیز بررسی شود که آیا نام وارد شده تکراری است یا خیر. برای این منظور تنها کافی است رویداد afterSubmit حالتهای Add و Edit را بررسی کنیم:
$('#list').jqGrid({ // ... }).navGrid( '#pager', //enabling buttons { add: true, del: true, edit: true, search: false }, //edit option { afterSubmit: showServerSideErrors }, //add options { afterSubmit: showServerSideErrors }, //delete options { }); }); function showServerSideErrors(response, postdata) { var result = $.parseJSON(response.responseText); if (result.success === false) { //نمایش خطای اعتبار سنجی سمت سرور پس از ویرایش یا افزودن //و همچنین جلوگیری از ثبت نهایی فرم return [false, result.message, result.id]; } return [true, "", result.id]; }
[HttpPost] public ActionResult AddUser(User postData) { //todo: Add user to repository if (postData == null) return Json(new { success = false, message = "اطلاعات دریافتی خالی است" }, JsonRequestBehavior.AllowGet); if (_usersInMemoryDataSource.Any( user => user.Name.Equals(postData.Name, StringComparison.InvariantCultureIgnoreCase))) { return Json(new { success = false, message = "نام کاربر تکراری است" }, JsonRequestBehavior.AllowGet); } if (_usersInMemoryDataSource.Any( user => user.Email.Equals(postData.Email, StringComparison.InvariantCultureIgnoreCase))) { return Json(new { success = false, message = "آدرس ایمیل کاربر تکراری است" }, JsonRequestBehavior.AllowGet); } postData.Id = _usersInMemoryDataSource.LastOrDefault() == null ? 1 : _usersInMemoryDataSource.Last().Id + 1; _usersInMemoryDataSource.Add(postData); return Json(new { id = postData.Id, success = true }, JsonRequestBehavior.AllowGet); }
خروجی روال رویدادگردان afterSubmit نیز بسیار شبیه است به حالت اعتبارسنجی سفارشی یک ستون. اگر عضو اول آرایه بازگشت داده شده توسط آن false باشد، یعنی اعتبارسنجی سمت سرور، با شکست مواجه شده و در این حالت از عضو دوم آرایه برای نمایش پیام خطای بازگشت داده شده از طرف سرور استفاده خواهد شد.
اعتبار سنجی ایمیل کاربر
colModel: [ { name: '@(StronglyTyped.PropertyName<User>(x => x.Email))', index: '@(StronglyTyped.PropertyName<User>(x => x.Email))', align: 'center', width: 150, editable: true, edittype: 'text', editoptions: { maxlength: 250, dir: 'ltr' }, editrules: { required: true, email: true }, formatter: 'email' }, ],
مطابق نیازمندیهای اعتبارسنجی پروژه، ایمیل وارد شده نیز نباید تکراری باشد. این مورد نیز توسط خروجی روال رویدادگردان afterSubmit که پیشتر توضیح داده شده، مدیریت میشود.
اعتبار سنجی کلمه عبور کاربر
colModel: [ { name: '@(StronglyTyped.PropertyName<User>(x => x.Password))', index: '@(StronglyTyped.PropertyName<User>(x => x.Password))', align: 'center', width: 70, editable: true, edittype: 'password', editoptions: { maxlength: 10, dir: 'ltr' }, editrules: { //required: true ---> در این حالت خاص قابل استفاده نیست //در حالت ویرایش رکورد، ورود کلمه عبور اختیاری است //در حالت افزودن رکورد، ورود کلمه عبور اجباری است } }, ],
برای این منظور تنها کافی است از روال رویدادگردان beforeSubmit استفاده کرد:
$('#list').jqGrid({ // ... }).navGrid( '#pager', //enabling buttons { add: true, del: true, edit: true, search: false }, //edit option { /*, beforeSubmit: function (posdata, obj) { //در حالت ویرایش رکورد، ورود کلمه عبور اختیاری است return [true, ""]; }*/ }, //add options { beforeSubmit: function (postdata, obj) { //در حالت افزودن رکورد، ورود کلمه عبور اجباری است if (postdata.Password == null || postdata.Password == "" || postdata.Password == undefined) return [false, "لطفا کلمه عبور را وارد کنید"]; return [true, ""]; } }, //delete options { }); });
اعتبار سنجی آدرس سایت کاربر
colModel: [ { name: '@(StronglyTyped.PropertyName<User>(x => x.SiteUrl))', index: '@(StronglyTyped.PropertyName<User>(x => x.SiteUrl))', align: 'center', width: 150, editable: true, edittype: 'text', editoptions: { maxlength: 1000, dir: 'ltr' }, editrules: { required: true, url: true }, formatter: function (cellvalue, options, rowObject) { return "<a href='" + cellvalue + "' >" + cellvalue + "</a>"; }, unformat: function (cellvalue, options, cell) { return $('a', cell).attr('href'); } }, ],
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
jqGrid08.zip
Webgrid گرید توکار asp.net
mvc 3 است که در سری آموزشهای mvc جناب نصیری به خوبی بررسی شده است . WebGrid از طریق مجموعه ای از خواص امکان استایل دهی
به ستونها و ردیفها را به توسعه دهنده میدهد . اما در این بخش مشکلی وجود دارد که
در ادامه به آن خواهم پرداخت . کدهای زیر را در نظر بگیرید
مدلها :
public class Customer { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public string Website { get; set; } public string Phone { get; set; } } public class Customers { public IList<Customer> GetList() { return new List<Customer>() { new Customer() { Id=1, Name="mohsen.d", Email="email@domain.com", Website="domain.com", Phone="213214321" } }; } public IList<Customer> GetEmptyList() { return new List<Customer>(); } }
public class HomeController : Controller { public ActionResult List() { var model = new Customers().GetList(); return View(model); } public ActionResult EmptyList() { var model = new Customers().GetEmptyList(); return View("list", model); } }
تابع کمکی برای ایجاد گرید :
@helper GenerateList(IEnumerable<object> items, List<WebGridColumn> columns) { var grid = new WebGrid(items); <div> @grid.GetHtml( tableStyle: "list", headerStyle: "list-header", footerStyle: "list-footer", alternatingRowStyle: "list-alt", selectedRowStyle: "list-selected", rowStyle: "list-row", htmlAttributes: new { id = "listItems" }, mode: WebGridPagerModes.All, columns: columns ) </div> }
@model IEnumerable<WebGridHeaderStyle.Models.Customer> @{ ViewBag.Title = "List"; } <h2>List</h2> @_List.GenerateList( Model, new List<WebGridColumn>() { new WebGridColumn(){ ColumnName="Id", Header="Id", Style="list-small-field" }, new WebGridColumn(){ ColumnName="Name", Header="Name", Style="list-long-field" }, new WebGridColumn(){ ColumnName="Email", Header="Email", Style="list-mid-field" }, new WebGridColumn(){ ColumnName="Website", Header="Website", Style="list-mid-field" }, new WebGridColumn(){ ColumnName="Phone", Header="Phone", Style="list-mid-field" } } )
خوب چندان بد نیست . با استفاده از استایلهای تعریف شده برای فیلدها و ردیفها ، لیست ساختار مناسبی دارد . اما حالا به Home/EmptyList می رویم :
همانطور که میبینید استایل هایی که برای هر ستون تعریف کرده بودیم اعمال نشده اند. مشکل هم همین
جاست . WebGrid استایل تعریف شده را تنها به ستونهای درون tbody
اعمال میکند و thead از این تنظیمات بی نصیب میماند ( WebGrid از table برای ساختن لیست استفاده میکند ) و در زمانی که رکوردی وجود نداشته باشد فرمت طراحی شده اعمال نمیشود .
در وب ترفندهایی را برای این مشکل پیدا
کردم که اصلا جالب نبودند . در نهایت راه حل زیر به نظرم رسید :
در زمان ساختن
گرید ، استایلهای تعریف شده را در یک فیلد hidden ذخیره و سپس با
استفاده از jquery این استایلها را به ستونهای header اعمال میکنیم .
تابع ساختن فیلد hidden :
@helper SetHeaderColumnsStyle(IEnumerable<WebGridColumn> columns) { var styles = new List<string>(); foreach(var col in columns) { styles.Add(col.Style); } <input id="styles" type="hidden" value="@string.Join("#",styles)" /> }
@SetHeaderColumnsStyle(columns)
<script> $(document).ready(function () { var styles = $("#styles").attr("value").split('#'); var $cols = $("#listItems th"); $cols.each(function (i) { $(this).addClass(styles[i]); }); }); </script>
محدود کردن بارگذاری اشیاء مرتبط یک ViewModel در حین کار با Entity Framework و AutoMapper
public class SiteUser { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<Address> Addresses { get; set; } public virtual ICollection<Email> Emails { get; set; } } public class Email { public int Id { get; set; } public string Text { get; set; } [ForeignKey("SiteUserId")] public virtual SiteUser SiteUser { get; set; } public int SiteUserId { get; set; } } public class Address { public int Id { get; set; } public string Text { get; set; } [ForeignKey("SiteUserId")] public virtual SiteUser SiteUser { get; set; } public int SiteUserId { get; set; } }
public class UserViewModel { public int Id { get; set; } public string Name { get; set; } public ICollection<Address> Addresses { get; set; } public ICollection<Email> Emails { get; set; } }
var user1 = context.Users.Project().To<UserViewModel>().FirstOrDefault();
public class TestProfile : Profile { protected override void Configure() { this.CreateMap<SiteUser, UserViewModel>() .ForMember(dest => dest.Addresses, opt => opt.ExplicitExpansion()) .ForMember(dest => dest.Emails, opt => opt.ExplicitExpansion()); } public override string ProfileName { get { return this.GetType().Name; } } }
پس از تنظیم فوق، اگر کوئری ذکر شده را اجرا کنید، مشاهده خواهید کرد که دو خاصیت آدرسها و ایمیلهای شخص، نال هستند.
برای ذکر صریح خواص راهبری مورد نیاز، اینبار میتوان از پارامترهای متد Project To مانند مثال ذیل استفاده کرد:
using (var context = new MyContext()) { var user1 = context.Users .Project() .To<UserViewModel>(parameters: new { }, membersToExpand: viewModel => viewModel.Emails) .FirstOrDefault(); if (user1 != null) { foreach (var email in user1.Emails) { Console.WriteLine(email.Text); } } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
ثبت وقایع توکار در NET Core.
For an application, logging is very important to keep track of that application and keep it error-free. In .NET Core, we don't need any third party logging; instead, we can use built-in logging whenever we want. This is very efficient in terms of code and performance.
Let’s start. Create a new .NET Core application and name it.