نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیت‌های پایه و DbContext برنامه
- « Identity.Dapper »
- « AspNetCore.Identity.MongoDB »
- این مورد بیشتر به طراحی کل سیستم بر می‌گردد؛ چون اجزای مختلف یک سیستم قرار است هماهنگ با اطلاعات کاربران و سطوح دسترسی آن‌ها کار کنند. بنابراین بهتر است زیرساخت سیستم یکپارچه باشد (اگر NoSQL انتخاب شد، کل سیستم NoSQL طراحی شود) تا مشکلات نگهداری، توسعه و همچنین کاهش کارآیی آن با سوئیچ بین بانک‌های اطلاعاتی مختلف وجود نداشته باشد.
به علاوه با تعویض این موارد در آینده همیشه یک قدم عقب‌تر از تیم ASP.NET Core Identity خواهید بود. همچنین بهینه سازی‌هایی که در اینجا صورت گرفته و طراحی آن، عمیقا با دید به EF Core انجام شده‌است (و اساس طراحی آن هم «رابطه‌ای» است). EF Core وابستگی به SQL Server ندارد و تعداد پروایدرهای رسمی بانک‌های اطلاعاتی مختلف آن قابل توجه است (SQL Server, SQLite, PostgreSQL, MySQL, SQL CE, InMemory) و سیستمی که با یک ORM کار می‌کند، همواره این مزیت قابل تعویض بودن بانک اطلاعاتی برنامه را خواهد داشت.
استفاده‌ی از Dapper و SQL نویسی مستقیم، تنها با یک اشتباه کوچک و فراموشی نکته‌ای، به حملات تزریق اس‌کیوال ختم خواهد شد و این مورد مساله‌ای است که حداقل با EF Core وجود ندارد و فوق العاده مهم است.
نظرات مطالب
EF Code First #5
با تشکر.
در سناریویی برای استفاده از FileStream با EF CodeFirst مجبور شدم از متد Up مربوط به Migration استفاده کنم. با شکل زیر:
  DropColumn("dbo.Judges", "Photo");
  Sql("alter table [dbo].[Judges] add [PhotoTemp] varbinary(max) FILESTREAM not null");
  RenameColumn("dbo.Judges", "PhotoTemp", "Photo");
  Sql("alter table [dbo].[Judges] add constraint [DF_Judges_Photo] default(0x) for [Photo]");
ولی فیلد مورد نظر Photo حذف نمیشود. آیا DropColumn در متد Up  جواب نخواهد داد؟ خیلی دنبال این موضوع گشتم ولی دلیلی برای آن نیافتم.
بروز رسانی:
با حذف کلاس Migration و جنریت کردن دوباره آن و حذف دیتابیس و اجرای دستور update-database ، مشکل حل شد.
نظرات مطالب
چه زمانی بهتر است از بانک‌های اطلاعاتی NoSQL استفاده کرد و چه زمانی خیر؟
با سلام، با توجه به این که Raven Db یکی از دیتابیس‌های No SQL، در پشت صحنه خود از دیتابیس ویندوز با نام ESENT استفاده می‌کند، که مطابق با مستندات سایت‌های معتبر چون MSDN و ویکی پدیا هم Transactional است، و هم دارای امکانات دیگر، آیا می‌توان Transactional بودن را مزیتی صرف برای دیتابیس‌های رابطه ای به شمار آورد، و برای دیتابیس‌های No SQL این مزیت را قائل نشد ؟

Windows comes with an embeddable, transactional database engine 
ESE provides transacted data update and retrieva 
با سپاس
مطالب
EF Code First #14

ردیابی تغییرات در EF Code first

