مطالب
MongoDB #3

محیط MongoDB

نصب MongoDB در ویندوز

برای نصب MongoDB در ویندوز، اول باید آخرین نسخه MongoDB را از آدرس http://www.mongodb.org/downloads دریافت کنید. مطمئن شوید که نسخه‌ی صحیحی از MongoDB را نسبت به معماری ویندوزتان دریافت کرده‌اید. برای پیدا کردن معماری ویندوز، پنجره‌ی Command Prompt را باز کنید و دستور زیر را اجرا کنید:

C:\>wmic os get osarchitecture
OSArchitecture
64-bit
C:\>

نسخه‌های 32بیتی MongoDB فقط پایگاه داده‌های کوچکتر از 2 گیگابایت را پشتیبانی می‌کنند و صرفا برای تست و ارزیابی مناسب هستند. اکنون فایل دریافتی را نصب کنید. MongoDB یک پوشه داده، برای ذخیره فایل‌هایش نیاز دارد. مسیر پیش فرض پوشه داده c:\data\db است؛ بنابراین نیاز دارید این پوشه را بسازید. شما می‌توانید یک مسیر دیگر را نیز برای مسیر داده تنظیم کنید. برای انجام این کار، Command Prompt را در پوشه bin (در مسیر نصب شده MongoDB) باز کنید و دستور زیر را اجرا کنید: (فرض کنید MongoDB در مسیر D:\set up\mongodb  نصب شده است)

D:\set up\mongodb\bin>mongod.exe --dbpath "d:\set up\mongodb\data"

بعد از اجرای دستور، پیام “waiting for connections” در کنسول نمایش داده می‌شود که نشان دهنده‌ی این است که پروسه Mongod.exe با موفقیت اجرا شده است. حالا برای اجرای MongoDB یک Command Prompt دیگر نیاز دارید تا دستور زیر را اجرا کنید:

D:\set up\mongodb\bin>mongo.exe
MongoDB shell version: 2.6.6
connecting to: test

>db.test.save( { a: 1 } )
>db.test.find()
{ "_id" : ObjectId(5879b0f65a56a454), "a" : 1 }
>

این دستور نشان خواهد داد که MongoDB نصب و با موفقیت اجرا شده‌است. برای اجرای MongoDB در دفعات بعدی نیز همین 2 مرحله را تکرار کنید (تعیین مسیر پوشه داده و اجرای Mongo.exe در یک Command Prompt دیگر).


نصب MongoDB در اوبونتو

دستور زیر را برای وارد کردن کلید GPG عمومی MongoDB در ترمینال اجرا کنید:

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10

فایل /etc/apt/sources.list.d/mongodb.list را با دستور زیر بسازید:

echo  'deb  http://downloads-distro.mongodb.org/repo/ubuntu-upstart  dist  10gen'  |  sudo  tee 
/etc/apt/sources.list.d/mongodb.list

اکنون دستور زیر را برای بروز رسانی مخازن پکیج‌ها اجرا کنید:

sudo apt-get update

حالا MongoDB را با استفاده از دستور زیر نصب کنید:

apt-get install mongodb-10gen=2.2.3

در دستور نصب فوق، به نسخه‌ی 2.2.3 از MongoDB انتشار شده است. همیشه مطمئن شوید که آخرین نسخه را نصب کرده اید. اکنون MongoDB با موفقیت نصب شده است.

راه اندازی MongoDB

sudo service mongodb start


متوقف کردن MongoDB

sudo service mongodb stop


راه اندازی مجدد MongoDB

sudo service mongodb restart


برای استفاده از MongoDB از دستور زیر استفاده کنید:

Mongo


این دستور شما را به نمونه‌ی در حال اجرای Mongod متصل خواهد کرد.


راهنمای MongoDB

برای دریافت لیست دستورات، ()db.help را در نسخه کلاینت MongoDB تایپ کنید. این دستور، لیست دستورات را مانند تصویر زیر به شما می‌دهد: 


آمار و ارقام در MongoDB

برای گرفتن آمار و ارقام از MongoDB سرور، دستور ()db.stats را در نسخه کلاینت MongoDB تایپ کنید. این دستور نام پایگاه داده، تعداد مجموعه‌ها و سندهای موجود در پایگاه داده را نمایش می‌دهد:

نظرات مطالب
EF Code First #3
- ایجاد کلید منحصربفرد ترکیبی روی چند ستون
+ یک سری از فضاهای نام از EF 4 به EF 6 اندکی تغییر کرده‌اند که در مطلب «ارتقاء به Entity framework 6 » به آن اشاره شده‌است. در کل باید اجازه دهید تا NuGet ارجاعات قدیمی را به صورت خودکار حذف کند و ارجاعات جدید را اضافه کند. بعد هم از فضای نام جدید بدون مشکل می‌توانید استفاده کنید.
PM> update-package
+ اگر از دات نت 4 استفاده می‌کردید و اکنون برنامه را به دات نت 4.5 ارتقاء دادید، باید این دستور را صادر کنید:
PM> update-package -reinstall
به این ترتیب از EF مخصوص دات نت 4.5 استفاده خواهد شد. در غیر اینصورت تداخل فضای نام پیدا می‌کنید.
مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت چهارم
در قسمت قبل تشخیص تغییرات توسط Web API را بررسی کردیم. در این قسمت نگاهی به پیاده سازی Change-tracking در سمت کلاینت خواهیم داشت.


ردیابی تغییرات در سمت کلاینت توسط Web API

فرض کنید می‌خواهیم از سرویس‌های REST-based برای انجام عملیات CRUD روی یک Object graph استفاده کنیم. همچنین می‌خواهیم رویکردی در سمت کلاینت برای بروز رسانی کلاس موجودیت‌ها پیاده سازی کنیم که قابل استفاده مجدد (reusable) باشد. علاوه بر این دسترسی داده‌ها توسط مدل Code-First انجام می‌شود.

در مثال جاری یک اپلیکیشن کلاینت (برنامه کنسول) خواهیم داشت که سرویس‌های ارائه شده توسط پروژه Web API را فراخوانی می‌کند. هر پروژه در یک Solution مجزا قرار دارد، با این کار یک محیط n-Tier را شبیه سازی می‌کنیم.

مدل زیر را در نظر بگیرید.

همانطور که می‌بینید مدل مثال جاری مشتریان و شماره تماس آنها را ارائه می‌کند. می‌خواهیم مدل‌ها و کد دسترسی به داده‌ها را در یک سرویس Web API پیاده سازی کنیم تا هر کلاینتی که به HTTP دسترسی دارد بتواند از آن استفاده کند. برای ساخت سرویس مذکور مراحل زیر را دنبال کنید.

  • در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe4.Service تغییر دهید.
  • کنترلر جدیدی با نام CustomerController به پروژه اضافه کنید.
  • کلاسی با نام BaseEntity ایجاد کنید و کد آن را مطابق لیست زیر تغییر دهید. تمام موجودیت‌ها از این کلاس پایه مشتق خواهند شد که خاصیتی بنام TrackingState را به آنها اضافه می‌کند. کلاینت‌ها هنگام ویرایش آبجکت موجودیت‌ها باید این فیلد را مقدار دهی کنند. همانطور که می‌بینید این خاصیت از نوع TrackingState enum مشتق می‌شود. توجه داشته باشید که این خاصیت در دیتابیس ذخیره نخواهد شد. با پیاده سازی enum وضعیت ردیابی موجودیت‌ها بدین روش، وابستگی‌های EF را برای کلاینت از بین می‌بریم. اگر قرار بود وضعیت ردیابی را مستقیما از EF به کلاینت پاس دهیم وابستگی‌های بخصوصی معرفی می‌شدند. کلاس DbContext اپلیکیشن در متد OnModelCreating به EF دستور می‌دهد که خاصیت TrackingState را به جدول موجودیت نگاشت نکند.
