Let’s dive in and see what’s new in TypeScript 3.4!
- Faster subsequent builds with the --incremental flag
- Higher order type inference from generic functions
- Improvements for ReadonlyArray and readonly tuples
- const assertions
- Type-checking for globalThis
- Convert parameters to destructured object
- Breaking changes
مثال دوم) به استفاده کننده از API کتابخانه خود، اجازه فرمول نویسی بدهید
برای نمونه مثال ساده زیر را درنظر بگیرید که در آن قرار است یک سری عدد که از منبع دادهای دریافت شدهاند، بر روی صفحه نمایش داده شوند:
public static void PrintNumbers() { var numbers = new[] { 1,2,3,5,7,90 }; // from a data source foreach(var item in numbers) { Console.WriteLine(item); } }
روال کار اکثر ابزارهای گزارشسازی موجود، ارائه یک زبان اسکریپتی جدید برای حل این نوع مسایل است. اما با استفاده از Func و ... روشهای Code first (بجای روشهای Wizard first)، خیلی از این رنج و دردها را میتوان سادهتر و بدون نیاز به اختراع و یا آموزش زبان جدیدی حل کرد:
public static void PrintNumbers(Func<int,string> formula) { var numbers = new[] { 1,2,3,5,7,90 }; // from a data source foreach(var item in numbers) { var data = formula(item); Console.WriteLine(data); } }
PrintNumbers(number => string.Format("{0:n0}",number));
PrintNumbers((number) =>{ return string.Format("{0:n0}",number); });
از این نوع طراحی، در ابزارها و کتابخانههای جدید گزارش سازی مخصوص ASP.NET MVC زیاد مشاهده میشوند.
مثال سوم) حذف کدهای تکراری برنامه
فرض کنید قصد دارید در برنامه وب خود مباحث caching را پیاده سازی کنید:
using System; using System.Web; using System.Web.Caching; using System.Collections.Generic; namespace WebToolkit { public static class CacheManager { public static void CacheInsert(this HttpContextBase httpContext, string key, object data, int durationMinutes) { if (data == null) return; httpContext.Cache.Add( key, data, null, DateTime.Now.AddMinutes(durationMinutes), TimeSpan.Zero, CacheItemPriority.AboveNormal, null); } } }
var item = httpContext.Cache[key]; if (item == null) { item = ReadDataFromDataSource(); if (item == null) return null; CacheInsert(httpContext, key, item, durationMinutes); }
میتوان در این الگوی تکراری، خواندن اطلاعات را از منبع داده، به یک Func واگذار کرد و به این صورت کدهای ما به نحو زیر بازسازی خواهند شد:
using System; using System.Web; using System.Web.Caching; using System.Collections.Generic; namespace WebToolkit { public static class CacheManager { public static void CacheInsert(this HttpContextBase httpContext, string key, object data, int durationMinutes) { if (data == null) return; httpContext.Cache.Add( key, data, null, DateTime.Now.AddMinutes(durationMinutes), TimeSpan.Zero, CacheItemPriority.AboveNormal, null); } public static T CacheRead<T>(this HttpContextBase httpContext, string key, int durationMinutes, Func<T> ifNullRetrievalMethod) { var item = httpContext.Cache[key]; if (item == null) { item = ifNullRetrievalMethod(); if (item == null) return default(T); CacheInsert(httpContext, key, item, durationMinutes); } return (T)item; } } }
var user = HttpContext.CacheRead( "Key1", 15, () => _usersService.FindUser(userId));
gRPC Service with .NET 7
In this video we build a gRPC service with 5 methods: Create, Read (single), List (multiple), Update and Delete. We then employ JSON transcoding (a new feature added in .NET 7) to allow our gRPC service to act as a REST based API. This allows web-based endpoints to consume our service, while at the same time continuing to allow native gRPC clients to consume as well.
⏲️ Time Codes ⏲️
- 0:33 - Welcome
- 2:00 - gRPC Overview
- 5:08 - Scaffold the prject
- 9:58 - Test "greeter" service with Postman
- 12:32 - Add package dependencies
- 14:48 - Create the Model
- 16:38 - Create DB Context & migrations
- 22:37 - Define the protobuf file
- 32:39 - Build the first service method
- 40:55 - Test method with postman
- 42:52 - Read method
- 48:42 - List method
- 52:39 - Update method
- 59:14 - Delete method
- 1:03:24 - Add the annotation files
- 1:06:07 - Annotate the first gRPC method
- 1:09:26 - Annotate remaining methods
- 1:12:42 - Test with Postman
- 1:16;00 - Patreon supporter credits
کدهای کامل این مثال را در ذیل ملاحظه میکنید:
using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Diagnostics; using System.Linq; namespace EFGeneral { public class User { public int Id { get; set; } public string Name { get; set; } } public class MyContext : DbContext { public DbSet<User> Users { get; set; } } public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { for (int i = 0; i < 21000; i++) { context.Users.Add(new User { Name = "name " + i }); if (i % 1000 == 0) context.SaveChanges(); } base.Seed(context); } } public class PerformanceHelper { public static string RunActionMeasurePerformance(Action action) { GC.Collect(); long initMemUsage = Process.GetCurrentProcess().WorkingSet64; var stopwatch = new Stopwatch(); stopwatch.Start(); action(); stopwatch.Stop(); var currentMemUsage = Process.GetCurrentProcess().WorkingSet64; var memUsage = currentMemUsage - initMemUsage; if (memUsage < 0) memUsage = 0; return string.Format("Elapsed time: {0}, Memory Usage: {1:N2} KB", stopwatch.Elapsed, memUsage / 1024); } } public static class Test { public static void RunTests() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>()); StartDb(); for (int i = 0; i < 3; i++) { Console.WriteLine("\nRun {0}", i + 1); var memUsage = PerformanceHelper.RunActionMeasurePerformance(() => LoadWithTracking()); Console.WriteLine("LoadWithTracking:\n{0}", memUsage); memUsage = PerformanceHelper.RunActionMeasurePerformance(() => LoadWithoutTracking()); Console.WriteLine("LoadWithoutTracking:\n{0}", memUsage); } } private static void StartDb() { using (var ctx = new MyContext()) { var user = ctx.Users.Find(1); if (user != null) { // keep the object in memory } } } private static void LoadWithTracking() { using (var ctx = new MyContext()) { var list = ctx.Users.ToList(); if (list.Any()) { // keep the list in memory } } } private static void LoadWithoutTracking() { using (var ctx = new MyContext()) { var list = ctx.Users.AsNoTracking().ToList(); if (list.Any()) { // keep the list in memory } } } } }
توضیحات:
مدل برنامه یک کلاس ساده کاربر است به همراه id و نام او.
سپس این کلاس توسط Context برنامه در معرض دید EF Code first قرار میگیرد.
در کلاس Configuration تعدادی رکورد را در ابتدای کار برنامه در بانک اطلاعاتی ثبت خواهیم کرد. قصد داریم میزان مصرف حافظه بارگذاری این اطلاعات را بررسی کنیم.
کلاس PerformanceHelper معرفی شده، دو کار اندازه گیری میزان مصرف حافظه برنامه در طی اجرای یک فرمان خاص و همچنین مدت زمان سپری شدن آنرا اندازهگیری میکند.
در کلاس Test فوق چندین متد به شرح زیر وجود دارند:
متد StartDb سبب میشود تا تنظیمات ابتدایی برنامه به بانک اطلاعاتی اعمال شوند. تا زمانیکه کوئری خاصی به بانک اطلاعاتی ارسال نگردد، EF Code first بانک اطلاعاتی را آغاز نخواهد کرد.
در متد LoadWithTracking اطلاعات تمام رکوردها به صورت متداولی بارگذاری شده است.
در متد LoadWithoutTracking نحوه استفاده از متد الحاقی AsNoTracking را مشاهده میکنید. در این متد سطح اول کش به این ترتیب خاموش میشود.
و متد RunTests، این متدها را در سه بار متوالی اجرا کرده و نتیجه عملیات را نمایش خواهد داد.
برای نمونه این نتیجه در اینجا حاصل شده است:
همانطور که ملاحظه کنید، بین این دو حالت، تفاوت بسیار قابل ملاحظه است؛ چه از لحاظ مصرف حافظه و چه از لحاظ سرعت.
نتیجه گیری:
اگر قصد ندارید بر روی اطلاعات دریافتی از بانک اطلاعاتی تغییرات خاصی را انجام دهید و فقط قرار است از آنها به صورت فقط خواندنی گزارشگیری شود، بهتر است سطح اول کش را به کمک متد الحاقی AsNoTracking خاموش کنید.
همانطور که در تصویر مشخص است، خروجیهای JSON عموما ترکیبی هستند از مقادیر، آرایهها و اشیاء. هر کدام از اینها در LINQ to JSON به اشیاء JValue، JArray و JObject نگاشت میشوند. البته در حالت JObject هر عضو به یک JProperty و JValue تجزیه خواهد شد.
برای مثال آرایه [1,2] تشکیل شدهاست از یک JArray به همراه دو JValue که مقادیر آنرا تشکیل میدهند. اگر مستقیما بخواهیم یک JArray را تشکیل دهیم میتوان از شیء JArray استفاده کرد:
var array = new JArray(1, 2, 3); var arrayToJson = array.ToString();
var json = "[1,2,3]"; var jArray= JArray.Parse(json); var val = (int)jArray[0];
در حالت کار با اشیاء، شیء JObject امکان تهیه اشیاء JSON ایی را دارا است که میتواند مجموعهای از JPropertyها باشد:
var jObject = new JObject( new JProperty("prop1", "value1"), new JProperty("prop2", "value2") ); var jObjectToJson = jObject.ToString();
dynamic jObj = new JObject(); jObj.Prop1 = "value1"; jObj.Prop2 = "value2"; jObj.Roles = new[] {"Admin", "User"};
و حالت عکس آن توسط متد JObject.Parse قابل انجام است:
var json = "{ 'prop1': 'value1', 'prop2': 'value2'}"; var jObj = JObject.Parse(json); var val1 = (string)jObj["prop1"];
اکنون که با اجزای تشکیل دهندهی LINQ to JSON آشنا شدیم، مثال ذیل را درنظر بگیرید:
var array = @"[ { 'prop1': 'value1', 'prop2': 'value2' }, { 'prop1': 'test1', 'prop2': 'test2' } ]"; var objects = JArray.Parse(array); var obj1 = objects.FirstOrDefault(token => (string) token["prop1"] == "value1");
var values = objects.OrderBy(token => (string) token["prop1"]) .Select(token => new {Value = (string) token["prop2"]}) .ToList();
var array = @"[ { 'prop1': 'value1', 'prop2': [1,2] }, { 'prop1': 'test1', 'prop2': [1,2,3] } ]"; var objects = JArray.Parse(array); var objectContaining3 = objects.Where(token => token["prop2"].Any(v => (int)v == 3)).ToList();
Reflector 10 منتشر شد
What's new in this release?
- Full C#7.0 support
- .NET Core support
- بررسی ویرویس Stuxnet | www.idevcenter.com
- تایپ متن فارسی در Gmail | googlepersianblog.blogspot.com
- خصوصیات جدید در ASP.NET 4.5 - خصوصیت HTML Editor Smart Tasks and Event Handler Generation | mojtabasahraei.blogfa.com
- دیتابیس رنگی | www.dotnetdev.info
- شما مرغابی هستید یا عقاب؟ | hrnews.blogfa.com
- نقد فیلم "one flew over the cuckoos nest - دیوانه از قفس پرید( پرواز بر فراز آشیانه فاخته)" | filmhafteh.blogfa.com
- Addressing Visual Studio performance | blogs.msdn.com
- BUILD conference–day 3 | lostechies.com
- BUILD: WinRT, Silverlight, WPF, XAML | www.japf.fr
- C# and Visual Basic on the WinRT API | www.infoq.com
- Multi Monitor in VS.NET 2010 & SQL Server 2011 | www.nikamooz.com
- My List of Must-See Build 2011 Videos | 10rem.net
- New CSS editor features in Visual Studio 11 Developer Preview | blogs.msdn.com
- New Tools and New Content - ASP.NET, Visual Studio 11 Web and .NET 4.5 Developer Preview (with commentary) | www.hanselman.com
- Prototype xUnit.net Visual Studio 11 Unit Testing Plugin | bradwilson.typepad.com
- RIA Services updates for //build | feeds.jeffhandley.com
- Using WinRT from .NET | ermau.com
- Windows 8 and WinRT: Links, News and Resources | ajlopez.wordpress.com
- Windows 8 Replaces the Win32 API | www.infoq.com
- Windows 8: What you Need to Know | csharperimage.jeremylikness.com
- WinRT: An Object Orientated Replacement for Win32 | www.infoq.com
public void MsInteropReplace(Microsoft.Office.Interop.Word.Application doc, object findText, object replaceWithText) { object matchCase = false; object matchWholeWord = true; object matchWildCards = false; object matchSoundsLike = false; object matchAllWordForms = false; object forward = true; object format = false; object matchKashida = false; object matchDiacritics = false; object matchAlefHamza = false; object matchControl = false; object read_only = false; object visible = true; object replace = 2; object wrap = 1; //execute find and replace doc.Selection.Find.Execute(ref findText, ref matchCase, ref matchWholeWord, ref matchWildCards, ref matchSoundsLike, ref matchAllWordForms, ref forward, ref wrap, ref format, ref replaceWithText, ref replace, ref matchKashida, ref matchDiacritics, ref matchAlefHamza, ref matchControl); }
private static void MsInteropReplace2() { var doc = new Microsoft.Office.Interop.Word.Application().Documents.Open(@"D:\temp\te1.docx"); doc.Content.Find.Execute("@levelOrder", false, true, false, false, false, true, 1, false, "12345", 2, false, false, false, false); object missing = System.Reflection.Missing.Value; doc.SaveAs(@"D:\temp\out.docx", ref missing, ref missing, ref missing, ref missing , ref missing, ref missing, ref missing, ref missing, ref missing, ref missing , ref missing, ref missing, ref missing, ref missing, ref missing); }
که هر دو مورد را در stackoverflow میتوانید پیدا کنید. به شخصه از این مورد برای replace کردن مقادیر در یک فایل template.docx استفاده میکردم؛ ولی بعد از مدتی فهمیدم که Footerها و Header را نمیتواند پردازش کند. کد زیر در تمامی قسمتهایی که در یک فایل word میتوان متغیر تعریف کرد را گشته و عمل پر کردن مقادیر را بر روی فایل نمونه، انجام میدهد و شامل سه متد ذیل است:
private static void repAll() { object Missing = System.Reflection.Missing.Value; Application app = null; Microsoft.Office.Interop.Word.Document doc = null; try { app = new Microsoft.Office.Interop.Word.Application(); doc = app.Documents.Open(@"D:\temp\te1.docx", Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing); FindReplaceAnywhere(app, "@levelOrder", "محرمانه"); doc.SaveAs(@"D:\temp\out.docx", Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing); } finally { try { if (doc != null) ((Microsoft.Office.Interop.Word._Document)doc).Close(true, Missing, Missing); } finally { } if (app != null) ((Microsoft.Office.Interop.Word._Application)app).Quit(true, Missing, Missing); } } private static void searchAndReplaceInStory(Microsoft.Office.Interop.Word.Range rngStory, string strSearch, string strReplace) { rngStory.Find.ClearFormatting(); rngStory.Find.Replacement.ClearFormatting(); rngStory.Find.Text = strSearch; rngStory.Find.Replacement.Text = strReplace; rngStory.Find.Wrap = WdFindWrap.wdFindContinue; object Missing = System.Reflection.Missing.Value; object arg1 = Missing; // Find Pattern object arg2 = Missing; //MatchCase object arg3 = Missing; //MatchWholeWord object arg4 = Missing; //MatchWildcards object arg5 = Missing; //MatchSoundsLike object arg6 = Missing; //MatchAllWordForms object arg7 = Missing; //Forward object arg8 = Missing; //Wrap object arg9 = Missing; //Format object arg10 = Missing; //ReplaceWith object arg11 = WdReplace.wdReplaceAll; //Replace object arg12 = Missing; //MatchKashida object arg13 = Missing; //MatchDiacritics object arg14 = Missing; //MatchAlefHamza object arg15 = Missing; //MatchControl rngStory.Find.Execute(ref arg1, ref arg2, ref arg3, ref arg4, ref arg5, ref arg6, ref arg7, ref arg8, ref arg9, ref arg10, ref arg11, ref arg12, ref arg13, ref arg14, ref arg15); } // Main routine to find text and replace it, // var app = new Microsoft.Office.Interop.Word.Application(); public static void FindReplaceAnywhere(Microsoft.Office.Interop.Word.Application app, string findText, string replaceText) { // http://forums.asp.net/p/1501791/3739871.aspx var doc = app.ActiveDocument; // Fix the skipped blank Header/Footer problem // http://msdn.microsoft.com/en-us/library/aa211923(office.11).aspx Microsoft.Office.Interop.Word.WdStoryType lngJunk = doc.Sections[1].Headers[WdHeaderFooterIndex.wdHeaderFooterPrimary].Range.StoryType; // Iterate through all story types in the current document foreach (Microsoft.Office.Interop.Word.Range rngStory in doc.StoryRanges) { // Iterate through all linked stories var internalRangeStory = rngStory; do { searchAndReplaceInStory(internalRangeStory, findText, replaceText); try { // 6 , 7 , 8 , 9 , 10 , 11 -- http://msdn.microsoft.com/en-us/library/aa211923(office.11).aspx switch (internalRangeStory.StoryType) { case Microsoft.Office.Interop.Word.WdStoryType.wdEvenPagesHeaderStory: // 6 case Microsoft.Office.Interop.Word.WdStoryType.wdPrimaryHeaderStory: // 7 case Microsoft.Office.Interop.Word.WdStoryType.wdEvenPagesFooterStory: // 8 case Microsoft.Office.Interop.Word.WdStoryType.wdPrimaryFooterStory: // 9 case Microsoft.Office.Interop.Word.WdStoryType.wdFirstPageHeaderStory: // 10 case Microsoft.Office.Interop.Word.WdStoryType.wdFirstPageFooterStory: // 11 if (internalRangeStory.ShapeRange.Count > 0) { foreach (Microsoft.Office.Interop.Word.Shape oShp in internalRangeStory.ShapeRange) { if (oShp.TextFrame.HasText != 0) { searchAndReplaceInStory(oShp.TextFrame.TextRange, findText, replaceText); } } } break; default: break; } } catch { // On Error Resume Next } // ON ERROR GOTO 0 -- http://www.harding.edu/fmccown/vbnet_csharp_comparison.html // Get next linked story (if any) internalRangeStory = internalRangeStory.NextStoryRange; } while (internalRangeStory != null); // http://www.harding.edu/fmccown/vbnet_csharp_comparison.html } }
برای تهیه pdf نیز میتوانید به کد زیر مراجعه کنید:
public static void getFileDocxInPdf() { // Create a new Microsoft Word application object Microsoft.Office.Interop.Word.Application word = new Microsoft.Office.Interop.Word.Application(); // C# doesn't have optional arguments so we'll need a dummy value object oMissing = System.Reflection.Missing.Value; // Get list of Word files in specified directory DirectoryInfo dirInfo = new DirectoryInfo(@"D:\temp"); FileInfo[] wordFiles = dirInfo.GetFiles("*.docx"); word.Visible = false; word.ScreenUpdating = false; foreach (FileInfo wordFile in wordFiles) { // Cast as Object for word Open method Object filename = (Object)wordFile.FullName; // Use the dummy value as a placeholder for optional arguments Microsoft.Office.Interop.Word.Document doc = word.Documents.Open(ref filename, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing); doc.Activate(); object outputFileName = wordFile.FullName.Replace(".docx", ".pdf"); object fileFormat = WdSaveFormat.wdFormatPDF; // Save document into PDF Format doc.SaveAs(ref outputFileName, ref fileFormat, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing); // Close the Word document, but leave the Word application open. // doc has to be cast to type _Document so that it will find the // correct Close method. object saveChanges = WdSaveOptions.wdDoNotSaveChanges; ((_Document)doc).Close(ref saveChanges, ref oMissing, ref oMissing); doc = null; } // word has to be cast to type _Application so that it will find // the correct Quit method. ((_Application)word).Quit(ref oMissing, ref oMissing, ref oMissing); word = null; }
Visual Studio 2019 RC منتشر شد
- Team Explorer - Changes: panel not sizing to the Team Explorer window.
- Visual Studio 2017 SQL Server Object Explorer server list not persisting .
- intellisense problems with linux-x64 mode.
- Index was out of range. Must be non-negative and less than the size of the collection.Parameter name: index.
- Visual Studio 2017 UNDO does not work/stops working (reported AGAIN!!!!).
- Intellisense not working for files created under WSL.
- Intellisense error: C++11 static constexpr member initialization causes "member may not be initialized".
- VS doesn't restore windows position when switching in/out of debug.
- Third party toolbox items are reloaded every time VS2019 Preview 2.2 is started.
- 'Set as StartUp Project' crashes the IDE after updating to VS2019 Preview Release 3.
- Visual Studio 2019 building Visual Studio 2017 C++ projects fail.
- Fixed Toolbox refresh issue.
- Toolbox controls are making vssettings file too big.
- SSDT: Fix for Login failed errors when performing a New Data comparison function .
- SSDT: Fix for Source is Unavailable error when performing Schema Compare .
- SSDT: Fix for Schema Compare Generate Script does not generate script .
- SSDT: Fix to improve performance of loading solutions with multiple projects.
- SSDT: Fix for SQL files not always being deleted when performing a Schema Compare between a database and a project and a delete table is executed subsequently.
- SSDT: Accessibility fixes to improve narration capabilities.
- SSDT: Replaced older sqlncli driver with new Microsoft ODBC Driver for SQL Server.
شروع به کار با RavenDB
- مروری بر مفاهیم مقدماتی NoSQL
- ردهها و انواع مختلف بانکهای اطلاعاتی NoSQL
- چه زمانی بهتر است از بانکهای اطلاعاتی NoSQL استفاده کرد و چه زمانی خیر؟
لطفا یکبار این پیشنیازها را پیش از شروع به کار مطالعه نمائید؛ چون بسیاری از مفاهیم پایهای و اصطلاحات مرسوم دنیای NoSQL در این سه قسمت بررسی شدهاند و از تکرار مجدد آنها در اینجا صرفنظر خواهد شد.
RavenDB چیست؟
RavenDB یک بانک اطلاعاتی سورس باز NoSQL سندگرای تهیه شده با دات نت است. ساختار کلی بانکهای اطلاعاتی NoSQL سندگرا، از لحاظ نحوه ذخیره سازی اطلاعات، با بانکهای اطلاعاتی رابطهای متداول، کاملا متفاوت است. در اینگونه بانکهای اطلاعاتی، رکوردهای اطلاعات، به صورت اشیاء JSON ذخیره میشوند. اشیاء JSON یا JavaScript Object Notation بسیار شبیه به anonymous objects سی شارپ هستند. JSON روشی است که توسط آن JavaScript اشیاء خود را معرفی و ذخیره میکند. به عنوان رقیبی برای XML مطرح است؛ نسبت به XML اندکی فشردهتر بوده و عموما دارای اسکیمای خاصی نیست و در بسیاری از اوقات تفسیر المانهای آن به مصرف کننده واگذار میشود.
در JSON عموما سه نوع المان پایه مشاهده میشوند:
- اشیاء که به صورت {object} تعریف میشوند.
- مقادیر "key":"value" که شبیه به نام خواص و مقادیر آنها در دات نت هستند.
- و آرایهها به صورت [array]
همچنین ترکیبی از این سه عنصر یاد شده نیز همواره میسر است. برای مثال، یک key مشخص میتواند دارای مقداری حاوی یک آرایه یا شیء نیز باشد.
JSON: JavaScript Object Notation document :{ key: "Value", another_key: { name: "embedded object" }, some_date: new Date(), some_number: 12 } C# anonymous object var Document = new { Key= "Value", AnotherKey= new { Name = "embedded object" }, SomeDate = DateTime.Now(), SomeNumber = 12 };
یک نکته مهم
اگر پیشنیازهای بحث را مطالعه کرده باشید، حتما بارها با این جمله که دنیای NoSQL از تراکنشها پشتیبانی نمیکند، برخورد داشتهاید. این مطلب در مورد RavenDB صادق نیست و این بانک اطلاعاتی NoSQL خاص، از تراکنشها پشتیبانی میکند. RavenDB در Document store خود ACID عملکرده و از تراکنشها پشتیبانی میکند. اما تهیه ایندکسهای آن بر مبنای مفهوم عاقبت یک دست شدن عمل میکند.
مجوز استفاده از RavenDB
هرچند مجموعه سرور و کلاینت RavenDB سورس باز هستند، اما این مورد به معنای رایگان بودن آن نیست. مجوز استفاده از RavenDB نوع خاصی به نام AGPL است. به این معنا که یا کل کار مشتق شده خود را باید به صورت رایگان و سورس باز ارائه دهید و یا اینکه مجوز استفاده از آنرا برای کارهای تجاری بسته خود خریداری نمائید. نسخه استاندارد آن نزدیک به هزار دلار است و نسخه سازمانی آن نزدیک به 2800 دلار به ازای هر سرور.
شروع به نوشتن اولین برنامه کار با RavenDB
ابتدا یک پروژه کنسول ساده را آغاز کنید. سپس کلاسهای مدل زیر را به آن اضافه نمائید:
using System.Collections.Generic; namespace RavenDBSample01.Models { public class Question { public string By { set; get; } public string Title { set; get; } public string Content { set; get; } public List<Comment> Comments { set; get; } public List<Answer> Answers { set; get; } public Question() { Comments = new List<Comment>(); Answers = new List<Answer>(); } } } namespace RavenDBSample01.Models { public class Comment { public string By { set; get; } public string Content { set; get; } } } namespace RavenDBSample01.Models { public class Answer { public string By { set; get; } public string Content { set; get; } } }
PM> Install-Package RavenDB.Client PM> Install-Package RavenDB.Server
اکنون به پوشه packages\RavenDB.Server.2.5.2700\tools مراجعه کرده و برنامه Raven.Server.exe را اجرا کنید تا سرور RavenDB شروع به کار کند. این سرور به صورت پیش فرض بر روی پورت 8080 اجرا میشود. از این جهت که در RavenDB نیز همانند سایر Document Stores مطرح، امکان دسترسی به اسناد از طریق REST API و Urlها وجود دارد.
البته لازم به ذکر است که RavenDB در 4 حالت برنامه کنسول (همین سرور فوق)، نصب به عنوان یک سرویس ویندوز NT، هاست شدن در IIS و حالت مدفون شده یا Embedded قابل استفاده است.
خوب؛ همین اندازه برای برپایی اولیه RavenDB کفایت میکند.
using Raven.Client.Document; using RavenDBSample01.Models; namespace RavenDBSample01 { class Program { static void Main(string[] args) { using (var store = new DocumentStore { Url = "http://localhost:8080" }.Initialize()) { using (var session = store.OpenSession()) { session.Store(new Question { By = "users/Vahid", Title = "Raven Intro", Content = "Test...." }); session.SaveChanges(); } } } } }
کار با ایجاد یک DocumentStore که به آدرس سرور اشاره میکند و کار مدیریت اتصالات را برعهده دارد، شروع خواهد شد. اگر نمیخواهید Url را درون کدهای برنامه مقدار دهی کنید، میتوان از فایل کانفیگ برنامه نیز برای این منظور کمک گرفت:
<connectionStrings> <add name="ravenDB" connectionString="Url=http://localhost:8080"/> </connectionStrings>
سپس با ایجاد Session در حقیقت یک Unit of work آغاز میشود که درون آن میتوان انواع و اقسام دستورات را صادر نمود و سپس در پایان کار، با فراخوانی SaveChanges، این اعمال ذخیره میگردند. در RavenDB یک سشن باید طول عمری کوتاه داشته باشد و اگر تعداد عملیاتی که در آن صادر کردهاید، زیاد است با خطای زیر متوقف خواهید شد:
The maximum number of requests (30) allowed for this session has been reached.
در یک برنامه واقعی، ایجاد DocumentStore یکبار در آغاز کار برنامه باید انجام گردد. اما هر سشن یا هر واحد کاری آن، به ازای تراکنشهای مختلفی که باید صورت گیرند، بر روی این DocumentStore، ایجاد شده و سپس بسته خواهند شد. برای مثال در یک برنامه ASP.NET، در فایل Global.asax در زمان آغاز برنامه، کار ایجاد DocumentStore انجام شده و سپس به ازای هر درخواست رسیده، یک سشن RavenDB ایجاد و در پایان درخواست، این سشن آزاد خواهد شد.
برنامه را اجرا کنید، سپس به کنسول سرور RavenDB که پیشتر آنرا اجرا نمودیم مراجعه نمائید تا نمایی از عملیات انجام شده را بتوان مشاهده کرد:
Raven is ready to process requests. Build 2700, Version 2.5.0 / 6dce79a Server started in 14,438 ms Data directory: D:\Prog\RavenDBSample01\packages\RavenDB.Server.2.5.2700\tools\Database\System HostName: <any> Port: 8080, Storage: Esent Server Url: http://localhost:8080/ Available commands: cls, reset, gc, q Request # 1: GET - 514 ms - <system> - 404 - /docs/Raven/Replication/Destinations Request # 2: GET - 763 ms - <system> - 200 - /queries/?&id=Raven%2FHilo%2Fquestions&id=Raven%2FServerPrefixForHilo Request # 3: PUT - 185 ms - <system> - 201 - /docs/Raven/Hilo/questions Request # 4: POST - 103 ms - <system> - 200 - /bulk_docs PUT questions/1
عملیات PUT حتما نیاز به یک Id از پیش مشخص دارد و این Id، پیشتر در سطری که Hilo در آن ذکر شده (یکی از الگوریتمهای محاسبه Id در RavenDB)، محاسبه گردیده است. برای نمونه در اینجا الگوریتم Hilo مقدار "questions/1" را به عنوان Id محاسبه شده بازگشت داده است.
در سطری که عملیات Post به آدرس bulk_docs سرور ارسال گردیده است، کار ارسال یکباره چندین شیء JSON برای کاهش رفت و برگشتها به سرور انجام میشود.
و برای کوئری گرفتن مقدماتی از اطلاعات ثبت شده میتوان نوشت:
using (var session = store.OpenSession()) { var question1 = session.Load<Question>("questions/1"); Console.WriteLine(question1.By); }
نگاهی به بانک اطلاعاتی ایجاد شده
در همین حال که سرور RavenDB در حال اجرا است، مرورگر دلخواه خود را گشوده و سپس آدرس http://localhost:8080 را وارد نمائید. بلافاصله، کنسول مدیریتی تحت وب این بانک اطلاعاتی که با سیلورلایت نوشته شده است، ظاهر خواهد شد:
و اگر بر روی هر سطر اطلاعات دوبار کلیک کنید، به معادل JSON آن نیز خواهید رسید:
اینبار برنامه را به صورت زیر تغییر دهید تا روابط بین کلاسها را نیز پیاده سازی کند:
using System; using Raven.Client.Document; using RavenDBSample01.Models; namespace RavenDBSample01 { class Program { static void Main(string[] args) { using (var store = new DocumentStore { Url = "http://localhost:8080" }.Initialize()) { using (var session = store.OpenSession()) { var question = new Question { By = "users/Vahid", Title = "Raven Intro", Content = "Test...." }; question.Answers.Add(new Answer { By = "users/Farid", Content = "بررسی میشود" }); session.Store(question); session.SaveChanges(); } using (var session = store.OpenSession()) { var question1 = session.Load<Question>("questions/1"); Console.WriteLine(question1.By); } } } } }
اگر برنامه فوق را اجرا کرده و به ساختار اطلاعات ذخیره شده نگاهی بیندازیم به شکل زیر خواهیم رسید:
نکته جالبی که در اینجا وجود دارد، عدم نیاز به join نویسی برای دریافت اطلاعات وابسته به یک شیء است. اگر سؤالی وجود دارد، پاسخهای به آن و یا سایر نظرات، یکجا داخل همان سؤال ذخیره میشوند و به این ترتیب سرعت دسترسی نهایی به اطلاعات بیشتر شده و همچنین قفل گذاری روی سایر اسناد کمتر. این مساله نیز به ذات NoSQL و یا غیر رابطهای RavenDB بر میگردد. در بانکهای اطلاعاتی NoSQL، مفاهیمی مانند کلیدهای خارجی، JOIN بین جداول و امثال آن وجود خارجی ندارند. برای نمونه اگر به کلاسهای مدلهای برنامه دقت کرده باشید، خبری از وجود Id در آنها نیست. RavenDB یک Document store است و نه یک Relation store. در اینجا کل درخت تو در توی خواص یک شیء دریافت و به صورت یک سند ذخیره میشود. به حاصل این نوع عملیات در دنیای بانکهای اطلاعاتی رابطهای، Denormalized data هم گفته میشود.
البته میتوان به کلاسهای تعریف شده خاصیت رشتهای Id را نیز اضافه کرد. در این حالت برای مثال در حالت فراخوانی متد Load، این خاصیت رشتهای، با Id تولید شده توسط RavenDB مانند "questions/1" مقدار دهی میشود. اما از این Id برای تعریف ارجاعات به سؤالات و پاسخهای متناظر استفاده نخواهد شد؛ چون تمام آنها جزو یک سند بوده و داخل آن قرار میگیرند.