معرفی OLTP درون حافظهای در SQL Server 2014
جناب نصیری با تشکر از مقاله مفیدتون لازم میدونم در جهت تکمیل مباحث به چند نکته اشاره کنم
1- « اگر حالت SCHEMA_AND_DATA انتخاب شود، اطلاعات شما پس از ریاستارت سرور نیز در دسترس خواهد بود. این اطلاعات به صورت خودکار از لاگ تراکنشها بازیابی شده و مجددا در حافظه قرار میگیرند «.
بازیابی اطلاعات مربوط به تراکنشهایی که به ازای In Memory OLTP است بوسیله Data File + Delta File و Log File میباشد. در صورتیکه Schema_AND_Data را به ازای این نوع جداول فعال کنید دادههای شما در Data File و دادههای حذف شده در Delta File ثبت میگردد. مکانیزم Log File برای In Memory OLTP همچنان مانند جداول Disk base وجود دارد اما با بهینه سازی مناسب مانند ثبت Log Record کمتر به ازای عملیات کاربران و...
2- در جایی دیگر در متن اشاره شده که In Memory OLTP اجازه استفاده از Identity را به کاربر نمیدهد باید اشاره کنم که این موضوع برای نسخه CTP بوده است در نسخه RTM این قابلیت وجود دارد . لازم میدانم اشاره کنم که در Books Online جایی گفته شده که امکان استفاده وجود ندارد و در جایی هم گفته شده وجود دارد .
به مثال زیر دقت کنید
CREATE TABLE test( [ID] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT=10000), N1 NVARCHAR(100), N2 NVARCHAR(100), N3 NVARCHAR(100) ) WITH (MEMORY_OPTIMIZED=ON,DURABILITY = SCHEMA_AND_DATA) GO
باز هم از مطالب خوب شما متشکرم مقاله مفیدی بود .
شروع به کار با RavenDB
خیر. این فقط ساختار یک سند است. سند بعدی را هر طور که علاقمند بودید طراحی و ثبت کنید. متد session.Store محدودیتی ندارد. همچنین جایی هم در برنامه این ساختار در ابتدای کار به بانک اطلاعاتی معرفی یا ثبت نمیشود. وجود یک کلاس در برنامه به معنی تشکیل ساختار آن در بانک اطلاعاتی نیست.
بدون اسکیما یعنی هر رکورد با رکورد قبلی یا بعدی خودش میتواند ساختار کاملا متفاوتی داشته باشد.
نصب و راه اندازی
Install-Package MediatR
بعد از نصب نیاز داریم تا نیازمندیهای این فریمورک را داخل DI Container خود Register کنیم. اگر از DI Container پیشفرض ASP.NET Core استفاده کنیم ، کافیست پکیج متناسب آن با Microsoft.Extensions.DependencyInjection را نصب کرده و بهراحتی نیازمندیهای MediatR را فراهم سازیم:
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddMediatR(); }
* اگر از DI Containerهای دیگری استفاده میکنید، میتوانید با استفاده از توضیحات این لینک MediatR را داخل Container مورد نظرتان Register کنید.
IRequest
پیاده سازی Non-Generic آن برای درخواستهایی است که Response برگشتی ندارند ( معمولا Commandها ) و منتظر جوابی از سمت آنها نیستیم و پیاده سازی Generic آن، نوع Response ای را که بعد از پردازش درخواست برگشت داده میشود، مشخص میسازد.
public class Customer { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime RegistrationDate { get; set; } }
و Dto متناسب با آن نیز به این صورت تعریف شده است :
public class CustomerDto { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string RegistrationDate { get; set; } }
افزودن مشتری، یک Command است؛ زیرا باعث افزودن رکوردی جدیدی به دیتابیس و تغییر State برنامه میشود. کلاس جدیدی به اسم CreateCustomerCommand ایجاد کرده و از IRequest ارث بری میکنیم و نوع Response برگشتی آن را CustomerDto قرار میدهیم:
public class CreateCustomerCommand : IRequest<CustomerDto> { public CreateCustomerCommand(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } public string FirstName { get; } public string LastName { get; } }
کلاس CreateCustomerCommand نیازمندیهای خود را از طریق Constructor مشخص میسازد. برای ایجاد کردن یک مشتری حداقل چیزی که لازم است، Firstname و Lastname آن است و بعد از ارسال مقادیر مورد نیاز به سازنده این کلاس، مقادیر بدلیل get-only بودن قابل تغییر نیستند.
IRequestHandler
public class CreateCustomerCommandHandler : IRequestHandler<CreateCustomerCommand, CustomerDto> { readonly ApplicationDbContext _context; readonly IMapper _mapper; public CreateCustomerCommandHandler(ApplicationDbContext context, IMapper mapper) { _context = context; _mapper = mapper; } public async Task<CustomerDto> Handle(CreateCustomerCommand createCustomerCommand, CancellationToken cancellationToken) { Customer customer = _mapper.Map<Customer>(createCustomerCommand); await _context.Customers.AddAsync(customer, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map<CustomerDto>(customer); } }
ورودی اول IRequestHandler، کلاسی است که درخواست، آن را پردازش خواهد کرد و پارامتر ورودی دوم، کلاسی است که در نتیجه پردازش بعنوان Response برگشت داده خواهد شد.
همانطور که میبینید در این Handler از DbContext مربوط به Entity Framework برای ثبت اطلاعات داخل دیتابیس و IMapper مربوط به AutoMapper برای نگاشت CreateCustomerCommand به Customer استفاده شده است.
تنظیمات Profile مربوط به AutoMapper ما به این صورت است تا در هنگام نگاشت CreateCustomerCommand ، مقدار RegistrationDate مربوط به Customer برابر با زمان فعلی قرار داده شود و برای نگاشت Customer به CustomerDto نیز ، تاریخ RegistrationDate با فرمتی قابل فهم به کاربران نمایش داده شود :
public class DomainProfile : Profile { public DomainProfile() { CreateMap<CreateCustomerCommand, Customer>() .ForMember(c => c.RegistrationDate, opt => opt.MapFrom(_ => DateTime.Now)); CreateMap<Customer, CustomerDto>() .ForMember(cd => cd.RegistrationDate, opt => opt.MapFrom(c => c.RegistrationDate.ToShortDateString())); } }
در نهایت با inject کردن اینترفیس IMediator به کنترلر خود و فرستادن یک درخواست POST به این اکشن، درخواست ایجاد مشتری را توسط متد Send میدهیم :
[HttpPost] public async Task<IActionResult> CreateCustomer([FromBody] CreateCustomerCommand createCustomerCommand) { CustomerDto customer = await _mediator.Send(createCustomerCommand); return CreatedAtAction(nameof(GetCustomerById), new { customerId = customer.Id }, customer); }
همانطور که میبینید ما در اینجا فقط درخواست، فرستادهایم و وظیفه پیدا کردن Handler این درخواست را فریمورک MediatR برعهده گرفتهاست و ما هیچ جایی بطور مستقیم Handler خود را صدا نزده ایم. ( Hollywood Principle: Don't Call Us, We Call You )
اینترفیس IMediator علاوه بر متد Send ، دارای متد دیگری بنام Publish نیز هست که وظیفه Raise کردن Eventها را برعهده دارد که در مقالات بعدی از آن استفاده خواهیم کرد.
چند نکته :
ASP.NET MVC #6
آشنایی با انواع ActionResult
در قسمت چهارم، اولین متد یا اکشنی که به صورت خودکار توسط VS.NET به برنامه اضافه شد، اینچنین بود:
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
}
توضیحات تکمیلی مرتبط با خروجی از نوع ActionResult ایی را که مشاهده میکنید، در این قسمت ارائه خواهد شد.
رفتار یک کنترلر توسط متدهایی که در آن کلاس تعریف میشوند، مشخص میگردد. هر متد هم از طریق یک URL مجزا قابل دسترسی و فراخوانی خواهد بود. این متدها که به آنها اکشن نیز گفته میشود باید عمومی بوده، استاتیک یا متد الحاقی (extension method) نباشند و همچنین دارای پارامترهایی از نوع ref و out نیز نباشند.
هر درخواست رسیده، به یک کنترلر و متدی عمومی در آن توسط سیستم مسیریابی، نگاشت خواهد شد. اگر علاقمند باشید که در یک کلاس کنترلر، متدی عمومی را از این سیستم خارج کنید، تنها کافی است آنرا با ویژگی (attribute) به نام NonAction مزین کنید:
using System.Web.Mvc;
namespace MvcApplication2.Controllers
{
public class HomeController : Controller
{
[NonAction]
public string ShowData()
{
return "Text";
}
public ActionResult Index()
{
ViewBag.Message = string.Format("{0}/{1}/{2}",
RouteData.Values["controller"],
RouteData.Values["action"],
RouteData.Values["id"]);
return View();
}
public ActionResult Search(string data = "*")
{
// do something ...
return View();
}
}
}
چند نکته در این مثال قابل ذکر است:
الف) در اینجا اگر شخصی آدرس http://localhost/home/showdata را درخواست نماید، با توجه به استفاده از ویژگی NonAction، با پیغام یافت نشد یا 404 مواجه میگردد.
ب) صرفنظر از پارامترهای یک متد و ساختار کلاس جاری، اطلاعات مسیریابی از طریق شیء RouteData.Values نیز در دسترس هستند که نمونهای از آنرا در اینجا بر اساس مقادیر پیش فرض تعاریف مسیریابی یک پروژه ASP.NET MVC ملاحظه مینمائید.
ج) در متد Search، از قابلیت امکان تعریف مقداری پیش فرض جهت آرگومانها در سی شارپ 4 استفاده شده است. به این ترتیب اگر شخصی آدرس http://localhost/home/search را وارد کند، چون پارامتری را ذکر نکرده است، به صورت خودکار از مقدار پیش فرض آرگومان data استفاده میگردد.
انواع Action Results در ASP.NET MVC
در ASP.NET MVC بجای استفاده مستقیم از شیء Response، از شیء ActionResult جهت ارائه خروجی یک متد استفاده میشود و مهمترین دلیل آن هم مشکل بودن نوشتن آزمونهای واحد برای شیء Response است که وهله سازی آن مساوی است با به کار اندازی موتور ASP.NET و Http Runtime آن توسط یک وب سرور (بنابراین در ASP.NET MVC سعی کنید شیء Response را فراموش کنید).
سلسه مراتب ActionResultهای قابل استفاده در ASP.NET در تصویر زیر مشخص شدهاند:
و در مثال زیر تقریبا انواع و اقسام ActionResultهای مهم و کاربردی ASP.NET MVC را میتوانید مشاهده کنید:
using System.Web.Mvc;
namespace MvcApplication2.Controllers
{
public class ActionResultsController : Controller
{
//http://localhost/actionresults/welcome
public string Welcome()
{
return "Hello, World";
}
//http://localhost/actionresults/index
public ActionResult Index() // or ContentResult
{
return Content("Hello, World");
}
//http://localhost/actionresults/SendMail
public void SendMail()
{
}
public ActionResult SendMailCompleted() // or EmptyResult
{
// do whatever
return new EmptyResult();
}
public ActionResult GetFile() // or FilePathResult
{
return File(Server.MapPath("~/content/site.css"), "text/css", "mySite.css");
}
public ActionResult UnauthorizedStatus() // or HttpStatusCodeResult/HttpUnauthorizedResult
{
return new HttpUnauthorizedResult("You need to login first.");
}
public ActionResult Status() // or HttpStatusCodeResult
{
return new HttpStatusCodeResult(501, "Server Error");
}
public ActionResult GetJavaScript() // or JavaScriptResult
{
return JavaScript("...JavaScript...");
}
public ActionResult GetJson() // or JsonResult
{
var obj = new { prop1 = 1, prop2 = "data" };
return Json(obj, JsonRequestBehavior.AllowGet);
}
public ActionResult RedirectTo() // or RedirectResult
{
return RedirectPermanent("http://www.site.com");
//return RedirectToAction("Home", "Index");
}
public ActionResult ShowView() // or ViewResult
{
return View();
}
}
}
چند نکته در این مثال وجود دارد:
1) مثلا متد GetJavaScript را درنظر بگیرید. در این متد خاص، چه بنویسید public ActionResult GetJavaScript یا بنویسید public JavaScriptResult GetJavaScript تفاوتی نمیکند. در سایر موارد هم به همین ترتیب است. علت را در تصویر سلسله مراتبی ActionResultها میتوان جستجو کرد. تمام این کلاسها نوعی ActionResult هستند و از یک کلاس پایه به ارث رسیدهاند.
2) مثلا ContentResult شبیه به همان Response.Write سابق ASP.NET عمل میکند. علت وجودی آن هم عدم وابستگی مستقیم به شیء Response و سادهتر سازی نوشتن آزمونهای واحد برای این نوع اکشن متدها است.
3) منهای متد آخری که نمایش داده شده (ShowView)، هیچکدام از متدهای دیگر نیازی به View متناظر ندارند. یعنی نیازی نیست تا روی متد کلیک راست کرده و Add view را انتخاب کنیم. چون در همین متد کنترلر، کار Response به پایان میرسد و مرحله بعدی ندارد. مثلا در حالت return File، یک فایل به درون مرورگر کاربر Flush خواهد شد و تمام.
4) متد Welcome و متد Index در اینجا به یک صورت تفسیر میشوند. به این معنا که اگر خروجی متد تعریف شده در یک کنترلر از نوع ActionResult نباشد، به صورت پیش فرض درون یک ContentResult محصور خواهد شد.
5) اگر خروجی متدی در اینجا از نوع void باشد، با ActionResult ایی به نام EmptyResult یکسان خواهد بود. بنابراین با متدهای SendMail و SendMailCompleted به یک نحو رفتار میگردد.
6) return Json یاد شده که خروجیاش از نوع JsonResultاست در پیاده سازیهای Ajax ایی کاربرد دارد.
7) جهت بازگرداندن حالت وضعیت 403 یا غیرمجاز میتوان از return new HttpUnauthorizedResult استفاده کرد.
8) یا جهت اعلام مشکلی در سمت سرور به کمک return new HttpStatusCodeResultکد ویژهای را میتوان به کاربر نمایش داد.
9) به کمک return RedirectToAction میتوان به یک کنترلر و متدی خاص در آن، کاربر را هدایت کرد.
و خلاصه اینکه تمام کارهایی را که پیشتر در ASP.NET Web forms ، مستقیما به کمک شیء Response انجام میدادید (Response.Write، Response.End، Response.Redirect و غیره)، اینبار به کمک یکی از ActionResultهای یاد شده انجام دهید تا بتوان بدون نیاز به راه اندازی یک وب سرور، برای متدهای کنترلرها آزمون واحد نوشت. برای مثال:
[TestMethod]
public void TestMethod1()
{
// Arrange
var controller = new ActionResultsController();
// Act
var result = controller.Index() as ContentResult;
// Assert
Assert.NotNull(result);
Assert.AreEqual( "Hello, World", result.Content);
}
نوشتن TagHelperهای سفارشی برای ASP.NET Core
نامی که برای ثبت یک TagHelper یا مجموعهای از آنها در فایل ViewImports باید درج شود، دقیقا نام اسمبلی دربرگیرندهی آنها است و نه نام فضای نام کلاسهای مرتبط. برای مثال اگر dll تولیدی، core-resources.dll نام دارد و فضای نام آن core_resources است، برای تعریف و ثبت آن باید نوشت (استفاده از نام اسمبلی):
@addTagHelper *, core-resources
"buildOptions": { "outputName": "core_resources" },
@addTagHelper *, core_resources
public class TestViewModel { private readonly ITestService _testService; public TestViewModel(ITestService testService) //تزریق وابستگی در سازنده کلاس { _testService = testService; }
نام تمام Viewهای برنامه به View ختم میشوند و نام ViewModelها به ViewModel. برای مثال TestViewModel و TestView معرف یک ViewModel و View متناظر خواهند بود.
ساختار کلاسهای لایه سرویس برنامه
namespace DI07.Services { public interface ITestService { string Test(); } } namespace DI07.Services { public class TestService: ITestService { public string Test() { return "برای آزمایش"; } } }
علامتگذاری ViewModelها
در ادامه یک اینترفیس خالی را به نام IViewModel مشاهده میکنید:
namespace DI07.Core { public interface IViewModel // از این اینترفیس خالی برای یافتن و علامتگذاری ویوو مدلها استفاده میکنیم { } }
برای نمونه کلاس TestViewModel برنامه، با پیاده سازی IViewModel، به نوعی نشانه گذاری نیز شده است:
using DI07.Services; using DI07.Core; namespace DI07.ViewModels { public class TestViewModel : IViewModel // علامتگذاری ویوو مدل { private readonly ITestService _testService; public TestViewModel(ITestService testService) //تزریق وابستگی در سازنده کلاس { _testService = testService; } public string Data { get { return _testService.Test(); } } } }
تنظیمات آغازین IoC Container مورد استفاده
در کلاس استاندارد App برنامه WPF خود، کار تنظیمات اولیه StructureMap را انجام خواهیم داد:
using System.Windows; using DI07.Core; using DI07.Services; using StructureMap; namespace DI07 { public partial class App { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); ObjectFactory.Configure(cfg => { cfg.For<ITestService>().Use<TestService>(); cfg.Scan(scan => { scan.TheCallingAssembly(); // Add all types that implement IView into the container, // and name each specific type by the short type name. scan.AddAllTypesOf<IViewModel>().NameBy(type => type.Name); scan.WithDefaultConventions(); }); }); } } }
همچنین در ادامه از قابلیت اسکن این IoC Container برای یافتن کلاسهایی که IViewModel را در اسمبلی جاری پیاده سازی کردهاند، استفاده شده است. متد NameBy، سبب میشود که بتوان به این نوعهای یافت شده از طریق نام کلاسهای متناظر دسترسی یافت.
اتصال خودکار ViewModelها به Viewهای برنامه
using System.Windows.Controls; using StructureMap; namespace DI07.Core { /// <summary> /// Stitches together a view and its view-model /// </summary> public static class ViewModelFactory { public static void WireUp(this ContentControl control) { var viewName = control.GetType().Name; var viewModelName = string.Concat(viewName, "Model"); //قرار داد نامگذاری ما است control.Loaded += (s, e) => { control.DataContext = ObjectFactory.GetNamedInstance<IViewModel>(viewModelName); }; } } }
استفاده از کلاس ViewModelFactory
در ادامه کدهای TestView و پنجره اصلی برنامه را مشاهده میکنید:
<UserControl x:Class="DI07.Views.TestView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <TextBlock Text="{Binding Data}" /> </Grid> </UserControl> <Window x:Class="DI07.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Views="clr-namespace:DI07.Views" Title="MainWindow" Height="350" Width="525"> <Grid> <Views:TestView /> </Grid> </Window>
using DI07.Core; namespace DI07.Views { public partial class TestView { public TestView() { InitializeComponent(); this.WireUp(); //تزریق خودکار وابستگیها و یافتن ویوو مدل متناظر } } }
دریافت پروژه کامل این قسمت
DI07.zip
نحوه برقراری ارتباطات بین صفحات، سیستم راهبری و ViewModelها در قالب پروژه WPF Framework
1) ایجاد صفحه تغییر مشخصات کاربر
کلیه Viewهای برنامه، در پروژه ریشه، ذیل پوشه Views اضافه خواهند شد. همچنین چون در آینده تعداد این فایلها افزایش خواهند یافت، بهتر است جهت مدیریت آنها، به ازای هر گروه از قابلیتها، یک پوشه جدید را ذیل پوشه Views اضافه کرد.
همانطور که ملاحظه میکنید در اینجا پوشه UserInfo به همراه یک فایل جدید XAML به نام ChangeProfile.xaml، ذیل پوشه Views پروژه ریشه اصلی اضافه شدهاند.
ChangeProfile.xaml از نوع Page است؛ از این جهت که اگر به فایل MainWindow.xaml که سیستم راهبری برنامه در آن تعبیه شده است مراجعه کنید، یک چنین تعریفی را ملاحظه خواهید نمود:
<CustomControls:FrameFactory x:Name="ActiveScreen" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" NavigationUIVisibility="Hidden" Grid.Column="1" Margin="0" />
مرحله بعد، تعریف محتویات فایل ChangeProfile.xaml است. در این فایل اطلاعات انقیاد دادهها از ViewModel مرتبط که در ادامه توضیح داده خواهد شد دریافت میگردد. مثلا مقدار خاصیت ChangeProfileData.Password، از ViewModel به صورت خودکار تغذیه خواهد شد.
در این فایل یک سری DynamicResource را هم برای تعریف دکمههای سبک مترو ملاحظه میکنید. کلیدهای متناظر با آن در فایل Icons.xaml که در فایل App.xaml برای کل برنامه ثبت شده است، تامین میگردد.
2) تنظیم اعتبارسنجی صفحه اضافه شده
پس از اینکه صفحه جدید اضافه شد، نیاز است وضعیت دسترسی به آن مشخص شود:
/// <summary> /// تغییر مشخصات کاربر جاری /// </summary> [PageAuthorization(AuthorizationType.FreeForAuthenticatedUsers)] public partial class ChangeProfile
در مورد نحوه تعیین نقشهای متفاوت در صورت نیاز، در قسمت قبل بحث گردید.
3) تغییر منوی برنامه جهت اشاره به صفحه جدید
خوب، ما تا اینجا یک صفحه جدید را تهیه کردهایم. در مرحله بعد باید مدخلی را در منوی برنامه جهت اشاره به آن تهیه کنیم.
منوی برنامه در فایل MainMenu.xaml قرار دارد. اطلاعات متناظر با دکمه ورود به صفحه تغییر مشخصات کاربری نیز به شکل ذیل تعریف شده است:
<Button Style="{DynamicResource MetroCircleButtonStyle}" Height="55" Width="55" Command="{Binding DoNavigate}" CommandParameter="\Views\UserInfo\ChangeProfile.xaml" Margin="2"> <Rectangle Width="28" Height="17.25"> <Rectangle.Fill> <VisualBrush Stretch="Fill" Visual="{StaticResource appbar_user_tie}" /> </Rectangle.Fill> </Rectangle> </Button>
در این ViewModel تعاریف DoNavigate و پردازش پارامتر دریافتی به صورت خودکار صورت خواهد گرفت و سورس کامل آن در اختیار شما است. بنابراین تنها کافی است CommandParameter را مشخص کنید، DoNavigate کار هدایت به آنرا انجام خواهد داد.
4) ایجاد ViewModel متناظر با صفحه
مرحله آخر افزودن یک صفحه، تعیین ViewModel متناظر با آن است. عنوان شد که اطلاعات مورد نیاز جهت عملیات Binding در این فایل قرار میگیرند و اگر به فایل ChangeProfileViewModel.cs مراجعه کنید (نام آن مطابق قرارداد، یک کلمه ViewModel را نسبت به نام View متناظر بیشتر دارد)، چنین خاصیت عمومی را در آن خواهید یافت.
مطابق قراردادهای توکار قالب تهیه شده:
- نیاز است ViewModel تعریف شده از کلاس پایه BaseViewModel مشتق شود تا علاوه بر تامین یک سری کدهای تکراری مانند:
public abstract class BaseViewModel : DataErrorInfoBase, INotifyPropertyChanged, IViewModel
- پس از اضافه شدن کلاس پایه BaseViewModel نیاز است تکلیف خاصیت public override bool ViewModelContextHasChanges را نیز مشخص کنید. در اینجا به سیستم راهبری اعلام میکنید که آیا در ViewModel جاری تغییرات ذخیره نشدهای وجود دارند؟ فقط باید true یا false را بازگردانید. برای مثال خاصیت uow.ContextHasChanges برای این منظور بسیار مناسب است و از طریق پیاده سازی الگوی واحد کار به صورت خودکار چنین اطلاعاتی را در اختیار برنامه قرار میدهد.
در ViewModelها هرجایی که نیاز به اطلاعات کاربر وارد شده به سیستم داشتید، از اینترفیس IAppContextService در سازنده کلاس ViewModel جاری استفاده کنید. اینترفیس IUnitOfWork امکانات ذخیره سازی اطلاعات و همچنین مشخص سازی وضعیت Context جاری را در اختیار شما قرار میدهد.
کلیه کدهای کار کردن با یک موجودیت باید در کلاس سرویس متناظر با آن قرار گیرند و این کلاس سرویس توسط اینترفیس آن مانند IUsersService در اینجا باید توسط سازنده کلاس در اختیار ViewModel قرار گیرد.
تزریق وابستگیها در اینجا خودکار بوده و تنظیمات آن در فایل IocConfig.cs پروژه Infrastructure قرار دارد. این کلاس آنچنان نیازی به تغییر ندارد؛ اگر پیش فرضهای نامگذاری آنرا مانند کلاسهای Test و اینترفیسهای ITest، در لایه سرویس برنامه رعایت شوند.
- GraphQL نیازمند رفت و برگشتهای کمتری به server، به منظور بازیابی دادهها برای template یا view است. همراه با REST ما مجبور هستیم که چندیدن endpoint مثلا (... api/students, api/courses, api/instructors ) را برای گرفتن همه دادههای که برای template یا view نیاز داریم ملاقات کنیم؛ ولی این شرایط در GraphQL برقرار نیست. با GraphQL ما تنها یک query را ایجاد میکنیم که چندین تابع (resolver) را در سمت سرور فراخوانی میکند و همه دادهها را از منابع مختلفی، در یک درخواست برگشت میدهد.
- همراه با REST، همانطور که Application ما رشد میکند، تعداد endpointها هم زیاد میشوند که این نیازمند زمان بیشتری برای نگهداری میباشد. اما با GraphQL ما تنها یک endpoint داریم؛ همین!
- با استفاده از GraphQL، ما هرگز به مشکل گرفتن دادههایی کم یا زیاد از منبع روبرو نخواهیم شد. به این خاطر است که ما queryها را با فیلدهایی که چیزهایی را که نیاز داریم، نشان میدهند، تعریف میکنیم. در این صورت ما همیشه چیزهایی را که درخواست دادهایم، دریافت میکنیم.
query OwnersQuery { owners { name account { type } } }
{ "data": { "owners": [ { "name": "John Doe", "accounts": [ { "type": "Cash" }, { "type": "Savings" } ] } ] } }
dotnet new api -n ASPCoreGraphQL
پوشه Contracts شامل واسطهای مورد نیاز برای repository logic میباشد:
namespace ASPCoreGraphQL.Contracts { public interface IOwnerRepository { } }
namespace ASPCoreGraphQL.Contracts { public interface IAccountRepository { } }
public class Owner { [Key] public Guid Id { get; set; } [Required(ErrorMessage = "Name is required")] public string Name { get; set; } public string Address { get; set; } public ICollection<Account> Accounts { get; set; } } public class Account { [Key] public Guid Id { get; set; } [Required(ErrorMessage = "Type is required")] public TypeOfAccount Type { get; set; } public string Description { get; set; } [ForeignKey("OwnerId")] public Guid OwnerId { get; set; } public Owner Owner { get; set; } } public enum TypeOfAccount { Cash, Savings, Expense, Income }
public class ApplicationContext : DbContext { public ApplicationContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { } public DbSet<Owner> Owners { get; set; } public DbSet<Account> Accounts { get; set; } }
public class OwnerRepository : IOwnerRepository { private readonly ApplicationContext _context; public OwnerRepository(ApplicationContext context) { _context = context; } }
public class AccountRepository : IAccountRepository { private readonly ApplicationContext _context; public AccountRepository(ApplicationContext context) { _context = context; } }
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationContext>(opt => opt.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddScoped<IOwnerRepository, OwnerRepository>(); services.AddScoped<IAccountRepository, AccountRepository>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2) .AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore); }
dotnet add package GraphQL
dotnet add package GraphQL.Server.Transports.AspNetCore
dotnet add package GraphQL.Server.Ui.Playground
public class AppSchema : Schema { public AppSchema(IDependencyResolver resolver) :base(resolver) { } }
public class OwnerType : ObjectGraphType<Owner> { public OwnerType() { Field(x => x.Id, type: typeof(IdGraphType)).Description("Id property from the owner object."); Field(x => x.Name).Description("Name property from the owner object."); Field(x => x.Address).Description("Address property from the owner object."); } }
public interface IOwnerRepository { IEnumerable<Owner> GetAll(); } public class OwnerRepository : IOwnerRepository { private readonly ApplicationContext _context; public OwnerRepository(ApplicationContext context) { _context = context; } public IEnumerable<Owner> GetAll() => _context.Owners.ToList(); }
public class AppQuery : ObjectGraphType { public AppQuery(IOwnerRepository repository) { Field<ListGraphType<OwnerType>>( "owners", resolve: context => repository.GetAll() ); } }
public class AppSchema : Schema { public AppSchema(IDependencyResolver resolver) :base(resolver) { Query = resolver.Resolve<AppQuery>(); } }
public void ConfigureServices(IServiceCollection services) { ... services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService)); services.AddScoped<AppSchema>(); services.AddGraphQL(o => { o.ExposeExceptions = false; }) .AddGraphTypes(ServiceLifetime.Scoped); ... }
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { ... app.UseGraphQL<AppSchema>(); app.UseGraphQLPlayground(options: new GraphQLPlaygroundOptions()); app.UseMvc(); }
dotnet run
https://localhost:5001/ui/playground