public abstract class BaseEntity
{
    protected BaseEntity()
    {
        TrackingState = TrackingState.Nochange;
    }

    public TrackingState TrackingState { get; set; }
}

public enum TrackingState
{
    Nochange,
    Add,
    Update,
    Remove,
}
  • کلاس‌های موجودیت Customer و PhoneNumber را ایجاد کنید و کد آنها را مطابق لیست زیر تغییر دهید.
public class Customer : BaseEntity
{
    public int CustomerId { get; set; }
    public string Name { get; set; }
    public string Company { get; set; }
    public virtual ICollection<Phone> Phones { get; set; }
}

public class Phone : BaseEntity
{
    public int PhoneId { get; set; }
    public string Number { get; set; }
    public string PhoneType { get; set; }
    public int CustomerId { get; set; }
    public virtual Customer Customer { get; set; }
}
  • با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
  • کلاسی با نام Recipe4Context ایجاد کنید و کد آن را مطابق لیست زیر تغییر دهید. در این کلاس از یکی از قابلیت‌های جدید EF 6 بنام "Configuring Unmapped Base Types" استفاده کرده ایم. با استفاده از این قابلیت جدید هر موجودیت را طوری پیکربندی می‌کنیم که خاصیت TrackingState را نادیده بگیرند. برای اطلاعات بیشتر درباره این قابلیت EF 6 به این لینک مراجعه کنید.
public class Recipe4Context : DbContext
{
    public Recipe4Context() : base("Recipe4ConnectionString") { }
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Phone> Phones { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Do not persist TrackingState property to data store
        // This property is used internally to track state of
        // disconnected entities across service boundaries.
        // Leverage the Custom Code First Conventions features from Entity Framework 6.
        // Define a convention that performs a configuration for every entity
        // that derives from a base entity class.
        modelBuilder.Types<BaseEntity>().Configure(x => x.Ignore(y => y.TrackingState));
        modelBuilder.Entity<Customer>().ToTable("Customers");
        modelBuilder.Entity<Phone>().ToTable("Phones");
}
}
  • فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه نمایید.
<connectionStrings>
  <add name="Recipe4ConnectionString"
    connectionString="Data Source=.;
    Initial Catalog=EFRecipes;
    Integrated Security=True;
    MultipleActiveResultSets=True"
    providerName="System.Data.SqlClient" />
</connectionStrings>
  • فایل Global.asax را باز کنید و کد زیر را به متد Application_Start اضافه نمایید. این کد بررسی Entity Framework Model Compatibility را غیرفعال می‌کند و به JSON serializer دستور می‌دهد که self-referencing loop خواص پیمایشی را نادیده بگیرد. این حلقه بدلیل رابطه bidirectional بین موجودیت‌های Customer و PhoneNumber بوجود می‌آید.
protected void Application_Start()
{
    // Disable Entity Framework Model Compatibilty
    Database.SetInitializer<Recipe1Context>(null);
    // The bidirectional navigation properties between related entities
    // create a self-referencing loop that breaks Web API's effort to
    // serialize the objects as JSON. By default, Json.NET is configured
    // to error when a reference loop is detected. To resolve problem,
    // simply configure JSON serializer to ignore self-referencing loops.
    GlobalConfiguration.Configuration.Formatters.JsonFormatter
        .SerializerSettings.ReferenceLoopHandling =
            Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    ...
}
  • کلاسی با نام EntityStateFactory بسازید و کد آن را مطابق لیست زیر تغییر دهید. این کلاس مقدار خاصیت TrackingState که به کلاینت‌ها ارائه می‌شود را به مقادیر متناظر کامپوننت‌های ردیابی EF تبدیل می‌کند.
public static EntityState Set(TrackingState trackingState)
{
    switch (trackingState)
    {
        case TrackingState.Add:
            return EntityState.Added;
        case TrackingState.Update:
            return EntityState.Modified;
        case TrackingState.Remove:
            return EntityState.Deleted;
        default:
            return EntityState.Unchanged;
    }
}
  • در آخر کد کنترلر CustomerController را مطابق لیست زیر بروز رسانی کنید.
public class CustomerController : ApiController
{
    // GET api/customer
    public IEnumerable<Customer> Get()
    {
        using (var context = new Recipe4Context())
        {
            return context.Customers.Include(x => x.Phones).ToList();
        }
    }

    // GET api/customer/5
    public Customer Get(int id)
    {
        using (var context = new Recipe4Context())
        {
            return context.Customers.Include(x => x.Phones).FirstOrDefault(x => x.CustomerId == id);
        }
    }

    [ActionName("Update")]
    public HttpResponseMessage UpdateCustomer(Customer customer)
    {
        using (var context = new Recipe4Context())
        {
            // Add object graph to context setting default state of 'Added'.
            // Adding parent to context automatically attaches entire graph
            // (parent and child entities) to context and sets state to 'Added'
            // for all entities.
            context.Customers.Add(customer);
            foreach (var entry in context.ChangeTracker.Entries<BaseEntity>())
            {
                entry.State = EntityStateFactory.Set(entry.Entity.TrackingState);
                if (entry.State == EntityState.Modified)
                {
                    // For entity updates, we fetch a current copy of the entity
                    // from the database and assign the values to the orginal values
                    // property from the Entry object. OriginalValues wrap a dictionary
                    // that represents the values of the entity before applying changes.
                    // The Entity Framework change tracker will detect
                    // differences between the current and original values and mark
                    // each property and the entity as modified. Start by setting
                    // the state for the entity as 'Unchanged'.
                    entry.State = EntityState.Unchanged;
                    var databaseValues = entry.GetDatabaseValues();
                    entry.OriginalValues.SetValues(databaseValues);
                }
            }

        context.SaveChanges();
    }

    return Request.CreateResponse(HttpStatusCode.OK, customer);
}

    [HttpDelete]
    [ActionName("Cleanup")]
    public HttpResponseMessage Cleanup()
    {
        using (var context = new Recipe4Context())
        {
            context.Database.ExecuteSqlCommand("delete from phones");
            context.Database.ExecuteSqlCommand("delete from customers");
            return Request.CreateResponse(HttpStatusCode.OK);
        }
    }
}
حال اپلیکیشن کلاینت (برنامه کنسول) را می‌سازیم که از این سرویس استفاده می‌کند.

  • در ویژوال استودیو پروژه جدیدی از نوع Console Application بسازید و نام آن را به Recipe4.Client تغییر دهید.
  • فایل program.cs را باز کنید و کد آن را مطابق لیست زیر تغییر دهید.
internal class Program
{
    private HttpClient _client;
    private Customer _bush, _obama;
    private Phone _whiteHousePhone, _bushMobilePhone, _obamaMobilePhone;
    private HttpResponseMessage _response;

