شروع به کار با EF Core 1.0 - قسمت 14 - لایه بندی و تزریق وابستگی‌ها
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: هشت دقیقه

در مورد «امکانات توکار تزریق وابستگی‌ها در ASP.NET Core» پیشتر بحث شد. همچنین «نحوه‌ی تعریف Context، تزریق سرویس‌های EF Core و تنظیمات رشته‌ی اتصالی آن» را نیز بررسی کردیم. به علاوه مباحث «به روز رسانی ساختار بانک اطلاعاتی» و «انتقال مهاجرت‌ها به یک اسمبلی دیگر» نیز مرور شدند. بنابراین در این قسمت برای لایه بندی برنامه‌های EF Core، صرفا یک مثال را مرور خواهیم کرد که این قسمت‌ها را در کنار هم قرار می‌دهد و عملا نکته‌ی اضافه‌تری را ندارد.


تزریق مستقیم کلاس Context برنامه، تزریق وابستگی‌ها نام ندارد!

در همان قسمت اول سری شروع به کار با EF Core 1.0، مشاهده کردیم که پس از انجام تنظیمات اولیه‌ی آن در کلاس آغازین برنامه:
public void ConfigureServices(IServiceCollection services)
{    
   services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
Context برنامه را در تمام قسمت‌های آن می‌توان تزریق کرد و کار می‌کند:
    public class TestDBController : Controller
    {
        private readonly ApplicationDbContext _ctx;

        public TestDBController(ApplicationDbContext ctx)
        {
            _ctx = ctx;
        }

        public IActionResult Index()
        {
            var name = _ctx.Persons.First().FirstName;
            return Json(new { firstName = name });
        }
    }
این روشی است که در بسیاری از مثال‌های گوشه و کنار اینترنت قابل مشاهده‌است. یا کلاس Context را مستقیما در سازنده‌ی کنترلرها تزریق می‌کنند و از آن استفاده می‌کنند (روش فوق) و یا لایه‌ی سرویسی را ایجاد کرده و مجددا همین تزریق مستقیم را در آنجا انجام می‌دهند و سپس اینترفیس‌های آن سرویس را در کنترلرهای برنامه تزریق کرده و استفاده می‌کنند. به این نوع تزریق وابستگی‌ها، تزریق concrete types و یا concrete classes می‌گویند.
مشکلاتی را که تزریق مستقیم کلاس‌ها و نوع‌ها به همراه دارند به شرح زیر است:
- اگر نام این کلاس تغییر کند، باید این نام، در تمام کلاس‌هایی که به صورت مستقیم از آن استفاده می‌کنند نیز تغییر داده شود.
- اگر سازنده‌ای به آن اضافه شد و یا امضای سازنده‌ی موجود آن، تغییر کرد، باید نحوه‌ی وهله سازی این کلاس را در تمام کلاس‌های وابسته نیز اصلاح کرد.
- یکی از مهم‌ترین دلایل استفاده‌ی از تزریق وابستگی‌ها، بالابردن قابلیت تست پذیری برنامه است. زمانیکه از اینترفیس‌ها استفاده می‌شود، می‌توان در مورد نحوه‌ی تقلید (mocking) رفتار کلاسی خاص، مستقلا تصمیم گیری کرد. اما هنگامیکه یک کلاس را به همان شکل اولیه‌ی آن تزریق می‌کنیم، به این معنا است که همواره دقیقا همین پیاده سازی خاص مدنظر ما است و این مساله، نوشتن آزمون‌های واحد را با مشکل کردن mocking آن‌ها، گاهی از اوقات غیرممکن می‌کند. هرچند تعدادی از فریم ورک‌های پیشرفته‌ی mocking گاهی از اوقات امکان تقلید رفتار کلاس‌ها و نوع‌ها را نیز فراهم می‌کنند، اما با این شرط که تمام خواص و متدهای آن‌ها را virtual تعریف کنید؛ تا بتوانند متدهای اصلی را با نمونه‌های مدنظر شما بازنویسی (override) کنند.

به همین جهت در ادامه، به همان طراحی EF Code First #12 با نوشتن اینترفیس IUnitOfWork خواهیم رسید. یعنی کلاس Context برنامه را با این اینترفیس نشانه گذاری می‌کنیم (در انتهای لیست تمام اینترفیس‌های دیگری که ممکن است در اینجا ذکر شده باشند):
 public class ApplicationDbContext :  IUnitOfWork
و سپس اینترفیس IUnitOfWork را به لایه سرویس برنامه و یا هر لایه‌ی دیگری که به Context آن نیاز دارد، تزریق خواهیم کرد.


طراحی اینترفیس IUnitOfWork

برای اینکه دیگر با کلاس ApplicationDbContext مستقیما کار نکرده و وابستگی به آن‌را در تمام قسمت‌های برنامه پخش نکنیم، اینترفیسی را ایجاد می‌کنیم که تنها قسمت‌های مشخصی از DbContext را عمومی کند:
public interface IUnitOfWork : IDisposable
{
    DbSet<TEntity> Set<TEntity>() where TEntity : class;
 
    void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
    void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
 
    EntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
    void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class;
 
    void ExecuteSqlCommand(string query);
    void ExecuteSqlCommand(string query, params object[] parameters);
 
    int SaveAllChanges();
    Task<int> SaveAllChangesAsync();
}
توضیحات
- در این طراحی شاید عنوان کنید که DbSet، اینترفیس نیست. تعریف DbSet در EF Core به صورت زیر است و در حقیقت همانند اینترفیس‌ها یک abstraction به حساب می‌آید:
 public abstract class DbSet<TEntity> : IQueryable<TEntity>, IEnumerable<TEntity>, IEnumerable, IQueryable, IAsyncEnumerableAccessor<TEntity>, IInfrastructure<IServiceProvider> where TEntity : class
علت اینکه در پروژه‌های بزرگی مانند EF، تمایل زیادی به استفاده‌ی از کلاس‌های abstract وجود دارد (بجای اینترفیس‌ها) این است که اگر این نوع پرکاربرد را به صورت اینترفیس تعریف کنند، با تغییر متدی در آن، باید تمام کدهای خود را به اجبار بازنویسی کنید. اما در حالت استفاده‌ی از کلاس‌های abstract، می‌توان پیاده سازی پیش فرضی را برای متدهایی که قرار است در آینده اضافه شوند، ارائه داد (یکی از تفاوت‌های مهم آن‌ها با اینترفیس‌ها)، بدون اینکه تمام استفاده کنندگان از این کتابخانه، با ارتقاء نگارش EF خود، دیگر نتوانند برنامه‌ی خود را کامپایل کنند.
- این اینترفیس به عمد به صورت IDisposable تعریف شده‌است. این مساله به IoC Containers کمک خواهد کرد که بتوانند پاکسازی خودکار نوع‌های IDisposable را در انتهای هر درخواست انجام دهند و برنامه مشکلی نشتی حافظه را پیدا نکند.
- اصل کار این اینترفیس، تعریف DbSet و متدهای SaveChanges است. سایر متدهایی را که مشاهده می‌کنید، صرفا جهت بیان اینکه چگونه می‌توان قابلیتی از DbContext را بدون عمومی کردن خود کلاس DbContext، در کلاس‌هایی که از اینترفیس IUnitOfWork استفاده می‌کنند، میسر کرد.

پس از اینکه این اینترفیس تعریف شد، اعمال آن به کلاس Context برنامه به صورت ذیل خواهد بود:
public class ApplicationDbContext : DbContext, IUnitOfWork
{
    private readonly IConfigurationRoot _configuration;
 
    public ApplicationDbContext(IConfigurationRoot configuration)
    {
        _configuration = configuration;
    }
 
    //public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    //{
    //}
 
    public virtual DbSet<Blog> Blog { get; set; }

 
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(
            _configuration["ConnectionStrings:ApplicationDbContextConnection"]
            , serverDbContextOptionsBuilder =>
             {
                 var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                 serverDbContextOptionsBuilder.CommandTimeout(minutes);
             }
            );
    }
 
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
 
        base.OnModelCreating(modelBuilder);
    }
 
    public void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
    {
        base.Set<TEntity>().AddRange(entities);
    }
 
    public void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
    {
        base.Set<TEntity>().RemoveRange(entities);
    }
 
    public void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class
    {
        base.Entry(entity).State = EntityState.Modified; // Or use ---> this.Update(entity);
    }
 
    public void ExecuteSqlCommand(string query)
    {
        base.Database.ExecuteSqlCommand(query);
    }
 
    public void ExecuteSqlCommand(string query, params object[] parameters)
    {
        base.Database.ExecuteSqlCommand(query, parameters);
    }
 
    public int SaveAllChanges()
    {
        return base.SaveChanges();
    }
 
    public Task<int> SaveAllChangesAsync()
    {
        return base.SaveChangesAsync();
    }
}
در ابتدا اینترفیس IUnitOfWork به کلاس Context برنامه اعمال شده‌است:
 public class ApplicationDbContext : DbContext, IUnitOfWork