EF از DbContext برای ذخیره اطلاعات مرتبط با تغییرات موجودیت‌های تحت کنترل خود کمک می‌گیرد. این نوع اطلاعات توسط Change Tracker API جهت بررسی وضعیت فعلی یک شیء، مقادیر اصلی و مقادیر تغییر کرده آن در دسترس هستند. همچنین در اینجا امکان بارگذاری مجدد اطلاعات موجودیت‌ها از بانک اطلاعاتی جهت اطمینان از به روز بودن آن‌ها تدارک دیده شده است. ساده‌ترین روش دستیابی به این اطلاعات، استفاده از متد context.Entry می‌باشد که یک وهله از موجودیتی خاص را دریافت کرده و سپس به کمک خاصیت State خروجی آن، وضعیت‌هایی مانند Unchanged یا Modified را می‌توان به دست آورد. علاوه بر آن خروجی متد context.Entry، دارای خواصی مانند CurrentValues و OriginalValues نیز می‌باشد. OriginalValues شامل مقادیر خواص موجودیت درست در لحظه اولین بارگذاری در DbContext برنامه است. CurrentValues مقادیر جاری و تغییر یافته موجودیت را باز می‌گرداند. به علاوه این خروجی امکان فراخوانی متد GetDatabaseValues را جهت بدست آوردن مقادیر جدید ذخیره شده در بانک اطلاعاتی نیز ارائه می‌دهد. ممکن است در این بین، خارج از Context جاری، اطلاعات بانک اطلاعاتی توسط کاربر دیگری تغییر کرده باشد. به کمک GetDatabaseValues می‌توان به این نوع اطلاعات نیز دست یافت.
حداقل چهار کاربرد عملی جالب را از اطلاعات موجود در Change Tracker API می‌توان مثال زد که در ادامه به بررسی آن‌ها خواهیم پرداخت.


کلاس‌های مدل مثال جاری

در اینجا یک رابطه many-to-one بین جدول هزینه‌های اقلام خریداری شده یک شخص و جدول فروشندگان تعریف شده است:

using System;

namespace EF_Sample09.DomainClasses
{
public abstract class BaseEntity
{
public int Id { get; set; }

public DateTime CreatedOn { set; get; }
public string CreatedBy { set; get; }

public DateTime ModifiedOn { set; get; }
public string ModifiedBy { set; get; }
}
}

using System;

namespace EF_Sample09.DomainClasses
{
public class Bill : BaseEntity
{
public decimal Amount { set; get; }
public string Description { get; set; }

public virtual Payee Payee { get; set; }
}
}

using System.Collections.Generic;

namespace EF_Sample09.DomainClasses
{
public class Payee : BaseEntity
{
public string Name { get; set; }

public virtual ICollection<Bill> Bills { set; get; }
}
}


به علاوه همانطور که ملاحظه می‌کنید، این کلاس‌ها از یک abstract class به نام BaseEntity مشتق شده‌اند. هدف از این کلاس پایه تنها تامین یک سری خواص تکراری در کلاس‌های برنامه است و هدف از آن، مباحث ارث بری مانند TPH، TPT و TPC نیست.
به همین جهت برای اینکه این کلاس پایه تبدیل به یک جدول مجزا و یا سبب یکی شدن تمام کلاس‌ها در یک جدول نشود، تنها کافی است آن‌را به عنوان DbSet معرفی نکنیم و یا می‌توان از متد Ignore نیز استفاده کرد:

using System.Data.Entity;
using EF_Sample09.DomainClasses;

namespace EF_Sample09.DataLayer.Context
{
public class Sample09Context : MyDbContextBase
{
public DbSet<Bill> Bills { set; get; }
public DbSet<Payee> Payees { set; get; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<BaseEntity>();

base.OnModelCreating(modelBuilder);
}
}
}



الف) به روز رسانی اطلاعات Context در صورتیکه از متد context.Database.ExecuteSqlCommand مستقیما استفاده شود

در قسمت قبل با متد context.Database.ExecuteSqlCommand برای اجرای مستقیم عبارات SQL بر روی بانک اطلاعاتی آشنا شدیم. اگر این متد در نیمه کار یک Context فراخوانی شود، به معنای کنار گذاشتن Change Tracker API می‌باشد؛ زیرا اکنون در سمت بانک اطلاعاتی اتفاقاتی رخ داده‌اند که هنوز در Context جاری کلاینت منعکس نشده‌اند:

using System;
using System.Data.Entity;
using EF_Sample09.DataLayer.Context;
using EF_Sample09.DomainClasses;

namespace EF_Sample09
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample09Context, Configuration>());

using (var db = new Sample09Context())
{
var payee = new Payee { Name = "فروشگاه سر کوچه" };
var bill = new Bill { Amount = 4900, Description = "یک سطل ماست", Payee = payee };
db.Bills.Add(bill);

db.SaveChanges();
}

using (var db = new Sample09Context())
{
var bill1 = db.Bills.Find(1);
bill1.Description = "ماست";

db.Database.ExecuteSqlCommand("Update Bills set Description=N'سطل ماست' where id=1");
Console.WriteLine(bill1.Description);

db.Entry(bill1).Reload(); //Refreshing an Entity from the Database
Console.WriteLine(bill1.Description);

db.SaveChanges();
}
}
}
}