    private static void Main()
    {
        Task t = Run();
        t.Wait();
        Console.WriteLine("\nPress <enter> to continue...");
        Console.ReadLine();
    }

    private static async Task Run()
    {
        var program = new Program();
        program.ServiceSetup();
        // do not proceed until clean-up completes
        await program.CleanupAsync();
        program.CreateFirstCustomer();
        // do not proceed until customer is added
        await program.AddCustomerAsync();
        program.CreateSecondCustomer();
        // do not proceed until customer is added
        await program.AddSecondCustomerAsync();
        // do not proceed until customer is removed
        await program.RemoveFirstCustomerAsync();
        // do not proceed until customers are fetched
        await program.FetchCustomersAsync();
    }

    private void ServiceSetup()
    {
        // set up infrastructure for Web API call
        _client = new HttpClient { BaseAddress = new Uri("http://localhost:62799/") };
        // add Accept Header to request Web API content negotiation to return resource in JSON format
        _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue
        ("application/json"));
    }
    private async Task CleanupAsync()
    {
        // call the cleanup method from the service
        _response = await _client.DeleteAsync("api/customer/cleanup/");
    }

    private void CreateFirstCustomer()
    {
        // create customer #1 and two phone numbers
        _bush = new Customer
        {
            Name = "George Bush",
            Company = "Ex President",
            // set tracking state to 'Add' to generate a SQL Insert statement
            TrackingState = TrackingState.Add,
        };
        _whiteHousePhone = new Phone
        {
            Number = "212 222-2222",
            PhoneType = "White House Red Phone",
            // set tracking state to 'Add' to generate a SQL Insert statement
            TrackingState = TrackingState.Add,
        };
        _bushMobilePhone = new Phone
        {
            Number = "212 333-3333",
            PhoneType = "Bush Mobile Phone",
            // set tracking state to 'Add' to generate a SQL Insert statement
            TrackingState = TrackingState.Add,
        };
        _bush.Phones.Add(_whiteHousePhone);
        _bush.Phones.Add(_bushMobilePhone);
    }

    private async Task AddCustomerAsync()
    {
        // construct call to invoke UpdateCustomer action method in Web API service
        _response = await _client.PostAsync("api/customer/updatecustomer/", _bush, new JsonMediaTypeFormatter());
        if (_response.IsSuccessStatusCode)
        {
            // capture newly created customer entity from service, which will include
            // database-generated Ids for all entities
            _bush = await _response.Content.ReadAsAsync<Customer>();
            _whiteHousePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _bush.CustomerId);
            _bushMobilePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _bush.CustomerId);
            Console.WriteLine("Successfully created Customer {0} and {1} Phone Numbers(s)",
            _bush.Name, _bush.Phones.Count);
            foreach (var phoneType in _bush.Phones)
            {
                Console.WriteLine("Added Phone Type: {0}", phoneType.PhoneType);
            }
        }
        else
            Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase);
    }

    private void CreateSecondCustomer()
    {
        // create customer #2 and phone numbers
        _obama = new Customer
        {
            Name = "Barack Obama",
            Company = "President",
            // set tracking state to 'Add' to generate a SQL Insert statement
            TrackingState = TrackingState.Add,
        };
        _obamaMobilePhone = new Phone
        {
            Number = "212 444-4444",
            PhoneType = "Obama Mobile Phone",
            // set tracking state to 'Add' to generate a SQL Insert statement
            TrackingState = TrackingState.Add,
        };
        // set tracking state to 'Modifed' to generate a SQL Update statement
        _whiteHousePhone.TrackingState = TrackingState.Update;
        _obama.Phones.Add(_obamaMobilePhone);
        _obama.Phones.Add(_whiteHousePhone);
    }

    private async Task AddSecondCustomerAsync()
    {
        // construct call to invoke UpdateCustomer action method in Web API service
        _response = await _client.PostAsync("api/customer/updatecustomer/", _obama, new JsonMediaTypeFormatter());
        if (_response.IsSuccessStatusCode)
        {
            // capture newly created customer entity from service, which will include
            // database-generated Ids for all entities
            _obama = await _response.Content.ReadAsAsync<Customer>();
            _whiteHousePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _obama.CustomerId);
            _bushMobilePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _obama.CustomerId);
            Console.WriteLine("Successfully created Customer {0} and {1} Phone Numbers(s)",
            _obama.Name, _obama.Phones.Count);
            foreach (var phoneType in _obama.Phones)
            {
                Console.WriteLine("Added Phone Type: {0}", phoneType.PhoneType);
            }
        }
        else
            Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase);
    }

    private async Task RemoveFirstCustomerAsync()
    {
        // remove George Bush from underlying data store.
        // first, fetch George Bush entity, demonstrating a call to the
        // get action method on the service while passing a parameter
        var query = "api/customer/" + _bush.CustomerId;
        _response = _client.GetAsync(query).Result;

        if (_response.IsSuccessStatusCode)
        {
            _bush = await _response.Content.ReadAsAsync<Customer>();
            // set tracking state to 'Remove' to generate a SQL Delete statement
            _bush.TrackingState = TrackingState.Remove;
            // must also remove bush's mobile number -- must delete child before removing parent
            foreach (var phoneType in _bush.Phones)
            {
                // set tracking state to 'Remove' to generate a SQL Delete statement
                phoneType.TrackingState = TrackingState.Remove;
            }
            // construct call to remove Bush from underlying database table
            _response = await _client.PostAsync("api/customer/updatecustomer/", _bush, new JsonMediaTypeFormatter());
            if (_response.IsSuccessStatusCode)
            {
                Console.WriteLine("Removed {0} from database", _bush.Name);
                foreach (var phoneType in _bush.Phones)
                {
                    Console.WriteLine("Remove {0} from data store", phoneType.PhoneType);
                }
            }
            else
                Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase);
        }
        else
        {
            Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase);
        }
    }

    private async Task FetchCustomersAsync()
    {
        // finally, return remaining customers from underlying data store
        _response = await _client.GetAsync("api/customer/");
        if (_response.IsSuccessStatusCode)
        {
            var customers = await _response.Content.ReadAsAsync<IEnumerable<Customer>>();
            foreach (var customer in customers)
            {
                Console.WriteLine("Customer {0} has {1} Phone Numbers(s)",
                customer.Name, customer.Phones.Count());
                foreach (var phoneType in customer.Phones)
                {
                    Console.WriteLine("Phone Type: {0}", phoneType.PhoneType);
                }
            }
        }
        else
        {
            Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase);
        }
    }
}

  • در آخر کلاس‌های Customer, Phone و BaseEntity را به پروژه کلاینت اضافه کنید. چنین کدهایی بهتر است در لایه مجزایی قرار گیرند و بین لایه‌های مختلف اپلیکیشن به اشتراک گذاشته شوند.

اگر اپلیکیشن کلاینت را اجرا کنید با خروجی زیر مواجه خواهید شد.








شرح مثال جاری

با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک MVC Web Controller دارد که پس از اجرا شما را به صفحه خانه هدایت می‌کند. در این مرحله سایت در حال اجرا است و سرویس‌ها قابل دسترسی هستند.

