شمارهی 12.0 مربوط به VS 2013 هست و شمارهی 14.0 مربوط به VS 2015.
C# 6 - Expression-Bodied Members
مجموعه نکاتی از VS Code
نتایج نظرسنجی State of JS 2020
23,765 people from 137 countries took part in the recent State of JS survey and while there are some common criticisms of the project, the results are nonetheless interesting and we’ll be digging into some in forthcoming issues. Standouts include:
- Svelte took the top frontend framework crown from React for developer satisfaction.
- Testing Library jumped straight to #1 for testing libraries.
- More developers than ever are producing PWAs or using WebAssembly.
- 86% of respondents are using VS Code to work on their code.
- Custom tasks with default contextType via tasks.vs.json are broken in Open Folder.
- No snapshot created for C++ native code in Memory Usage tool in the Diagnostic Tools window while debugging.
- Crash in VS 16.1.0 when pressing the link "search online".
- Fixed the issue where sometimes certain features (i.e. Find All References) in LiveShare guest session do not work.
- Fixed an issue where the devenv.exe process could hang around after shutdown of Visual Studio for up to 30 seconds.
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0"> <edmx:DataServices> <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="OwinAspNetCore.Models"> <EntityType Name="Product"> <Key> <PropertyRef Name="Id"/> </Key> <Property Name="Id" Type="Edm.Int32" Nullable="false"/> <Property Name="Name" Type="Edm.String"/> <Property Name="Price" Type="Edm.Decimal" Nullable="false"/> </EntityType> </Schema> <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Default"> <Function Name="TestFunction" IsBound="true"> <Parameter Name="bindingParameter" Type="Collection(OwinAspNetCore.Models.Product)"/> <Parameter Name="Val" Type="Edm.Int32" Nullable="false"/> <Parameter Name="Name" Type="Edm.String"/> <ReturnType Type="Edm.Int32" Nullable="false"/> </Function> <EntityContainer Name="Container"> <EntitySet Name="Products" EntityType="OwinAspNetCore.Models.Product"/> </EntityContainer> </Schema> </edmx:DataServices> </edmx:Edmx>
string postUrl = "http://localhost:port/...."; HttpClient client = new HttpClient(); var response = client.PostAsync(postUrl, new StringContent(JsonConvert.SerializeObject(new { Rating = 5 }), Encoding.UTF8, "application/json")).Result;
آن را نصب نموده و بعد از تکمیل شدن، visual studio را restart کنید.
پروژهی console خود را باز کرده و از طریق Add -> new item، آیتم OData client را جستجو کرده و با نام ProductClient.tt آن را تولید نمایید (نام آن اختیاری است):
فایل ProductClient.tt را که یک T4 code generator میباشد، باز کرده و مقدار ثابت MetadataDocumentUri را به آدرس سرویس odata خود تغییر دهید:
public const string MetadataDocumentUri = "http://localhost:port/odata/";
روی این آیتم کلیک راست و گزینهی Run Custom tool را انتخاب نمایید. این تمام کاری است که نیاز به انجام دادن دارید.
حال فایل Program.cs را باز کرده و آنرا اینگونه تغییر دهید:
using ConsoleApplication1.OwinAspNetCore.Models; using System; using System.Linq; namespace ConsoleApplication1 { public class Program { static void Main(string[] args) { Uri uri = new Uri("http://localhost:24977/odata"); //var context = new Default.Container(uri); var context = new TestNameSpace.TestNameSpace(uri); //get var products = context.Products.Where(pr => pr.Name.Contains("a")) .Take(1).Select(pr => new { Firstname = pr.Name, PriceValue = pr.Price }).ToList(); //add context.AddToProducts(new Product() { Name = "Name1", Price = 123 }); //update Product p = context.Products.First(); p.Name = "changed"; context.UpdateObject(p); //delete context.DeleteObject(context.Products.Last()); //commit context.SaveChanges(); } } }
مشاهده میفرمایید که همهی عملیاتهای لازم برای CRUD، به شرط اینکه در سمت سرور طراحی شده باشند، به راحتی از سمت کلاینت قابل فراخوانی خواهند بود.
از این ویژگی فوق العاده میتوان حتی در کلاینتها جاوااسکریپتی نیز استفاده کرد. فرض کنید نرم افزار تحت وبی را با استفاده از jquery یا angularjs طراحی کردهاید. قاعدتا فراخوانی درخواستهای شما به سمت سرور، چیزی شبیه به این خواهد بود:
//angularjs $http.get("/products/get", {Name: "Test", Company: "Test"}) .then(function(response) { console.log(response.data); }); //jquery $.get("/products/get", {Name: "Test", Company: "Test"}, function(data, status){ console.log("Data: " + data); });
با استفاده از odata و typescript و یک library مربوط به odata client در سمت کلاینت، نرم افزار شما بجای موارد، بالا چیزی شبیه به مثال زیر خواهد بود (با همراه داشتن strongly typed و intellisense کامل)
let product1 = await context.products.filter(c => c.Name.contains("Ali")).toArray(); let product2 = await context.products.getSomeFunction(1, 'Test'); context.product.add({Name: 'Test'} as Product); await context.saveChanges()
در مقالههای آتی به ویژگیهای بیشتری از Odata خواهیم پرداخت.
NOSQL قسمت سوم
در مطلب قبلی با نوع اول پایگاههایداده NoSQL یعنی Key/Value Store آشنا شدیم و در این مطلب به معرفی دسته دوم یعنی Document Database خواهیم پرداخت.
در این نوع پایگاه داده ، دادهها مانند نوع اول در قالب
کلید/مقدار ذخیره میشوند و بازگردانی مقادیر نیز دقیقا مشابه نوع اول یعنی Key/Value Store بر اساس کلید میباشد. اما
تفاوت این سیستم با نوع اول در دستهبندی دادههای مرتبط با یکدیگر در قالب یک Document میباشد. سعی کردم در این مطلب با ذکر مثال مطالب را شفافتر بیان کنم:
به عنوان مثال اگر بخواهیم جداول مربوط به پستهای یک سیستم CMS را بصورت رابطهای پیاده کنیم ، یکی از سادهترین حالات پایه برای پستهای این سیستم در حالت نرمال به صورت زیر میباشد.
جداول واضح بوده و نیازی به توضیح ندارد ، حال نحوهی ذخیرهسازی دادهها در سیستم Document Database برای چنین مثالی را بررسی میکنیم:
{ _id: ObjectID(‘4bf9e8e17cef4644108761bb’), Title: ‘NoSQL Part3’, url: ‘https://www.dntips.ir/yyy/xxxx’, author: ‘hamid samani’, tags: [‘databases’,’mongoDB’], comments:[ {user: ‘unknown user’, text:’unknown test’ }, {user:unknown user2’, text:’unknown text2 } ] }
همانگونه که مشاهد میکنید نحوهی ذخیرهسازی دادهها بسیار با سیستم رابطهای متفاوت میباشد ، با جمعبندی تفاوت نحوهی نگهداری دادهها در این سیستم و RDBMS و بررسی این سیستم نکات اصلی به شرح زیر میباشند:
۱-فرمت ذخیره سازی دادهها مشابه فرمت JSON میباشد.
۲-به مجموعه دادههای مرتبط به یکدیگر Document گفته میشود.
۳-در این سیستم JOIN ها وجود ندارند و دادههای مرتبط کنار یکدیگر قرار میگیرند ، و یا به تعریف دقیقتر دادهها در یک داکیومنت اصلی Embed میشوند.
به عنوان مثال در اینجا مقدار commentها برابر با آرایهای از Documentها میباشد.
۴-مقادیر میتوانند بصورت آرایه نیز در نظر گرفته شوند.
۵-در سیستمهای RDBMS در صورتی که بخواهیم از وجود JOINها صرفنظر کنیم. به عدم توانایی در نرمالسازی برخواهیم خورد که یکی از معایب عدم نرمالسازی وجود مقادیر Null در جداول میباشد؛ اما در این سیستم به دلیل Schema free بودن میتوان ساختارهای متفاوت برای Documentها در نظر گرفت.
به عنوان مثال برای یک پست میتوان مقدار n کامنت تعریف کرد و برای پست دیگر هیچ کامنتی تعریف نکرد.
۶-در این سیستم اصولا نیازی به تعریف ساختار از قبل موجود
نمیباشد و به محض اعلان دستور قرار دادن دادهها در پایگاهداده ساختار متناسب
ایجاد میشود.
با مقایسه دستورات CRUD در هر دو نوع پایگاه داده با نحوهی کوئری گرفتن از Document Database آشنا میشویم:
در SQL برای ایجاد جدول خواهیم داشت:
CREATE TABLE posts ( id INT NOT NULL AUTO_INCREMENT, author_id INT NOT NULL, url VARCHAR(50), PRIMARY KEY (id) )
دستور فوق در Document Database معادل است با:
db.posts.insert({id: “256” , author_id:”546”,url:"http://example.com/xxx"}) // با قرار دادن مقدار نوع ساختار مشخص میشود
در SQL جهت خواندن خواهیم داشت:
SELECT * from posts WHERE author_id > 100
db.posts.find({author_id:{$gt:”1000”}})
در SQL جهت بروزرسانی داریم:
UPDATE posts SET author_id= "123"
db.posts.update({ $set: { author_id: "123" }})
در SQL جهت حذف خواهیم داشت:
DELETE FROM posts WHERE author_id= "654"
که معادل است با:
db.posts.remove( { author_id: "654" } )
همانگونه که مشاهده میفرمایید نوشتن کوئری برای این پایگاه داده ساده بوده و زبان آن نیز بر پایه جاوا اسکریپت میباشد که برای اکثر برنامهنویسان قابل درک است.
تاکنون توسط شرکتهای مختلف پیادهسازیهای مختلفی از این سیستم انجام شده است که از مهمترین و پر استفادهترین آنها میتوان به موارد زیر اشاره کرد:
public static void PrintSum(int a, int b) { Console.WriteLine("Sum of a {0} b {1} is {2}", a, b, a + b); }
Console::WriteLine(string, object, object, object)
روشی برای نمایان ساختن تخصیصهای حافظهی نهان در ویژوال استودیو
اگر از ReSharper استفاده میکنید، افزونهی «Heap Allocations Viewer» آن و یا اگر از VS 2015 و Roslyn استفاده کنید، افزونهی «Roslyn Clr Heap Allocation Analyzer» آن، سبب نمایان شدن allocationهای مخفی میشوند. برای مثال قطعه کد فوق یک چنین نمایشی را پیدا میکند:
در اینجا در ذیل هر سه موردی که عملیات boxing allocation رخ داده، یک خط قرمز کشیده است. یکی از روشهایی که میتواند boxing allocation فوق را حذف کند، بکار گیری متد ToString بر روی مقادیر int است:
همانطور که مشاهده میکنید، اینبار دیگر خبری از خطوط قرمز، ذیل پارامترهای متد Console.WriteLine نیست. باید دقت داشت که ToString نیز سبب تخصیص حافظه میشود، اما اینبار دیگر int32 آن بر روی heap ذخیره نمیگردد. به عبارتی هر دو حالت سبب تخصیص حافظهی یک رشتهی جدید میشوند؛ اما در حالت اول علاوه بر این شیء جدید، شیء int32 نیز بر روی heap ذخیره میگردد.
تشخیص تخصیص اشیاء مخفی با افزونههای Heap Allocations Viewer
نمونهی دیگر پر کاربرد این نوع بهینه سازیها را در مثال ذیل میتوان مشاهده کرد:
public static void PrintA(int a) { Console.WriteLine("a is " + a); }
اینبار یک خط زرد رنگ ظاهر شده به همراه یک خط قرمز رنگ. خط قرمز رنگ را پیشتر بررسی کردیم و علت وجودی آن Boxing allocation ایی است که رخ میدهد. خط زرد رنگ در ذیل + ظاهر شدهاست و عنوان میکند که عملیات جمع زدن رشتهها، سبب تخصیص حافظهی یک شیء جدید میشود. رشتهها در دات نت immutable هستند. به همین جهت هر تغییری در آنها، سبب تخصیص یک شیء جدید میشود. بنابراین در همین مثال ساده، دو تخصیص حافظهی مخفی وجود دارند. مورد جمع زدن را با بکارگیری string.Format و مشکل boxing را با ToString میتوان برطرف کرد:
public static void PrintA(int a) { Console.WriteLine("a is {0}", a.ToString()); }
منابع دیگری که سبب تخصیصهای حافظهی مخفی میشوند
تا اینجا دو مورد از منابع متداول تخصیصهای حافظهی مخفی را بررسی کردیم. اما این لیست شامل موارد ذیل نیز میشود:
1) فراخوانی متدهایی با پارامترهایی از نوع param همیشه سبب تخصیص حافظهای جهت تشکیل یک آرایهی در برگیرندهی پارامترهای ارسالی میشود.
2) متدهایی که پارامتر از نوع IEnumerable دارند:
public static int Sum(IEnumerable<int> list) { var sum = 0; foreach (var number in list) { sum += number; } return sum; }
برای حل این مشکل فقط کافی است IEnumerable را با List تعویض کنید.
3) کار با LINQ نیز سبب تخصیصهای حافظهی قابل توجهی است. برای مثال در کد پایهی Roslyn، برای رسیدن به حداکثر کارآیی، بسیاری از الگوریتمها را با روشهای غیر LINQ پیاده سازی کردهاند. البته برای تیمی مانند Roslyn رسیدن به یک چنین کارآیی جهت رقابت با سایر محصولات مشابه ضروری بودهاست و گرنه در بسیاری از کارهای متداول، استفاده از LINQ به خوانایی هر چه بیشتر کدها کمک شایانی میکند.
برای مطالعهی بیشتر
Roslyn code base – performance lessons - part 2
Unusual Ways of Boosting Up App Performance. Boxing and Collections
On performance in .NET
اولا، اجازه بدهید نگاهی به تنظیم یک دامنه محلی داشته باشیم. زمانیکه شما برنامه محلی را اجرا میکنید IIS Express به صورت محلی، پورتی خاص را به برنامه اختصاص میدهد:
فرض کنید میخواهیم برای سایت خود، درگاه بانک را راه اندازی کنیم. برنامه را به صورت محلی اجرا کرده و زمانیکه قصد ارتباط با بانک را دارید، با پیامی که دامنه شما در سیستم پرداخت بانکی ثبت نشده، مواجه میشوید. در اینجا بانک انتظار دارد که ما از طریق دامنهای که قبلا در سیستم پرداخت بانک ثبت کردهایم برای مثال (www.elemarket.ir) با آن ارتباط برقرار کنیم؛ ولی به دلیل ارتباط به صورت محلی با یکچنین دامنهای (localhost:59395) روبهرو شده و پیغام عدم امکان برقراری ارتباط را میدهد.
حال قصد داریم با تغییر دامنه به صورت سفارشی، این مشکل را برطرف کرده، تا درحقیقت قبل از قرار دادن سایت بر روی سرور و تست عملیات بتوانیم به صورت محلی نتیجه را دریافت کنیم.
استفاده از Telerik Fiddler برای ایجاد یک دامنهی سفارشی
برای این کار میتوانید از برنامهی سبک وزن Telerik Fiddler استفاده کنید و تنها کافیست به قسمت Tools>Host برنامه بروید و آدرس محلی برنامه (localhost:59395 ) و آدرس دامنهی مورد نظر را وارد کنید تا برنامه هم به صورت local و هم توسط یک دامنهی سفارشی، در دسترس باشد. برنامهی Fiddler را باز نگه داشته و به ویژوال استودیو بازگرید.
در نهایت، پیکربندی IIS Express خود را با اتصالهای جدید به روز کنید. شما معمولا میتوانید پیکربندی IIS Express Application خود را در این مسیر پیدا کنید .
C: \ Users \ YOUR_USERNAME \ Documents \ IISExpress \ config / applicationhost.config
در دسترس است.
فایل را باز کرده و گره <sites> را جستجو کنید. شما باید بتوانید درخواست خود را در فهرست سایتها مشاهده کنید. ما قصد داریم HTTP binding را با تغییر localhost به نام دامنهی سفارشی خود بهروز رسانی کنیم. در اینجا HTTP binding به صورت پیشفرض بر روی localhost میباشد:
پس از تغییر localhost و ذخیره کردن تنظیمات، بررسی کنید تا IIS Express در حال اجرا نباشد. حال برنامه را اجرا کنید.
نکته: اگر هنگام اجرای برنامه با خطای
“Unable to create the virtual directory. You must use specify ‘localhost’ for the server name”
" Invalid URL:the hostame could not be parsed "
بعد از اجرای برنامه به طور پیشفرض بر روی همان پورت localhost اجرا شده، حال به دامنهی مورد نظر که ایجاد کردهاید بروید:
در اینجا بعد از تلاش برای ارتباط با بانک، دیگر با پیام «دامنه شما در سیستم پرداخت بانکی ثبت نشدهاست» مواجه نشده و با موفقیت امکان تست برنامه را داریم:
public class HandleConcurrencyExceptionAttribute : FilterAttribute, IExceptionFilter { private PropertyMatchingMode _propertyMatchingMode; /// <summary> /// This defines when the concurrencyexception happens, /// </summary> public enum PropertyMatchingMode { /// <summary> /// Uses only the field names in the model to check against the entity. This option is best when you are using /// View Models with limited fields as opposed to an entity that has many fields. The ViewModel (or model) field names will /// be used to check current posted values vs. db values on the entity itself. /// </summary> UseViewModelNamesToCheckEntity = 0, /// <summary> /// Use any non-matching value fields on the entity (except timestamp fields) to add errors to the ModelState. /// </summary> UseEntityFieldsOnly = 1, /// <summary> /// Tells the filter to not attempt to add field differences to the model state. /// This means the end user will not see the specifics of which fields caused issues /// </summary> DontDisplayFieldClashes = 2 } public HandleConcurrencyExceptionAttribute() { _propertyMatchingMode = PropertyMatchingMode.UseViewModelNamesToCheckEntity; } public HandleConcurrencyExceptionAttribute(PropertyMatchingMode propertyMatchingMode) { _propertyMatchingMode = propertyMatchingMode; } /// <summary> /// The main method, called by the mvc runtime when an exception has occured. /// This must be added as a global filter, or as an attribute on a class or action method. /// </summary> /// <param name="filterContext"></param> public void OnException(ExceptionContext filterContext) { if (!filterContext.ExceptionHandled && filterContext.Exception is DbUpdateConcurrencyException) { //Get original and current entity values DbUpdateConcurrencyException ex = (DbUpdateConcurrencyException)filterContext.Exception; var entry = ex.Entries.Single(); //problems with ef4.1/4.2 here because of context/model in different projects. //var databaseValues = entry.CurrentValues.Clone().ToObject(); //var clientValues = entry.Entity; //So - if using EF 4.1/4.2 you may use this workaround var clientValues = entry.CurrentValues.Clone().ToObject(); entry.Reload(); var databaseValues = entry.CurrentValues.ToObject(); List<string> propertyNames; filterContext.Controller.ViewData.ModelState.AddModelError(string.Empty, "The record you attempted to edit " + "was modified by another user after you got the original value. The " + "edit operation was canceled and the current values in the database " + "have been displayed. If you still want to edit this record, click " + "the Save button again to cause your changes to be the current saved values."); PropertyInfo[] entityFromDbProperties = databaseValues.GetType().GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance); if (_propertyMatchingMode == PropertyMatchingMode.UseViewModelNamesToCheckEntity) { //We dont have access to the model here on an exception. Get the field names from modelstate: propertyNames = filterContext.Controller.ViewData.ModelState.Keys.ToList(); } else if (_propertyMatchingMode == PropertyMatchingMode.UseEntityFieldsOnly) { propertyNames = databaseValues.GetType().GetProperties(BindingFlags.Public).Select(o => o.Name).ToList(); } else { filterContext.ExceptionHandled = true; UpdateTimestampField(filterContext, entityFromDbProperties, databaseValues); filterContext.Result = new ViewResult() { ViewData = filterContext.Controller.ViewData }; return; } UpdateTimestampField(filterContext, entityFromDbProperties, databaseValues); //Get all public properties of the entity that have names matching those in our modelstate. foreach (var propertyInfo in entityFromDbProperties) { //If this value is not in the ModelState values, don't compare it as we don't want //to attempt to emit model errors for fields that don't exist. //Compare db value to the current value from the entity we posted. if (propertyNames.Contains(propertyInfo.Name)) { if (propertyInfo.GetValue(databaseValues, null) != propertyInfo.GetValue(clientValues, null)) { var currentValue = propertyInfo.GetValue(databaseValues, null); if (currentValue == null || string.IsNullOrEmpty(currentValue.ToString())) { currentValue = "Empty"; } filterContext.Controller.ViewData.ModelState.AddModelError(propertyInfo.Name, "Current value: " + currentValue); } } //TODO: hmm.... how can we only check values applicable to the model/modelstate rather than the entity we saved? //The problem here is we may only have a few fields used in the viewmodel, but many in the entity //so we could have a problem here with that. //object o = propertyInfo.GetValue(myObject, null); } filterContext.ExceptionHandled = true; filterContext.Result = new ViewResult() { ViewData = filterContext.Controller.ViewData }; } }