در این مثال ابتدا دو رکورد به بانک اطلاعاتی اضافه می‌شوند. سپس توسط متد db.Bills.Find، اولین رکورد جدول Bills بازگشت داده می‌شود. در ادامه، خاصیت توضیحات آن به روز شده و سپس با استفاده از متد db.Database.ExecuteSqlCommand نیز بار دیگر خاصیت توضیحات اولین رکورد به روز خواهد شد.
اکنون اگر مقدار bill1.Description را بررسی کنیم، هنوز دارای مقدار پیش از فراخوانی db.Database.ExecuteSqlCommand می‌باشد، زیرا تغییرات سمت بانک اطلاعاتی هنوز به Context مورد استفاده منعکس نشده است.
در اینجا برای هماهنگی کلاینت با بانک اطلاعاتی، کافی است متد Reload را بر روی موجودیت مورد نظر فراخوانی کنیم.



ب) یکسان سازی ی و ک اطلاعات رشته‌ای دریافتی پیش از ذخیره سازی در بانک اطلاعاتی

یکی از الزامات برنامه‌های فارسی، یکسان سازی ی و ک دریافتی از کاربر است. برای این منظور باید پیش از فراخوانی متد SaveChanges نهایی،‌ مقادیر رشته‌ای کلیه موجودیت‌ها را یافته و به روز رسانی کرد:

using System;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Reflection;
using EF_Sample09.DataLayer.Toolkit;
using EF_Sample09.DomainClasses;

namespace EF_Sample09.DataLayer.Context
{
public class MyDbContextBase : DbContext
{
public void RejectChanges()
{
foreach (var entry in this.ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Modified:
entry.State = EntityState.Unchanged;
break;

case EntityState.Added:
entry.State = EntityState.Detached;
break;
}
}
}

public override int SaveChanges()
{
applyCorrectYeKe();
auditFields();
return base.SaveChanges();
}

private void applyCorrectYeKe()
{
//پیدا کردن موجودیت‌های تغییر کرده
var changedEntities = this.ChangeTracker
.Entries()
.Where(x => x.State == EntityState.Added || x.State == EntityState.Modified);

foreach (var item in changedEntities)
{
if (item.Entity == null) continue;

//یافتن خواص قابل تنظیم و رشته‌ای این موجودیت‌ها
var propertyInfos = item.Entity.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance
).Where(p => p.CanRead && p.CanWrite && p.PropertyType == typeof(string));

var pr = new PropertyReflector();

//اعمال یکپارچگی نهایی
foreach (var propertyInfo in propertyInfos)
{
var propName = propertyInfo.Name;
var val = pr.GetValue(item.Entity, propName);
if (val != null)
{
var newVal = val.ToString().Replace("ی", "ی").Replace("ک", "ک");
if (newVal == val.ToString()) continue;
pr.SetValue(item.Entity, propName, newVal);
}
}
}
}

private void auditFields()
{
// var auditUser = User.Identity.Name; // in web apps
var auditDate = DateTime.Now;
foreach (var entry in this.ChangeTracker.Entries<BaseEntity>())
{
// Note: You must add a reference to assembly : System.Data.Entity
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedOn = auditDate;
entry.Entity.ModifiedOn = auditDate;
entry.Entity.CreatedBy = "auditUser";
entry.Entity.ModifiedBy = "auditUser";
break;

case EntityState.Modified:
entry.Entity.ModifiedOn = auditDate;
entry.Entity.ModifiedBy = "auditUser";
break;
}
}
}
}
}


اگر به کلاس Context مثال جاری که در ابتدای بحث معرفی شد دقت کرده باشید به این نحو تعریف شده است (بجای DbContext از MyDbContextBase مشتق شده):
public class Sample09Context : MyDbContextBase
علت هم این است که یک سری کد تکراری را که می‌توان در تمام Contextها قرار داد، بهتر است در یک کلاس پایه تعریف کرده و سپس از آن ارث بری کرد.
تعاریف کامل کلاس MyDbContextBase را در کدهای فوق ملاحظه می‌کنید.
در اینجا کار با تحریف متد SaveChanges شروع می‌شود. سپس در متد applyCorrectYeKe کلیه موجودیت‌های تحت نظر ChangeTracker که تغییر کرده باشند یا به آن اضافه شده‌ باشند، یافت شده و سپس خواص رشته‌ای آن‌ها جهت یکسانی سازی ی و ک، بررسی می‌شوند.