سپس اپلیکیشن کنسول را باز کنید و روی خط اول کد فایل program.cs یک breakpoint قرار داده و آن را اجرا کنید. ابتدا آدرس سرویس را نگاشت می‌کنیم و از سرویس درخواست می‌کنیم که اطلاعات را با فرمت JSON بازگرداند.

سپس توسط متد DeleteAsync که روی آبجکت HttpClient تعریف شده است اکشن متد Cleanup را روی سرویس فراخوانی می‌کنیم. این فراخوانی تمام داده‌های پیشین را حذف می‌کند.

در قدم بعدی یک مشتری بهمراه دو شماره تماس می‌سازیم. توجه کنید که برای هر موجودیت مشخصا خاصیت TrackingState را مقدار دهی می‌کنیم تا کامپوننت‌های Change-tracking در EF عملیات لازم SQL برای هر موجودیت را تولید کنند.

سپس توسط متد PostAsync که روی آبجکت HttpClient تعریف شده اکشن متد UpdateCustomer را روی سرویس فراخوانی می‌کنیم. اگر به این اکشن متد یک breakpoint اضافه کنید خواهید دید که موجودیت مشتری را بعنوان یک پارامتر دریافت می‌کند و آن را به context جاری اضافه می‌نماید. با اضافه کردن موجودیت به کانتکست جاری کل object graph اضافه می‌شود و EF شروع به ردیابی تغییرات آن می‌کند. دقت کنید که آبجکت موجودیت باید Add شود و نه Attach.

قدم بعدی جالب است، هنگامی که از خاصیت DbChangeTracker استفاده می‌کنیم. این خاصیت روی آبجکت context تعریف شده و یک <IEnumerable<DbEntityEntry را با نام Entries ارائه می‌کند. در اینجا بسادگی نوع پایه EntityType را تنظیم میکنیم. این کار به ما اجازه می‌دهد که در تمام موجودیت هایی که از نوع BaseEntity هستند پیمایش کنیم. اگر بیاد داشته باشید این کلاس، کلاس پایه تمام موجودیت‌ها است. در هر مرحله از پیمایش (iteration) با استفاده از کلاس EntityStateFactory مقدار خاصیت TrackingState را به مقدار متناظر در سیستم ردیابی EF تبدیل می‌کنیم. اگر کلاینت مقدار این فیلد را به Modified تنظیم کرده باشد پردازش بیشتری انجام می‌شود. ابتدا وضعیت موجودیت را از Modified به Unchanged تغییر می‌دهیم. سپس مقادیر اصلی را با فراخوانی متد GetDatabaseValues روی آبجکت Entry از دیتابیس دریافت می‌کنیم. فراخوانی این متد مقادیر موجود در دیتابیس را برای موجودیت جاری دریافت می‌کند. سپس مقادیر بدست آمده را به کلکسیون OriginalValues اختصاص می‌دهیم. پشت پرده، کامپوننت‌های EF Change-tracking بصورت خودکار تفاوت‌های مقادیر اصلی و مقادیر ارسالی را تشخیص می‌دهند و فیلدهای مربوطه را با وضعیت Modified علامت گذاری می‌کنند. فراخوانی‌های بعدی متد SaveChanges تنها فیلدهایی که در سمت کلاینت تغییر کرده اند را بروز رسانی خواهد کرد و نه تمام خواص موجودیت را.

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

متد UpdateCustomer در سرویس ما مقادیر TrackingState را به مقادیر متناظر EF تبدیل می‌کند و آبجکت‌ها را به موتور change-tracking ارسال می‌کند که نهایتا منجر به تولید دستورات لازم SQL می‌شود.

نکته: در اپلیکیشن‌های واقعی بهتر است کد دسترسی داده‌ها و مدل‌های دامنه را به لایه مجزایی منتقل کنید. همچنین پیاده سازی فعلی change-tracking در سمت کلاینت می‌تواند توسعه داده شود تا با انواع جنریک کار کند. در این صورت از نوشتن مقادیر زیادی کد تکراری جلوگیری خواهید کرد و از یک پیاده سازی می‌توانید برای تمام موجودیت‌ها استفاده کنید.

نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 2 - به روز رسانی ساختار بانک اطلاعاتی
من در یک پروژه دستور ایجاد migration را وارد میکنم ولی متاسفانه با خطای Build Failed روبرو میشود . در صورتی که خود پروژه به راحتی بیلد میشود. پروژه اصلی شامل پوشه بندی برای لایه دیتا و سرویس و .. میباشد و رفرنسی به پروژه دیگر برای این موضوع به آن وجود ندارد.
مطالب
استفاده از درایوها در Window Azure Storage جهت استفاده در RavenDB
در تلاش برای راه اندازی دیتابیس RavenDB بر روی Windows Azure چند مقاله ‌ای خوندم که گاهی خیلی گیج کننده بود. الان تقریباً به نتایجی رسیده‌ام و دوست دارم در این مقاله نکاتی رو که به نظرم دانستن آنها بایسته است را مطرح کنم. باشد که مفید واقع شود.

پیش زمینه 1، یکی دیگر از روشهای راه اندازی RavenDB:
راه اندازی سرویس، نصب بر روی IIS و استفاده به صورت توکار، روش‌هایی هستند که در خود مستندات نچندان کامل RavenDB در حال حاضر مطرح شده است. راه دیگری که برای راه اندازی RavenDB می‌تواند مورد استفاده قرار گیرد، از طریق برنامه نویسی است. یعنی سرور RavenDB را با اجرای کد بالا می‌آوریم. نگران نباشید، این کار خیلی سخت نیست و به سادگی از طریق نمونه سازی از کلاس HttpServer و ارائه پارامترهای پیکره‌بندی و فراخوانی یک و یا دو متود می‌تواند صورت گیرد. مزیت این روش در پویایی و انعطاف پذیری آن است. شما می‌توانید هر تعداد سرور را با هر پیکره‌بندی پویایی، بالا بیاورید.
به کلمه HttpServer خوب دقت کنید. بله، درست است؛ این یک سرور کامل است و تمام درخواست‌های Http را طبق قواعد RavenDB و البته HTTP پاسخ می‌دهد. حتی studio ی RavenDB ,که یک برنامه Silverlight است, نیز سرو میشود. (برنامه Silverlight در ریسورسهای RavenDB.Database.dll توکار(embed) شده است.)
کد مینیمالیست نمونه، یک RavenDB http server در قالب یک برنامه Console Application:
static void Main(string[] args)
{
    var configuration = new Raven.Database.Config.RavenConfiguration() {
        AccessControlAllowMethods = "All",
        AnonymousUserAccessMode = Raven.Database.Server.AnonymousUserAccessMode.All,
        DataDirectory = @"C:\Sam\labs\HttpServerData",
        Port = 8071,
    };
    var database = new Raven.Database.DocumentDatabase(configuration);
    var server = new Raven.Database.Server.HttpServer(configuration, database);
    database.SpinBackgroundWorkers();
    server.StartListening();

    Console.WriteLine("RavenDB http server is running ...");
    Console.ReadLine();
}
با اجرای برنامه فوق، پایگاه داده شما در پورت 8071 ماشین، فعال است و آماده پاسخگویی. استودیوی RavenDB نیز از طریق مسیر http://127.0.0.1:8071 قابل دسترسی است.
چرا این مطلب را گفتم، چون برای راه اندازی RavenDB در Azure می‌خواهیم از این روش استفاده کنیم. در یک worker role دیگر ما نه IIS داریم و نه یک virtual machine در اختیار داریم تا یک service را بر روی آن نصب کنیم. پس بهترین گزینه برای ما راه اندازی سرور RavenDB از طریق برنامه نویسی است.