و سپس متدهای آن منهای پیاده سازی اینترفیس IDisposable اعمالی به IUnitOfWork :
 public interface IUnitOfWork : IDisposable
پیاده سازی شده‌اند. علت اینجا است که چون کلاس پایه DbContext از همین اینترفیس مشتق می‌شود، دیگر نیاز به پیاده سازی اینترفیس IDisposable نیست.
در مورد تزریق IConfigurationRoot به سازنده‌ی کلاس Context برنامه، در مطلب اول این سری در قسمت «یک نکته: امکان تزریق IConfigurationRoot به کلاس Context برنامه» پیشتر بحث شده‌است.


ثبت تنظیمات تزریق وابستگی‌های IUnitOfWork

پس از تعریف و پیاده سازی اینترفیس IUnitOfWork، اکنون نوبت به معرفی آن به سیستم تزریق وابستگی‌های ASP.NET Core است:
public void ConfigureServices(IServiceCollection services)
{
  services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
  services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
  services.AddScoped<IUnitOfWork, ApplicationDbContext>();
در اینجا هم ApplicationDbContext و هم IUnitOfWork با طول عمر Scoped به تنظیمات IoC Container مربوط به ASP.NET Core اضافه شده‌اند. به این ترتیب هر زمانیکه وهله‌ای از نوع IUnitOfWork درخواست شود، تنها یک وهله از ApplicationDbContext در طول درخواست وب جاری، در اختیار مصرف کننده قرار می‌گیرد و همچنین مدیریت Dispose این وهله‌ها نیز خودکار است. به همین جهت اینترفیس IUnitOfWork را با IDisposable علامتگذاری کردیم.


استفاده از IUnitOfWork در لایه سرویس‌های برنامه

اکنون لایه سرویس برنامه و فایل project.json آن چنین شکلی را پیدا می‌کند:
{
  "version": "1.0.0-*",
 
    "dependencies": {
        "Core1RtmEmptyTest.DataLayer": "1.0.0-*",
        "Core1RtmEmptyTest.Entities": "1.0.0-*",
        "Core1RtmEmptyTest.ViewModels": "1.0.0-*",
        "Microsoft.Extensions.Configuration.Abstractions": "1.0.0",
        "Microsoft.Extensions.Options": "1.0.0",
        "NETStandard.Library": "1.6.0"
    },
 
  "frameworks": {
    "netstandard1.6": {
      "imports": "dnxcore50"
    }
  }
}
در اینجا ارجاعاتی را به اسمبلی‌های موجودیت‌ها و DataLayer برنامه مشاهده می‌کنید. در مورد این اسمبلی‌ها در مطلب «شروع به کار با EF Core 1.0 - قسمت 3 - انتقال مهاجرت‌ها به یک اسمبلی دیگر» پیشتر بحث شد.
پس از تنظیم وابستگی‌های این اسمبلی، اکنون یک کلاس نمونه از لایه سرویس برنامه، به شکل زیر خواهد بود: 
namespace Core1RtmEmptyTest.Services
{
    public interface IBlogService
    {
        IReadOnlyList<Blog> GetPagedBlogsAsNoTracking(int pageNumber, int recordsPerPage);
    }
 
    public class BlogService : IBlogService
    {
        private readonly IUnitOfWork _uow;
        private readonly DbSet<Blog> _blogs;
 
        public BlogService(IUnitOfWork uow)
        {
            _uow = uow;
            _blogs = _uow.Set<Blog>();
        }
 
        public IReadOnlyList<Blog> GetPagedBlogsAsNoTracking(int pageNumber, int recordsPerPage)
        {
            var skipRecords = pageNumber * recordsPerPage;
            return _blogs
                        .AsNoTracking()
                        .Skip(skipRecords)
                        .Take(recordsPerPage)
                        .ToList();
        }
    }
}
در اینجا اکنون می‌توان IUnitOfWork را به سازنده‌ی کلاس سرویس Blog تنظیم کرد و سپس به نحو متداولی از امکانات EF Core استفاده نمود.


استفاده از امکانات لایه سرویس برنامه، در دیگر لایه‌های آن

خروجی لایه سرویس، توسط اینترفیس‌هایی مانند IBlogService در قسمت‌های دیگر برنامه قابل استفاده و دسترسی می‌شود.
به همین جهت نیاز است مشخص کنیم، این اینترفیس را کدام کلاس ویژه قرار است پیاده سازی کند. برای این منظور همانند قبل در متد ConfigureServices کلاس آغازین برنامه این تنظیم را اضافه خواهیم کرد:
public void ConfigureServices(IServiceCollection services)
{
  services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
  services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
  services.AddScoped<IUnitOfWork, ApplicationDbContext>();
  services.AddScoped<IBlogService, BlogService>();
پس از آن، امضای سازنده‌ی کلاس کنترلری که در ابتدای بحث عنوان شد، به شکل زیر تغییر پیدا می‌کند:
public class TestDBController : Controller
{
    private readonly IBlogService _blogService;
    private readonly IUnitOfWork _uow;
 
    public TestDBController(IBlogService blogService, IUnitOfWork uow)
    {
        _blogService = blogService;
        _uow = uow;
    }
در اینجا کنترلر برنامه تنها با اینترفیس‌های IUnitOfWork و IBlogService کار می‌کند و دیگر ارجاع مستقیمی را به کلاس ApplicationDbContext ندارد.
  • #
    ‫۷ سال و ۵ ماه قبل، یکشنبه ۲۷ فروردین ۱۳۹۶، ساعت ۰۲:۱۷
    در زمان CRUD مشکل Update وجود دارد یعنی در زمان فراخوانی 
    _unitofwork.MarkAsChanged(entity);
    در کنترلر MVC خطایی با این عنوان اتفاق می‌افتد 
    An unhandled exception has occurred while executing the request
    System.InvalidOperationException: The instance of entity type 'Country' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.

  • #
    ‫۷ سال و ۵ ماه قبل، یکشنبه ۲۷ فروردین ۱۳۹۶، ساعت ۰۲:۴۷
    آیا با تغییر این قسمت 
    services.AddScoped<IUnitOfWork, ApplicationDbContext>();
    به AddTransient ایا کارایی برنامه به مشکل میخوره ؟ چون در عملیات Ajax بروی Controller با توجه به وهله ای که انجام میشه برای Update  در حالت AddScope با خطای زیر مواجه میشم 
    Cannot create a ModificationFunction for an entity in state 'Unchanged'.

    • #
      ‫۷ سال و ۵ ماه قبل، یکشنبه ۲۷ فروردین ۱۳۹۶، ساعت ۰۳:۰۰
      Transient یعنی هر قسمتی که به آن نیاز داشت، یک وهله‌ی جدید ایجاد شود. در کل نیاز است مطلب EF #12 به همراه تمام نظرات آن‌را مطالعه کنید تا با دلیل استفاده‌ی از حالت Scoped یا همان Context Per Request آشنا شوید.
  • #
    ‫۶ سال و ۵ ماه قبل، پنجشنبه ۹ فروردین ۱۳۹۷، ساعت ۱۶:۳۵
    درباره این لایه بندی چند سوال برام پیش اومده  :
    1-  چرا  اینترفیس IUnitOfWork در لایه Service تعریف نشده و چون به نظر میرسه که ما اینجا اصل( Dependency Inversion Principle (DIP) رو رعایت نکرده ایم.
    2- بهتر نبود ِApplicationDbContext  در لایه Infrastructure  تعریف می‌شد؟ 
    • #
      ‫۶ سال و ۵ ماه قبل، پنجشنبه ۹ فروردین ۱۳۹۷، ساعت ۱۷:۲۴
      لایه سرویس، استفاده کننده‌ی از IUnitOfWork است و امکانات آن‌را پس از کپسوله کردن در اختیار سایر لایه‌های برنامه قرار می‌دهد (اینجا است که معکوس کردن وابستگی‌ها رخ می‌دهد). پیشترها به این لایه‌ها DAL (Data Access Layer) و BLL (Business Logic Layer) گفته می‌شد؛ این لایه‌ها یکی نیستند و در یک سطح قرار نمی‌گیرند. کار لایه DAL، اتصال و کار مستقیم با بانک اطلاعاتی است. خارج از امکانات ارائه شده‌ی توسط این لایه، لایه دیگری حق تعامل مستقیم با بانک اطلاعاتی را ندارد (بنابراین اگر دیدید که Context را مستقیما داخل یک کنترلر (که در لایه نمایشی یا Presentation Layer قرار دارد) فراخوانی کردند، چنین برنامه‌ای لایه بندی صحیحی ندارد؛ کل کنترلرها و ویووهای مرتبط با آن‌ها در لایه نمایشی قرار می‌گیرند. کار الگوی MVC صرفا نظم بخشی به لایه نمایشی است). منطق‌هایی که از DAL برای ارائه‌ی سرویس‌هایی به قسمت‌های مختلف برنامه پیاده سازی می‌شوند، در BLL قرار می‌گیرند و در نهایت جریان اطلاعات در اینجا به این صورت است:
       Presentation Layer-->BLL-->DAL-->Database
      برای اطلاعات بیشتر نظرات مطلب «EF Code First #12» را مطالعه کنید.
      • #
        ‫۶ سال و ۵ ماه قبل، پنجشنبه ۹ فروردین ۱۳۹۷، ساعت ۲۱:۱۹
        ممنون بابت پاسختون...
        اگر لایه Service به عنوان ماژول سطح بالا   فرض کنیم  و لایه DataAccsessLayer رو به عنوان   ماژول سطج پایین  فرض کنیم. مگر طبق تعریف قانون DIP  نباید رابط IUnitOfWork رو در لایه Service تعریف کرده و  لایه DAl موظف باشد ویژگی‌های مورد نیاز Service را  پیاده سازی کند و دیگر لازم نیست applicationDbContext رو به صورت public کنیم و میتوانیم اون رو به صورت internal  تعریف کنیم.

        مانند پروژه  eShopOnContainers .


        • #
          ‫۶ سال و ۵ ماه قبل، پنجشنبه ۹ فروردین ۱۳۹۷، ساعت ۲۲:۵۰
          - سطوح بالا و پایین در اینجا بر اساس رابطه‌ی  Presentation Layer-->BLL-->DAL-->Database مشخص می‌شوند (پایین‌ترین سطح بانک اطلاعاتی است و بالاترین سطح، همانی‌است که کاربر با آن تعامل دارد یا لایه‌ی نمایشی). در اینجا DAL بر اساس اینترفیس IUnitOfWork، امکان دسترسی به بانک اطلاعاتی را به صورت کنترل شده، در اختیار BLL (یا همان لایه سرویس در اینجا) قرار می‌دهد (مانند همان IUnitOfWork‌های تزریق شده‌ی در سازنده‌ی کلاس‌های لایه سرویس). سپس BLL هم منطق تجاری تعریف شده‌ی در آن‌را توسط اینترفیس‌هایی در اختیار بالاترین سطح سیستم که لایه نمایش هست (کنترلرها و قسمت MVC)  قرار خواهد داد. پیاده سازی‌های این‌ها هم توسط سیستم تزریق وابستگی‌ها تامین می‌شود و عموما اینترفیس‌های هر لایه هم در همان لایه تعریف می‌شوند و سپس در اختیار لایه‌های بالاتر قرار می‌گیرند. یعنی لایه‌های مختلف از جزئیات پیاده سازی‌های یک‌دیگر مطلع نیستند و فقط با اینترفیس‌ها کار می‌کنند که سهولت نوشتن آزمون‌های واحد را سبب خواهند شد و همچنین امکان تعویض ساده‌تر پیاده سازی‌ها در صورت نیاز، بدون نیاز به تغییرات جزئیات کدنویسی لایه‌ای خاص (اصل باز بودن برای توسعه و بسته بودن برای تغییر).
          - همچنین از آنجائیکه ما در یک دنیای محض زندگی نمی‌کنیم، نیاز خواهید داشت از
          IUnitOfWork گاهی از اوقات در لایه نمایشی هم استفاده کنید تا بتوانید یک تراکنش حاصل از چند موجودیت و چند کلاس لایه سرویس را در پایان کار درخواست رسیده (فقط یک تراکنش به ازای کل تعاملات یک درخواست)، به بانک اطلاعاتی اعمال کنید. بنابراین internal تعریف کردن آن در دنیای واقعی میسر نیست. حتی برای تعریف سیم‌کشی‌های تزریق وابستگی‌های اولیه‌ی آن هم نباید internal تعریف شود. همچنین باز هم یک برنامه‌ی واقعی الزاما تمام نیازهای تجاری‌اش در لایه سرویس قرار نمی‌گیرد. مثلا نیاز خواهید داشت با میان‌افزارها، اکشن فیلترها و غیره هم کار کنید که این‌ها محل قرارگیری استاندارد و مشخصی ندارند و هر جائی قابل تعریف هستند.
  • #
    ‫۶ سال و ۴ ماه قبل، پنجشنبه ۱۳ اردیبهشت ۱۳۹۷، ساعت ۰۲:۴۰
    من الان می‌خواهم یک Action در TestDBController  ایجاد کنم و یک دستور sql update مستقیما بزنم به دیتابیس. این دستور رو در اکشن بصورت 
    _un.ExecuteSqlCommand("Update command");
    باید بنویسم یا بهتره اول در Interface و کلاس مربوطه دستور فوق رو پیاده سازی کنم و 
    _blogService.SqlCmd("update command ");
     بنویسم.
  • #
    ‫۶ سال و ۳ ماه قبل، چهارشنبه ۲۳ خرداد ۱۳۹۷، ساعت ۱۷:۴۴
    باسلام؛ مشکلی که من بعد از پیاده سازی این موضوع دارم اینه که قبلا با تزریق مستقیم می‌تونستم به روش زیر یک Transaction ایجاد کنم و به راحتی چند تا SaveChanges رو فراخوانی کنیم ولی بعد از پیاده سازی این روش پیغام زیر رو بهم میده
    IunitofWork The configured execution strategy 'SqlServerRetryingExecution Strategy' does not support user initiated transactions. Use the execution strategy returned by 'DbContext.Database.Create Execution Strategy()' to execute all the operations in the transaction as a retriable unit. 
    • #
      ‫۶ سال و ۳ ماه قبل، چهارشنبه ۲۳ خرداد ۱۳۹۷، ساعت ۱۸:۴۹
      این خطا مرتبط به فعال بودن سعی مجدد درخواست‌های شکست خورده است:
      protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
      {
          optionsBuilder
              .UseSqlServer(
                  @"Server=(localdb)\mssqllocaldb;Database=EFMiscellanous.ConnectionResiliency;Trusted_Connection=True;ConnectRetryCount=0",
                  options => options.EnableRetryOnFailure());
      }
      و اگر این مورد فعال باشد، EF Core از تراکنش‌های سفارشی پشتیبانی نمی‌کند. بنابراین این مورد جزو خواص و تنظیمات DbContext شما است و مستقیما ارتباطی به IUnitOfWork که صرفا بیانگر وهله‌ای از آن است، ندارد.
      • #
        ‫۶ سال و ۳ ماه قبل، چهارشنبه ۲۳ خرداد ۱۳۹۷، ساعت ۲۰:۴۴
        سلام؛ ممنون. من تنظیمات context رو از روی پروژه identity core که در همین سایت ارائه شده کپی برداری کردم که همچین تنظیمی رو ندیدم و آیا برداشتن این تنظیم کار خوبیه یا نه؟ 
        اگر هم نخوام این تنظیم رو بردارم  و از روش لینک کمکی بخوام استفاده کنم  باید جداولی که توی کلاس لازم دارم رو به کلاس به صورت dbset<table> تزریق کنم که اگه بخوام به صورت strategy گفته شده در لینک کمکی استفاده کنم قبول نمیکنه و همچنین باید چند بار از context شی درست کرده که دقیقا نمیدونم چطور باید شی درست کنم. 
        • #
          ‫۶ سال و ۳ ماه قبل، چهارشنبه ۲۳ خرداد ۱۳۹۷، ساعت ۲۲:۵۳
          - در اینجا تنظیم شده‌است.
          - هدف از الگوی واحد کار به همراه تنظیمات تزریق وابستگی‌های آن و تمام این مسایل، به جهت داشتن یک وهله از DbContext در طول عمر یک درخواست هست. یعنی داشتن یک تراکنش کلی در طول درخواست و Commit خودکار آن توسط EF در پایان درخواست (با Dispose خودکار Context). تمام اعمال EF تراکنشی هستند و در بسیاری از موارد نیازی به ایجاد تراکنش‌های اضافی نیست. اگر به بیش از یک تراکنش نیاز دارید، شاید این الگو برای کار شما مناسب نباشد.
          - آیا امکان دسترسی به بیش از یک وهله از Context در طول درخواست جاری زمانیکه از الگوی واحد کار استفاده می‌کنیم وجود دارد؟ بله. یک نمونه‌ی آن در کلاس DbLogger جهت ایجاد یک Context مجزا، برای ثبت وقایع برنامه و عدم تداخل آن با Context جاری درخواست، ایجاد می‌شود. نمونه‌ی دیگر آن جهت دائمی کردن کلیدهای رمزنگاری برنامه استفاده شده‌است.
          • #
            ‫۶ سال و ۳ ماه قبل، چهارشنبه ۲۳ خرداد ۱۳۹۷، ساعت ۲۳:۳۶
            ممنون. در واقع دلیل ایجاد تراکنش برا اضافه کردن رکورد به جداول پدر و فرزندی در یک درخواست است. میخوام پدر رو وقتی ذخیره میکنم مطمئن بشم که فرزندانش هم درست ذخیره شدن و اگه فرزندان درست ثبت نشد پدر هم خودبخود پاک بشه. آیا مدل Iunitofwork  خودش این تراکنش‌ها رو‌کنترل میکنه یا من خودم با همین روش گفته شده در منبع باید  پیاده سازی کنم؟
            • #
              ‫۶ سال و ۳ ماه قبل، چهارشنبه ۲۳ خرداد ۱۳۹۷، ساعت ۲۳:۴۲
              شما نیازی ندارید که برای هر کدام از این موارد یک SaveChanges جدا داشته باشید. تمام این اشیاء را به Context اضافه کنید و در پایان SaveChanges را فراخوانی کنید. کل این عملیات در طی یک تراکنش به بانک اطلاعاتی اعمال می‌شود و محاسبه و جایگذاری Idها هم در طی این تراکنش به صورت خودکار مدیریت خواهد شد.
              یعنی شما نیازی به محاسبه و دریافت مستقیم Id والد از بانک اطلاعاتی و سپس درج آن در رکوردهای فرزندان ، ندارید. EF این موارد را در طی یک تراکنش به صورت خودکار مدیریت می‌کند. همینقدر که رکوردهای فرزندان توسط خاصیت راهبری که تعریف شده، ارجاعی را به والد خود داشته باشند، از دیدگاه EF یعنی محاسبه‌ی خودکار کلید خارجی و درج آن.
          • #
            ‫۴ سال و ۴ ماه قبل، سه‌شنبه ۲۶ فروردین ۱۳۹۹، ساعت ۰۶:۰۲
            آیا برای خواص سایه ای که قراره بصورت خودکار مقداردهی بشن در سیستم ردیابی تغییرات هم از این مفهوم یعنی استفاده از یک وهله از context جاری بصورت جداگانه استفاده شده است؟ و دلیل اون چی بوده؟
  • #
    ‫۶ سال و ۲ ماه قبل، سه‌شنبه ۱۹ تیر ۱۳۹۷، ساعت ۰۲:۳۱
    با سلام؛ زمانیکه از  کد زیر برای جدول blog استفاده می‌کنیم، موقع استفاده، کل جدول رو برامون اطلاعاتش رو میاره. مثلا جدولی که ده هزار رکورد داره رو برام میاره. در صورتیکه نیاز ندارم. حالا نمیدونم من اشتباه مسیر رو رفتم یا واقعا همینطوریه؟
    _blogs = _uow.Set<Blog>();
     public BlogService(IUnitOfWork uow)
            {
                _uow = uow;
                _blogs = _uow.Set<Blog>();
            }
    • #
      ‫۶ سال و ۲ ماه قبل، سه‌شنبه ۱۹ تیر ۱۳۹۷، ساعت ۰۳:۴۳
      در ویژوال استودیو بر روی این قسمت break point گذاشتید و مقدار آن‌را بررسی کردید؟ اگر بله، بله. این مورد اشاره‌گری هست به کل یک جدول. کار دیباگر و visualizer آن، اجرای این قسمت از کد است (یعنی یک ToList را در اینجا بر روی آن اجرا می‌کند تا بتوانید مقدار آن‌را مشاهده کنید). یعنی بارگذاری کل جدول مرتبط با آن. اما اگر این قطعه کد را وادار به اجرای زود هنگام نکنید، یک DbSet بیشتر نیست که در کل زنجیره‌ی LINQ تعریف شده، به صورت یک عبارت تفسیر خواهد شد و نه اینکه به اینجا رسید، اجرا می‌شود.
  • #
    ‫۶ سال و ۲ ماه قبل، چهارشنبه ۲۷ تیر ۱۳۹۷، ساعت ۱۲:۰۶
    سلام. اگر من چند تا context داشته باشم و بخوام معماری IUnitOfWork رو استفاده کنم باید چکار کنم و درضمن ممکنه  از هر دو کانتکس در یک کلاس استفاده بشه
  • #
    ‫۵ سال و ۶ ماه قبل، جمعه ۱۷ اسفند ۱۳۹۷، ساعت ۱۷:۴۸
    تزریق وابستگی رو طبق همین آموزش انجام دادم ولی به IConfigurationRoot ایراد می‌گیره ، از net core 2.2 استفاده می‌کنم . 
    An unhandled exception occurred while processing the request.
    InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Configuration.IConfigurationRoot' while attempting to activate 'OnionArchitecture.Data.ApplicationDbContext'.
    Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, bool throwIfCallSiteNotFound)
  • #
    ‫۴ سال و ۳ ماه قبل، جمعه ۲ خرداد ۱۳۹۹، ساعت ۱۶:۰۱
    طبق این قسمت که شما اشاره داشتین :
    public interface IBlogService
        {
            IReadOnlyList<Blog> GetPagedBlogsAsNoTracking(int pageNumber, int recordsPerPage);
        }
    
        public class BlogService : IBlogService
        {
            private readonly IUnitOfWork _uow;
            private readonly DbSet<Blog> _blogs;
    
            public BlogService(IUnitOfWork uow)
            {
                _uow = uow;
                _blogs = _uow.Set<Blog>();
            }
    
            public IReadOnlyList<Blog> GetPagedBlogsAsNoTracking(int pageNumber, int recordsPerPage)
            {
                var skipRecords = pageNumber * recordsPerPage;
                return _blogs
                            .AsNoTracking()
                            .Skip(skipRecords)
                            .Take(recordsPerPage)
                            .ToList();
            }
        }

    اگر بخواهیم از امکانات توکار DbContext ( Linq و Lambda) استفاده کنیم(مثل سرچ در لیست رکوردها با استفاده از یه آیتم خاص و نه الزاما کلید اصلی) باید توی سرویس هامون DbContext رو جداگانه به شکل زیر تعریف کنیم یا از طریق  uow_ هم میشه به این امکانات دسترسی داشت؟
        public class BlogService : IBlogService
        {
            private readonly IUnitOfWork _uow;
            private readonly DbSet<Blog> _blogs;
            private readonly AppDbContext _ctx;
    
            public BlogService(IUnitOfWork uow, AppDbContext ctx)
            {
                _uow = uow;
                _blogs = _uow.Set<Blog>();
                _ctx = ctx
            }
    
           public IReadOnlyList<Blog> GetBlogWithTerm(string search)
            {
              var list = _ctx.Posts.Where(p => p.Title.Contains("test title")))
                               .ToList();
            }
        }
    • #
      ‫۴ سال و ۳ ماه قبل، جمعه ۲ خرداد ۱۳۹۹، ساعت ۱۶:۲۳
      ctx.Posts_ و ()<uow.Set<Post_  یکی هستند.
  • #
    ‫۳ سال و ۷ ماه قبل، سه‌شنبه ۱۴ بهمن ۱۳۹۹، ساعت ۱۵:۱۰
    با عرض سلام و تشکر
    1. در پروژه برای کلاس Product و Category، مثالی برای اعمال CRUD در سطح کنترلر ارائه نشده، یا من پیدا نکردم؟ یا چون در بحث Identity نبوده تکمیل نشده این قسمت.
    در این پروژه آیا برای انجام عملیات CRUD، به روشی که در پروژه «اعتبار سنجی مبتنی بر Jwt در ASP.net Core 2.0 بدون استفاده از سیستم Identity» معرفی کردین عمل کنم ؟

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

    • #
      ‫۳ سال و ۷ ماه قبل، سه‌شنبه ۱۴ بهمن ۱۳۹۹، ساعت ۱۵:۳۲
      - بله.
      - تفاوتی ندارند و در اصل یکی هستند.
      • #
        ‫۳ سال و ۷ ماه قبل، چهارشنبه ۱۵ بهمن ۱۳۹۹، ساعت ۱۳:۳۵
        من مشابه روشی که در مقاله jwt ارائه فرموده بودین، در کنترلر سرویس Product  رو تزریق کردم، و در اکشن متد Add سعی کردم یه نمونه از Product در جدول ثبت کنم. 
        ولی ب این خطا مواجه شدم: «متاسفانه در حین پردازش درخواست جاری خطایی رخ داده‌است. »
        1. چطور سیستم Error handling  رو خاموش کنم که خود Exception رو بتونم ببینم ؟
        2. اشتباه من کجا بوده ؟ متن کد من اینه :
        using Common.GuardToolkit;
        using Entities;
        using Microsoft.AspNetCore.Mvc;
        using Services.Contracts;
        using ViewModels;
        
        namespace web.Controllers
        {
            public class ProductController : Controller
            {
                private readonly IProductService _ProductService;
        
                public ProductController(IProductService ProductService)
                {
                    _ProductService = ProductService;
                    _ProductService.CheckArgumentIsNull(nameof(ProductService));
                }
        
                public IActionResult Add()
                {
                    return View("ProductAdd");
                }
        
                [HttpPost] 
                public IActionResult Add(ProductAddModel model)
                {
                    var product = new Product() { Name = model.Title, Price = 1, CategoryId = 1};
                    
                    _ProductService.AddNewProduct(product);
                    return Json(model);
        
                }
            }
        }

        • #
          ‫۳ سال و ۷ ماه قبل، چهارشنبه ۱۵ بهمن ۱۳۹۹، ساعت ۱۴:۲۸
          - نظرات و مطالب « بررسی فریم ورک Logging» و « فعال سازی صفحات مخصوص توسعه دهنده‌ها » را مطالعه کنید. 
          - اگر برنامه را از طریق dotnet run و یا dotnet watch run اجرا می‌کنید، در صفحه‌ی کنسول ظاهر شده، خطاها هم لاگ می‌شوند. یا اگر با ویژوال استودیو آن‌را اجرا می‌کنید، در برگه‌ی دیباگ آن این خطاها هم لاگ می‌شوند.

          - اگر از پروژه‌ی DNT Identity استفاده می‌کنید، به همراه یک ef db logger هست که اطلاعات خطاهای رخ‌داده را در بانک اطلاعاتی ذخیره می‌کند و در قسمت لاگ‌های سیستم قابل گزارشگیری هست (منوی مدیریتی/گزارش رخ‌دادهای سیستم).
          + مطلب «کار با ASP.NET Web API» را هم در مورد ویژگی‌های FromBody و امثال آن مطالعه کنید.  
  • #
    ‫۳ سال و ۷ ماه قبل، پنجشنبه ۱۶ بهمن ۱۳۹۹، ساعت ۲۲:۱۳
    سلام
    1. باتوجه به استفاده از DateTime.UtcNow برای ذخیره تاریخ،د من بعد از ثبت رکورد، تاریخهای ثبت شده رو به شکل زیر میبینم، لطفا راهنمایی بفرمایید که برای نگهداری تاریخ در قالب شمسی چکار باید کنم ؟

    2. اگر بخوام برای یک منظور، تاریخ (فقط تاریخ، بدون زمان) رو نگهداری کنم ، توصیه شما برای نوع داده ای پایگاه داده، و نیز روش درست ذخیره و بازیابی چی هست؟

    متشکرم