ج) ساده‌تر سازی به روز رسانی فیلدهای بازبینی یک رکورد مانند DateCreated، DateLastUpdated و امثال آن بر اساس وضعیت جاری یک موجودیت

در کلاس MyDbContextBase فوق، کار متد auditFields، مقدار دهی خودکار خواص تکراری تاریخ ایجاد، تاریخ به روز رسانی، شخص ایجاد کننده و شخص تغییر دهنده یک رکورد است. به کمک ChangeTracker می‌توان به موجودیت‌هایی از نوع کلاس پایه BaseEntity دست یافت. در اینجا اگر entry.State آن‌ها مساوی EntityState.Added بود، هر چهار خاصیت یاد شده به روز می‌شوند. اگر حالت موجودیت جاری، EntityState.Modified بود، تنها خواص مرتبط با تغییرات رکورد به روز خواهند شد.
به این ترتیب دیگر نیازی نیست تا در حین ثبت یا ویرایش اطلاعات برنامه نگران این چهار خاصیت باشیم؛ زیرا به صورت خودکار مقدار دهی خواهند شد.


د) پیاده سازی قابلیت لغو تغییرات در برنامه

علاوه بر این‌ها در کلاس MyDbContextBase، متد RejectChanges نیز تعریف شده است تا بتوان در صورت نیاز، حالت موجودیت‌های تغییر کرده یا اضافه شده را به حالت پیش از عملیات، بازگرداند.



مطالب
ایجاد و نگهداری Stored Procedure و View با استفاده از Migration
اگر از Entity Framework به روش Code-First استفاده میکنید و بر حسب نیاز، لازم است Stored procedure, View و یا Function ی ایجاد و استفاده نمایید، بهتر است برای ایجاد و نگهداری آنها از Migration‌ها استفاده نمایید. مزیت این روش این است که بر روی سورس کنترل قرار دارد و افراد تیم با گرفتن پروژه و اجرا کردن دستور Update-Database آخرین تغییرات را خواهند داشت.

فرض کنید میخواهید یک Procedure را بر روی دیتابیس ایجاد نمایید که وظیفه‌ی درج یک نویسنده را در جدول Authors، برعهده دارد. برای این منظور SP زیر لازم است نوشته شود:
CREATE PROCEDURE CreateNewAuthor
@name nvarchar(50)
AS
BEGIN
INSERT INTO Authors values(@name)
END
GO

برای ایجاد خودکار این SP میخواهیم از Migration استفاده نماییم. برای این کار ابتدا یک Migration جدید را با دستور زیر ایجاد میکنیم:
Add-Migration StoredProcedureForCreateNewAuthor
اگر مشکلی در بیلد پروژه وجود نداشته باشد، خروجی زیر حاصل می‌شود که یک فایل خالی می‌باشد:
 public partial class StoredProcedureForCreateNewAuthor : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {

        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {

        }
    }
در اینجا کاری که باید انجام شود این است که در متد Up که در زمان به روزرسانی دیتابیس اجرا می‌گردد، SP (View یا Function) را ایجاد نماییم. برای اینکار از متد Sql بر روی migrationBuilder، همانند کدهای زیر استفاده می‌کنیم:
protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql(@"CREATE PROCEDURE CreateNewAuthor
                                    @name nvarchar(50)
                                    AS
                                    BEGIN
                                        INSERT INTO Authors values(@name)
                                    END
                                    GO");
        }
با اجرای دستور Update-Database پروسیجر فوق در دیتابیس ایجاد می‌شود.
لازم به ذکر است، اگر با طرز کار Migration‌ها آشنایی داشته باشید، متد down، عکس عملکرد متد Up می‌باشد و وظیفه‌ی حذف تمامی تغییرات مربوط به Migration فعلی را دارد. با این اوصاف، نیاز هست متد Down را به صورت زیر تغییر دهیم:
 protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql("DROP PROCEDURE CreateNewAuthor");
        }

- در این مقاله فقط در مورد Stored Procedure‌ها صحبت شد؛ همین کار را میتوانید برای View‌ها هم انجام دهید.