پیش زمینه 2، چندساکنی در RavenDB و مسیر داده ها:(Multi Tenancy)
یک سرور RavenDB می‌تواند چندین پایگاه داده را میزبانی کند. هر چند به طور پیش فرض تک ساکنی برگزیده شده است. اما شما می‌توانید پایگاه‌های داده جدید را به سیستم اضافه کنید. مشکلی که من با مستندات RavenDB دارم این است که به طور پیش فرض درباره زمانی مصداق پیدا می‌کنند که RavenDB در حالت تک ساکنی مورد استفاده قرار میگیرد. 
مهم است که بدانید مسیری که به عنوان مسیر داده‌ها در هنگام راه اندازی سرور ارائه می‌دهید برای پایگاه داده پیش فرض مورد استفاده قرار میگیرد و باید مسیرهای جداگانه مستقلی برای پایگاه داده‌های بعدی تنظیم کنید.
توجه داشته باشید که در RavenDB اگر در هنگام ساخت پایگاه داده، مسیری را مطرح نکنید، مسیر پیش فرض انتخاب خواهد شد. همچنین در حالت چندساکنی هم هیچ ارتباطی بین پایگاه‌های داده بعدی با پایگاه داده <system> وجود ندارد و همواره مسیر پیش فرض به صورت ~/Databases/dbName خواهد بود که dbName نام پایگاه داده مورد نظر شما است. مهم است که بدانید که ~ در مسیر فوق دارای تعریف رسمی ای نیست و آنچه از کد بر می‌آید ~ مسیر BaseDirectory برای AppDomain جاری است. پس با توجه اینکه نوع برنامه میزبان سرور چیست (IIS, Windows Service, Worker Role) مقدار آن می‌تواند متفاوت باشد.

تعریف Worker Role برای RavenDB
در واقع مطلب اصلی درباره نحوه استفاده از CloudDrive در Web Role یا Worker Role است. همانطور که میدانید Web Role و Worker Role هر دو برای ذخیره سازی داده‌ها مناسب نیستند. در واقع بایستی با این رویکرد به آنها نگاه کنید که فقط کدهای اجرایی بر روی آنها قرار بگیرند و نه چیز دیگری. در مورد استفاده پایگاه داده RavenDB در Windows Azure می‌توانید آن را به صورت یک Worker Role تعریف کنید. اما برای اینکه داده‌ها را ذخیره کنید بایستی از یک Cloud Drive استفاده کنید.
خوب، در ابتد لازم است که کمی درباره‌ی CloudDrive بدانیم؛ خواندن این مطلب درباره‌ی اولین انتشار Windows Azure Drive خالی از لطف نیست.
حالا برای اینکه RavenDB را راه بیاندازیم باید نخست Wroker Role را بسازیم و سپس قطعه کدی بنویسیم تا درایو مجزا و مختصی را برای اینکه RavenDB اطلاعات را در آن بریزد بسازد. در آخر باید Worker Role را تنظیم کنیم تا درایو ساخته شده را در خود mount کند.
برای ساختن درایو قطعه کد زیر آن را انجام میدهد:
CloudStorageAccount storageAccount = CloudStorageAccount.FromConfigurationSetting(connectionString);
// here is when later on you may add code for inititalizing CloudDrive chache
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
blobClient.GetContainerReference("drives").CreateIfNotExist();

CloudDrive cloudDrive = storageAccount.CreateCloudDrive(
    blobClient
    .GetContainerReference("drives")
    .GetPageBlobReference("ravendb4.vhd")
    .Uri.ToString()
);

try
{
    // create a 1GB Virtual Hard Drive
    cloudDrive.Create(1024);
}
catch (CloudDriveException /*ex*/ )
{
    // the most likely exception here is ERROR_BLOB_ALREADY_EXISTS
    // exception is also thrown if the drive already exists 
} 
در کد فوق نامهای drives و ravendb.vhd کاملاً اختیاری هستند. اما باید از قواعد نامگذاری container پیروی کنند.
برای سوار کردن درایو قطعه کد زیر آن را انجام میدهد:
string driveLetter = cloudDrive.Mount(25, DriveMountOptions.Force);
توجه داشته باشید که کد سوار کردن درایو، قاعدتاً، بایستی در Worker Role صورت بگیرد و همچنین باید قبل از راه اندازی RavenDB باشد.
این یک ایراد طراحی Windows Azure است که شما نمیتوانید حرف درایو را خودتان انتخاب کنید، بلکه خروجی متود Mount مشخص میکند که درایو در چه حرف درایوی سوار شده است. و شما محدود هستند که کدهای خود را به گونه ای بنویسید که مسیر ذخیره سازی اطلاعات در Cloud Drive را ثابت فرض نکند و ارجاعات به این مسیرها شامل حرف درایو نباشد.

رفع مشکل کندی درایو در Windows Azure با تعریف کش:
کد فوق برای راه اندازی درایو مورد نظر ما کافی است. اما هنوز دارای یک مشکل اساسی و مهم است و آن اینست که بسیار کند عمل خواهد کرد.
با فراخوانی متود CloudDrive.InitializeCache این متود به طور اتوماتیک برای تمام درایوهای mount شده یک کش محلی فراهم میکند و در نتیجه network I/O کمتری صورت خواهد گرفت. توجه داشته باشید که در صورت استفاده از این متود بایستی کش را برای Worker Role تعریف کنید. در صورت عدم استفاده از این متود کارائی پایگاه داده شما به شدت افت میکند. کد زیر را قبل از تعریف هر نوع درایوی قرار دهید.
LocalResource localCache = RoleEnvironment.GetLocalResource("RavenCache");
CloudDrive.InitializeCache(localCache.RootPath, localCache.MaximumSizeInMegabytes);
در کد فوق RavenCache نام یک Local Storage است که شما در تنظیمات Worker Role تعریف میکنید.(نام آن اختیاری است.) برای تعریف Local Storage بایستی در قسمت تنظیمات Worker Role رفته و آنگاه زبانه Local Storage رفته و سپس یک Local Storage را به مانند تصویر زیر اضافه کنید. نام که میتواند هر نامی باشد. اندازه را به اندازه مجموع درایوهایی که میخواهید در Worker Role تعریف کنید قرار دهید(در مثال برنامه ما در اینجا مقدار 1024) و گزینه Clean on role recycle را آنتیک کنید.


حال که درایو مورد نیاز ما آماده است قدم دیگر این است که پورتی را که RavenDB میخواهد در آن فعال شود را تعریف کنیم. برای اینکار بایستی در  قسمت تنظیمات Worker Role در زبانه Endpoints رفته و یک endpoint جدید به آن مطابق تصویر زیر ارائه کنیم.

حال که پورت هم تنظیم شده است میتوانیم RavenDB را در Worker Role راه بیاندازیم:

var config = new RavenConfiguration
{
    DataDirectory = driveLetter,
    AnonymousUserAccessMode = AnonymousUserAccessMode.All,
    HttpCompression = true,
    DefaultStorageTypeName = "munin",
    Port = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["Raven"].IPEndpoint.Port,
    PluginsDirectory = "plugins"
};

try
{
    documentDatabase = new DocumentDatabase(config);
    documentDatabase.SpinBackgroundWorkers();
    httpServer = new HttpServer(config, documentDatabase);
    try
    {
        httpServer.StartListening();
    }
    catch (Exception ex)
    {
        Trace.WriteLine("StartRaven Error: " + ex.ToString(), "Error");

        if (httpServer != null)
        {
            httpServer.Dispose();
            httpServer = null;
        }
    }
}
catch (Exception ex)
{
    Trace.WriteLine("StartRaven Error: " + ex.ToString(), "Error");

    if (documentDatabase != null)
    {
        documentDatabase.Dispose();
        documentDatabase = null;
    }
}

نظرات مطالب
پیاده سازی UnitOfWork به وسیله MEF
در مورد سوال اول که چرا CategoryMap رو به متد AddFromAssembly پاس دادم.
متد AddFromAssembly نیاز به یک Assembly دارد تا بتونه تمام کلاس هایی رو که از کلاس EntityTypeConfiguration ارث برده اند رو پیدا کنه و اونها رو به صورت خودکار به ModelBuilder اضافه کنه. به همین دلیل من Assembly کلاس CategoryMap رو به اون پاس دادم. دقت کنید که اگر من n تا کلاس Map دیگه هم توی این ClassLibrary داشتم باز توسط همین دستور این کار به صورت خودکار انجام می‌شد. (پیشنهاد می‌کنم تست کنید)
نکته: این متد برگرفته شده از متد AddFromAssembly در NHibernate Session Configuration   است.
البته بهتر است که یک کلاس پایه برای این کار بسازید و اون کلاس رو به AddFromAssembly پاس بدید.

در مورد سوال دوست عزیز مبنی بر اینکه متد Save رو در خود توابع Repository قرار دادم.
اگر به کد‌های نوشته شده دقت کنید من اصلا مفهومی به نام Repository رو پیاده سازی نکردم. به این دلیل که خود DbContext ترکیبی از Repository Pattern , UnitOfWork است. متد SaveChange صدا زده شده همان متد SaveChange در DbContext است.
فرض کنید من یک کلاس Business دیگر به صورت زیر داشتم.

public class MyBusiness : BusinessBase<Entity.MyEntity>
    {
        [ImportingConstructor]
        public MyBusiness  ( [Import( typeof( IUnitOfWork ) )] IUnitOfWork unitOfWork )
            : base( unitOfWork )
        {
        }
        public void Add(Entity.MyEntity entity)
        {
            UnitOfWork.Set<MyEntity>().Add(entity);
        }
    }
حالا کد کلاس Business Category به صورت زیر تغییر می‌کنه.
public class Category : BusinessBase<Entity.Category>
    {      
        [ImportingConstructor]
        public Category( [Import( typeof( IUnitOfWork ) )] IUnitOfWork unitOfWork )
            : base( unitOfWork )
        {
        }

        public override void Add( Entity.Category entity )
        {
            new MyBusiness().Add( new Entity.MyEntity() );
            UnitOfWork.Set<Entity.Category>().Add( entity );
            UnitOfWork.SaveChanges();
        }
    }
همان طور که می‌بینید فقط یک بار متد SaveChange فراخوانی شده است. Virtual کردن متد BusinessBase دقیقا به همین دلیل است.

مطالب
Asp.Net Identity #2
پیشتر در اینجا در مورد تاریخچه‌ی سیستم Identity مطالبی را عنوان کردیم. در این مقاله می‌خواهیم نحوه‌ی برپایی سیستم Identity را بحث کنیم.
ASP.NET Identity مانند ASP.NET Membership به اسکیمای SQL Server وابسته نیست؛ اما Relational Storage همچنان واحد ذخیره سازی پیش فرض و آسانی می‌باشد. بدین جهت که تقریبا بین همه‌ی توسعه دهندگان جا افتاده است. ما در این نوشتار از LocalDB جهت ذخیره سازی جداول استفاده می‌کنیم. ذکر این نکته ضروری است که سیستم Identity از Entity Framework Code First جهت ساخت اسکیما استفاده می‌کند.
پیش از هر چیز، ابتدا یک پروژه‌ی وب را ایجاد کنید؛ مانند شکل زیر:

در مرحله‌ی بعد سه پکیج زیر را باید نصب کنیم :

- Microsoft.AspNet.Identity.EntityFramework

-   Microsoft.AspNet.Identity.OWIN

-  Microsoft.Owin.Host.SystemWeb

بعد از اینکه پکیج‌های بالا را نصب کردیم، باید فایل Web.config را بروز کنیم. اولین مورد، تعریف یک Connection String می‌باشد:
<connectionStrings>
 <add name="IdentityDb" 
      providerName="System.Data.SqlClient"
      connectionString="Data Source=(localdb)\v11.0;Initial Catalog=IdentityDb;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;MultipleActiveResultSets=True"/>
 </connectionStrings>
بعد از آن، تعریف یک کلید در قسمت AppSettings تحت عنوان Owin:AppStartup است:
<appSettings>
 <add key="webpages:Version" value="3.0.0.0" />
 <add key="webpages:Enabled" value="false" />
 <add key="ClientValidationEnabled" value="true" />
 <add key="UnobtrusiveJavaScriptEnabled" value="true" />
 <add key="owin:AppStartup" value="Users.IdentityConfig" />
 </appSettings>
Owin مدل شروع برنامه (Application Startup Model) خودش را تعریف می‌کند که از کلاس کلی برنامه (منظور Global.asax) جداست. AppSetting تعریف شده با نام owin:Startup شناخته می‌شود و مشخص کننده کلاسی است که Owin وهله سازی خواهد کرد. وقتی برنامه شروع به کار می‌کند، تنظیمات خودش را از این کلاس دریافت خواهد کرد (در این نوشتار کلاس IdentityConfig می‌باشد).

ساخت کلاس User
مرحله‌ی بعد ساخت کلاس User می‌باشد. این کلاس بیانگر یک کاربر می‌باشد. کلاس User باید از کلاس IdentityUser ارث بری کند که این کلاس در فضای نام Microsoft.AspNet.Identity.EntityFramework قرار دارد. کلاس IdentityUser فراهم کننده‌ی یک کاربر عمومی و ابتدایی است. اگر بخواهیم اطلاعات اضافی مربوط به کاربر را ذخیره کنیم باید آنها در کلاسی که از کلاس IdentityUser ارث بری می‌کند قرار دهیم. کلاس ما در اینجا AppUser نام دارد.
using System;
using Microsoft.AspNet.Identity.EntityFramework;
namespace Users.Models 
{
   public class AppUser : IdentityUser 
  {
      // پروپرتی‌های اضافی در اینجا
  }
}

ساخت کلاس Database Context برنامه
مرحله‌ی بعد ساخت کلاس DbContext برنامه می‌باشد که بر روی کلاس AppUser عمل میکند. کلاس Context برنامه که ما در اینجا آن را AppIdentityDbContext تعریف کرده‌ایم، از کلاس <IdentityDbContext<T ارث بری می‌کند که T همان کلاس User می‌باشد (در مثال ما AppUser) .
using System.Data.Entity;
using Microsoft.AspNet.Identity.EntityFramework;
using Users.Models;
namespace Users.Infrastructure {
 public class AppIdentityDbContext : IdentityDbContext<AppUser> 
{
   public AppIdentityDbContext() 
              : base("IdentityDb") { }

  static AppIdentityDbContext() 
 {
    Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit());
  }
 public static AppIdentityDbContext Create() {
 return new AppIdentityDbContext();
 }
 }
public class IdentityDbInit
 : DropCreateDatabaseIfModelChanges<AppIdentityDbContext> {
 protected override void Seed(AppIdentityDbContext context) {
 PerformInitialSetup(context);
 base.Seed(context);
 }
 public void PerformInitialSetup(AppIdentityDbContext context) {
 // initial configuration will go here
 }
 }
}

ساخت کلاس User Manager
یکی از مهمترین کلاسهای Identity کلاس User Manager (مدیر کاربر) می‌باشد که نمونه‌هایی از کلاس User را مدیریت می‌کند. کلاسی را که تعریف می‌کنیم، باید از کلاس <UserManager<T ارث بری کند که T همان کلاس User می‌باشد. کلاس UserManager خاص EF نمی‌باشد و ویژگی‌های عمومی بیشتری برای ساخت و انجام عملیات بر روی داده‌های کاربر را فراهم می‌نماید.

کلاس UserManager حاوی متدهای بالا است. اگر دقت کنید، می‌بینید که تمامی متدهای بالا به کلمه‌ی Async ختم می‌شوند. زیرا Asp.Net Identity تقریبا کل ویژگیهای برنامه نویسی Async را پیاده سازی کرده است و این بدین معنی است که عملیات میتوانند به صورت غیر همزمان اجرا شده و دیگر فعالیت‌ها را بلوکه نکنند.

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Users.Models;

namespace Users.Infrastructure 
{
 public class AppUserManager : UserManager<AppUser> 
{

 public AppUserManager(IUserStore<AppUser> store)
 : base(store) {
 }
 public static AppUserManager Create( IdentityFactoryOptions<AppUserManager> options, IOwinContext context) 
{
 AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
 AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db));
 return manager;
 }
 }
}

زمانی که Identity نیاز به وهله‌ای از کلاس AppUserManager داشته باشد، متد استاتیک Create را صدا خواهد زد. این عمل زمانی اتفاق می‌افتد که عملیاتی بر روی داده‌های کاربر انجام گیرد. برای ساخت وهله‌ای از کلاس AppUserManager نیاز به کلاس <UserStor<AppUser دارد. کلاس <UserStore<T یک پیاده سازی از رابط <IUserStore<T  توسط  EF میباشد که وظیفه‌ی آن فراهم کننده‌ی پیاده سازی Storage-Specific متدهای تعریف شده در کلاس User Manager (که در اینجا AppUserManager ) می‌باشد. برای ساخت <UserStore<T نیاز به وهله‌ای از کلاس AppIdentityDbContext می‌باشد که از طریق Owin به صورت زیر قابل دریافت است:

AppIdentityDbContext db = context.Get<AppIdentityDbContext>();

یک پیاده سازی از رابط IOwinContext، به عنوان پارامتر به متد Create پاس داده می‌شود. در این پیاده سازی، یک تابع جنریک به نام Get تعریف شده که اقدام به برگشت وهله ای از اشیای ثبت شده‌ی در کلاس شروع Owin می‌نماید.


ساخت کلاس شروع Owin

اگر خاطرتان باشد یک کلید در قسمت AppSettings فایل Web.config به صورت زیر تعریف کردیم:

<add key="owin:AppStartup" value="Users.IdentityConfig" />

قسمت Value کلید بالا از دو قسمت تشکیل شده است: Users بیانگر فضای نام برنامه‌ی شماست و IdentityConfig بیانگر کلاس شروع می‌باشد. (البته شما می‌توانید هر نام دلخواهی را برای کلاس شروع بگذارید. فقط دقت داشته باشید که نام کلاس شروع و مقدار، با کلیدی که تعریف می‌کنید یکی باشد)

Owin مستقل از ASP.NET اعلام شده است و قراردادهای خاص خودش را دارد. یکی از این قراردادها تعریف یک کلاس و وهله سازی آن به منظور بارگذاری و پیکربندی میان افزار و انجام دیگر کارهای پیکربندی که نیاز است، می‌باشد. به طور پیش فرض این کلاس Start نام دارد و در پوشه‌ی App_Start تعریف می‌شود. این کلاس حاوی یک متد به نام Configuration می‌باشد که بوسیله زیرساخت Owin فراخوانی می‌شود و یک پیاده سازی از رابط Owin.IAppBuilder به عنوان آرگومان به آن پاس داده می‌شود که کار پشتیبانی از Setup میان افزار مورد نیاز برنامه را برعهده دارد.

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
using Users.Infrastructure;
namespace Users 
{
 public class IdentityConfig 
{
 public void Configuration(IAppBuilder app) 
{
 app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create);
 app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
 app.UseCookieAuthentication(new CookieAuthenticationOptions {
 AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
 LoginPath = new PathString("/Account/Login"),
 });
 }
 }
}

رابط IAppBuilder بوسیله تعدادی متد الحاقی که در کلاسهایی که در فضای نام Owin تعریف شده‌اند، تکمیل شده است. متد CreatePerOwinContext کار ساخت وهله‌ای از کلاس AppUserManager و کلاس AppIdentityDbContext را برای هر درخواست بر عهده دارد. این مورد تضمین می‌کند که هر درخواست، به یک داده‌ی تمیز از Asp.Net Identity دسترسی خواهد داشت و نگران اعمال همزمانی و یا کش ضعیف داده نخواهد بود. متد UseCookieAuthentication به Asp.Net Identity می‌گوید که چگونه از کوکی‌ها برای تصدیق هویت کاربران استفاده کند که Optionهای آن از طریق کلاس CookieAuthenticationOptions مشخص می‌شود. مهمترین قسمت در اینجا پروپرتی LoginPath می‌باشد و مشخص می‌کند که کلاینت‌های تصدیق هویت نشده، هنگام دسترسی به یک منبع محافظت شده، به کدام URL هدایت شوند که توسط یک رشته به متد PathString پاس داده می‌شود.

خوب دوستان برپایی سیستم Identity به پایان رسید. انشالله در قسمت بعدی به چگونگی استفاده‌ی از این سیستم خواهیم پرداخت.

پاسخ به بازخورد‌های پروژه‌ها
استفاده از database first
مطالب
Func یا Expression Func در EF
با بررسی کدهای مختلف Entity framework گاهی از اوقات در امضای توابع کمکی نوشته شده، <>Func مشاهده می‌شود و در بعضی از موارد <<>Expression<Func و ... به نظر استفاده کنندگان دقیقا نمی‌دانند که تفاوت این دو در چیست و کدامیک را باید/یا بهتر است بکار برد.

ابتدا مثال کامل ذیل را در نظر بگیرید:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
using System.Linq.Expressions;

namespace Sample
{
    public abstract class BaseEntity
    {
        public int Id { set; get; }
    }

    public class Receipt : BaseEntity
    {
        public int TotalPrice { set; get; }
    }

    public class MyContext : DbContext
    {
        public DbSet<Receipt> Receipts { get; set; }
    }

    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            if (!context.Receipts.Any())
            {
                for (int i = 0; i < 20; i++)
                {
                    context.Receipts.Add(new Receipt { TotalPrice = i });
                }
            }
            base.Seed(context);
        }
    }

    public static class EFUtils
    {
        public static IList<T> LoadEntities<T>(this DbContext ctx, Expression<Func<T, bool>> predicate) where T : class
        {
            return ctx.Set<T>().Where(predicate).ToList();
        }

        public static IList<T> LoadData<T>(this DbContext ctx, Func<T, bool> predicate) where T : class
        {
            return ctx.Set<T>().Where(predicate).ToList();
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            startDB();

            using (var context = new MyContext())
            {
                var list1 = context.LoadEntities<Receipt>(x => x.TotalPrice == 10);
                var list2 = context.LoadData<Receipt>(x => x.TotalPrice == 20);
            }
        }

        private static void startDB()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
            // Forces initialization of database on model changes.
            using (var context = new MyContext())
            {
                context.Database.Initialize(force: true);
            }
        }
    }
}
در این مثال ابتدا کلاس Receipt تعریف شده و سپس توسط کلاس MyContext در معرض دید EF قرار گرفته است. در ادامه توسط کلاس Configuration نحوه آغاز بانک اطلاعاتی مشخص گردیده است؛ به همراه ثبت تعدادی رکورد نمونه.
نکته اصلی مورد بحث، کلاس کمکی EFUtils است که در آن دو متد الحاقی LoadEntities و LoadData تعریف شده‌اند. در متد LoadEntities، امضای متد شامل Expression Func است و در متد LoadData فقط Func ذکر شده است.
در ادامه اگر برنامه را توسط فراخوانی متد RunTests اجرا کنیم، به نظر شما خروجی SQL حاصل از list1 و list2 چیست؟
احتمالا شاید عنوان کنید که هر دو یک خروجی SQL دارند (با توجه به اینکه بدنه متدهای LoadEntities و LoadData دقیقا/یا به نظر یکی هستند) اما یکی از پارامتر 10 استفاده می‌کند و دیگری از پارامتر 20. تفاوت دیگری ندارند.
اما ... اینطور نیست!
خروجی SQL متد LoadEntities در متد RunTests به صورت زیر است:
 SELECT
[Extent1].[Id] AS [Id],
[Extent1].[TotalPrice] AS [TotalPrice]
FROM [dbo].[Receipts] AS [Extent1]
WHERE 10 = [Extent1].[TotalPrice]
و ... خروجی متد LoadData به نحو زیر:
 SELECT
[Extent1].[Id] AS [Id],
[Extent1].[TotalPrice] AS [TotalPrice]
FROM [dbo].[Receipts] AS [Extent1]
بله. در لیست دوم هیچ فیلتری انجام نشده (در حالت استفاده از Func خالی) و کل اطلاعات موجود در جدول Receipts، بازگشت داده شده است.
چرا؟
Func اشاره‌گری است به یک متد و Expression Func بیانگر ساختار درختی عبارت lambda نوشته شده است. این ساختار درختی صرفا بیان می‌کند که عبارت lambda منتسب، چه کاری را قرار است یا می‌تواند انجام دهد؛ بجای انجام واقعی آن.
 public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
اگر از Expression Func استفاده شود، از متد Where ایی استفاده خواهد شد که خروجی IQueryable دارد. اگر از Func استفاده شود، از overload دیگری که خروجی و ورودی  IEnumerable دارد به صورت خودکار استفاده می‌گردد.
بنابراین هرچند بدنه دو متد LoadEntities و LoadData به ظاهر یکی هستند، اما بر اساس نوع ورودی Where ایی که دریافت می‌کنند، اگر Expression Func باشد، EF فرصت آنالیز و ترجمه عبارت ورودی را خواهد یافت اما اگر Func باشد، ابتدا باید کل اطلاعات را به صورت یک لیست IEnumerable دریافت و سپس سمت کلاینت، خروجی نهایی را فیلتر کند.
اگر برنامه را اجرا کنید نهایتا هر دو لیست یک و دو، بر اساس شرط عنوان شده عمل خواهند کرد و فیلتر خواهند شد. اما در حالت اول این فیلتر شدن سمت بانک اطلاعاتی است و در حالت دوم کل اطلاعات بارگذاری شده و سپس سمت کاربر فیلتر می‌شود (که کارآیی پایینی دارد).


نتیجه گیری
به امضای متد Where ایی که در حال استفاده است دقت کنید. همینطور در مورد Sum ، Count و یا موارد مشابه دیگری که predicate قبول می‌کنند.
اشتراک‌ها
دوره 9 ساعته Clean Architecture + CQRS

Mini Course #1 Clean Architecture + CQRS - YouTube

00:00:00 - Intro
00:01:10 - Why do we record this course?
00:04:39 - Layered architecture: Why I don't like it anymore?
00:27:27 - Clean architecture
00:38:16 - Course prerequisites (still can't pronounce it correctly)
00:42:30 - What do we build?
00:45:18 - *Domain Layer*
00:49:57 - Entity
01:00:20 - Primitive obsession code smell
01:03:20 - Value Object
01:10:54 - Custom exceptions
01:33:25 - Domain model validation
01:38:55 - Aggregate
01:50:50 - Domain event
02:14:30 - Factory
02:28:50 - Policy
02:43:50 - Repository
02:48:55 - Domain Layer: Summary
02:56:45 - CQS: Command Query Separation
03:07:46 - CQRS: Command Query Responsibility Segregation
03:29:48 - *Application Layer*
03:30:25 - Command/Command Handler/Command Dispatcher definitions
03:40:45 - Automatic command handlers registration
03:44:12 - Application & Domain registration
03:50:52 - Command
03:55:31 - Command Handler
04:01:11 - Where to put methods related to reading?
04:07:00 - Read Service
04:14:14 - Weather Service
04:28:25 - Overview of the other commands & command handlers
04:34:35 - Query/Query Handler/Query Dispatcher definitions
04:44:37 - Automatic query handlers registration (BONUS: example of copy/paste pattern)
04:52:50 - How to tackle reading on Query side?
05:06:30 - *Infrastructure Layer*
05:07:56 - Configuration of Entity Framework Core
05:15:15 - Read Models
05:19:05 - ReadDbContext & WriteDbContext
05:24:13 - EF Entity Configuration
05:42:37 - DbContexts registration
05:55:51 - Query Handlers
06:12:18 - Repository implementation on top of EF Core
06:16:45 - Read Service implementation on top of EF Core
06:19:00 - Dummy Weather Service implementation
06:23:20 - EF Migration
06:29:50 - Applying EF migrations automatically
06:39:38 - *Presentation Layer*
06:41:35 - Controller
06:50:43 - How to return ID of resource in CQRS approach?
07:00:02 - Testing API
07:09:29 - *Cross Cutting Concerns*
07:09:30 - Error Handling
07:20:49 - Logging
07:34:57 - *Unit Testing*
07:42:34 - Unit Test on Domain Layer
08:09:45 - Unit Test on Application Layer
08:34:00 - Summary 

دوره 9 ساعته Clean Architecture + CQRS