یک ویرایشگر متفاوت با خروجی JSON
Editor.js is a block-styled editor for rich media stories. It outputs clean data in JSON instead of heavy HTML-markup. And more important thing is that Editor.js is designed to be API extendable and pluggable.
نگاهی به ویژگیهای EF Core 6x
ریلیز شد Orleans 7.0
The .NET 7 release marks an exciting milestone in many ways, but one in particular that’s exciting for ASP.NET developers building distributed apps or apps designed to be cloud native and ready for dynamic horizontal scale out is the addition of the Orleans team to the broader .NET team. Bringing Orleans and ASP.NET Core closer together has led to some exciting ideas for the future of how we blend Orleans into the ASP.NET toolchain, and coupled with the huge advances in performance throughout .NET 7 are improvements to Orleans 7 that bring over 150% improvements to some areas of the Orleans toolchain. This post will introduce you to some of the new features in Orleans 7.
CREATE TABLE Users ( id INT NOT NULL AUTO_INCREMENT, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, gender ENUM('Male', 'Female') NOT NULL, PRIMARY KEY (id) ); CREATE TABLE BlogPosts ( id INT NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL, user_id INT NOT NULL, KEY user_id (user_id), CONSTRAINT `blogposts_ibfk_1` FOREIGN KEY (user_id) REFERENCES Users (id), PRIMARY KEY (id) )
SELECT U.id, CONCAT_WS(' ', U.first_name, U.last_name) AS FullName, BP.title FROM Users AS U JOIN BlogPosts AS BP ON U.id = BP.user_id;
تبدیل یک ساختار Relational به JSON
میتوانیم خروجی موردنظر را در قالب JSON نیز کوئری بگیریم:
SELECT JSON_OBJECT('id', U.id, 'user', CONCAT_WS(' ', U.first_name, U.last_name), 'title', BP.title) FROM Users AS U JOIN BlogPosts AS BP ON U.id = BP.user_id;
به عنوان مثال میتوانیم لیست کاربران را به همراه بلاگ پستهایشان، اینگونه کوئری بگیریم:
SELECT JSON_OBJECT('user', CONCAT_WS(' ', U.first_name, U.last_name), 'blog_posts', JSON_ARRAYAGG(JSON_OBJECT('id', BP.id, 'title', BP.title))) AS JSON FROM Users AS U JOIN BlogPosts AS BP ON U.id = BP.user_id GROUP BY U.id;
خروجی کوئری فوق اینچنین خواهد بود:
تبدیل یک ساختار JSON به Relational
همچنین میتوانیم یک ساختار JSON را به صورت Relational تبدیل کنیم. اینکار توسط تابع JSON_TABLE قابل انجام است. کاری که این تابع انجام میدهد، ایجاد یک جدول موقت و کپی کردن دیتای موردنظر درون آن است. فرض کنید ساختار JSON زیر را به اینصورت درون دیتابیس ذخیره کردهایم:
{ "id": "1", "new": false, "sku": "asdf123", "tag": ["fashion", "men", "jacket", "full sleeve"], "name": "Lorem ipsum jacket", "image": [ "/assets/img/product/fashion/1.jpg", "/assets/img/product/fashion/3.jpg", "/assets/img/product/fashion/6.jpg", "/assets/img/product/fashion/8.jpg", "/assets/img/product/fashion/9.jpg" ], "price": 12.45, "rating": 4, "category": ["fashion", "men"], "discount": 10, "offerEnd": "October 5, 2020 12:11:00", "saleCount": 54, "description": { "fullDescription": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?", "shortDescription": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur." } }
اکنون میخواهیم چنین خروجیای داشته باشیم:
کوئری موردنیاز برای تهیه خروجی فوق اینچنین خواهد بود:
SELECT newTable.* FROM experiments.productMetadata, JSON_TABLE( data, "$" COLUMNS ( id INT PATH "$.id", new CHAR(5) PATH "$.new", sku CHAR(20) PATH "$.sku", price FLOAT PATH "$.price", rating FLOAT PATH "$.rating", name CHAR(255) PATH "$.name", discount FLOAT PATH "$.discount", offerEnd TEXT PATH "$.offerEnd", saleCount INT PATH "$.saleCount", description TEXT PATH "$.description.shortDescription" ) ) AS newTable;
تابع JSON_TABLE دو ورودی نیاز خواهد داشت؛ ورودی اول ستون JSONی است که میخوایم از آن کوئری بگیریم. ورودی دوم با تعیین path شروع خواهد شد. از آنجائیکه محتوای داخل ستون data به صورت آبجکت ذخیره شدهاست، از $ استفاده کردهایم که به معنای داکیومنت جاری است. سپس توسط کلمه کلیدی COLUMNS ساختار جدول موقتمان را تعریف خواهیم کرد. این ساختار به صورت یک آرگومان به COLUMNS ارسال خواهد شد و شبیه به ساختار CREATE TABLE است؛ با این تفاوت که بعد از تعریف نوع دادهای هر ستون باید مسیر رسیدن به مقدار موردنظر را نیز تعیین کنیم که در واقع همان سینتکس pathی است که در مثالهای قبل نیز بررسی کردیم. به عنوان مثال برای رسیدن به مقدار پراپرتی name، مسیر را به صورت name.$ نوشتهایم. این path از آرایه نیز پشتیبانی میکند؛ مثلاً برای دسترسی به عنصر اول آرایه tag کافی است اینگونه عمل کنیم:
tag CHAR(20) PATH "$.tag[0]"
همچنین تابع JSON_TABLE، از ساختارهای تودرتو نیز پشتیبانی میکند. به عنوان مثال برای داشتن مقادیر tag, category, image در خروجی میتوانیم از کلمه کلیدی NESTED استفاده کنیم:
SELECT newTable.* FROM experiments.productMetadata, JSON_TABLE( data, "$" COLUMNS ( id INT PATH "$.id", new CHAR(5) PATH "$.new", sku CHAR(20) PATH "$.sku", price FLOAT PATH "$.price", rating FLOAT PATH "$.rating", NESTED PATH '$.tag[*]' COLUMNS (tag TEXT PATH '$'), name CHAR(255) PATH "$.name", discount FLOAT PATH "$.discount", offerEnd TEXT PATH "$.offerEnd", saleCount INT PATH "$.saleCount", description TEXT PATH "$.description.shortDescription", NESTED PATH '$.image[*]' COLUMNS (image TEXT PATH '$'), NESTED PATH '$.category[*]' COLUMNS (category TEXT PATH '$') ) ) AS newTable;
درون COLUMNS میتوانیم یک name FOR ORDINALITY نیز تعیین کنیم. این فیلد دقیقاً مشابه AUTO INCREMENT در ساختار CREATE TABLE میباشد. به این معنا که به ازای هر آیتم فیلد nested، یک واحد اضافه خواهد شد. از آن میتوانیم به عنوان rowId برای آیتم آرایه استفاده کنیم:
NESTED PATH '$.category[*]' COLUMNS (categoryRowId FOR ORDINALITY, category TEXT PATH '$')
همچنین از ON EMPTY برای پراپرتیهایی که در ساختار JSON وجود ندارند نیز میتوانیم استفاده کنیم. به عنوان مثال در کوئری زیر گفتهایم در صورت عدم وجود price، یک مقدار پیشفرض باید نمایش داده شود و همچنین در صورت عدم وجود name، یک خطا در خروجی نمایش داده شود:
name CHAR(255) PATH "$.name" ERROR ON EMPTY, price FLOAT PATH "$.price" DEFAULT "0" ON EMPTY,
همچنین میتوانیم مقدار NULL را در صورت عدم وجود name ست کنیم:
name CHAR(255) PATH "$.name" NULL ON EMPTY,
SQLZOO؛ سایتی برای فراگیری SQL
NET Core 3.0 Preview 5. منتشر شد
System.Text.Json source generator چیست؟
پایهی تمام اعمال serialization و deserialization در دات نت، استفاده از Reflection است که در زمینهی ارائهی برنامههایی با کارآیی بالا و با مصرف حافظهی پایین، بهینه عمل نمیکند. راهحل جایگزین استفاده از Reflection که در زمان اجرای برنامه رخ میدهد، به همراه دات نت 5 ارائه شد و source generators نام دارد. Source generators امکان تولید فایلهای #C را در زمان کامپایل برنامه میسر میکنند که نسبت به راهحل Reflection که در زمان اجرای برنامه فعال میشود، کارآیی بسیار بیشتری را ارائه میکنند. برای مثال به همراه دات نت 6، علاوه بر روش پیشفرض مبتنی بر Reflection ارائه شدهی توسط System.Text.Json، راه حل جدید امکان استفادهی از source generators توکار آن نیز پیش بینی شدهاست. کار اصلی آن، انجام تمام مراحلی است که پیشتر توسط Reflection در زمان اجرای برنامه صورت میگرفت، اینبار در زمان کامپایل برنامه و ارائهی آن به صورت از پیش آماده شده و مهیا.
مزایای این روش شامل موارد زیر است:
- بالا رفتن سرعت برنامه
- کاهش زمان آغاز اولیهی برنامه
- کاهش میزان حافظهی مورد نیاز برنامه
- عدم نیاز به استفادهی از System.Reflection و System.Reflection.Emit
- ارائهی Trim-compatible serialization که سبب کاهش اندازهی نهایی برنامه میشود. برای مثال در برنامههای Blazor میتوان با فعالسازی Trimming، کدهای استفاده نشده را از فایلهای بایناری نهایی حذف کرد. استفاده از source generators، با این روش سازگاری کاملی دارد.
مثالی از نحوهی کار با JSON در دات نت 6، توسط source generators آن
فرض کنید قصد داریم اعمال serialization و deserialization از نوع JSON را بر روی نمونههای کلاس زیر انجام دهیم:
namespace Test { internal class Person { public string FirstName { get; set; } public string LastName { get; set; } } }
using System.Text.Json.Serialization; namespace Test { [JsonSerializable(typeof(Person))] internal partial class MyJsonContext : JsonSerializerContext { } }
پس از آن فقط کافی است MyJsonContext را به عنوان پارامتر متدهای جدید Serialize و یا Deserialize، به صورت زیر ارسال کنیم تا از آن استفاده شود:
Person person = new() { FirstName = "Jane", LastName = "Doe" }; byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(person, MyJsonContext.Default.Person); person = JsonSerializer.Deserialize(utf8Json, MyJsonContext.Default.Person);
متدهای جدید این API مبتنی بر source generators را در ادامه ملاحظه میکنید:
namespace System.Text.Json { public static class JsonSerializer { public static object? Deserialize(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(ReadOnlySpan<char> json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(string json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(ref Utf8JsonReader reader, Type returnType, JsonSerializerContext context) => ...; public static ValueTask<object?> DeserializeAsync(Stream utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static ValueTask<TValue?> DeserializeAsync<TValue>(Stream utf8Json, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static TValue? Deserialize<TValue>(ReadOnlySpan<byte> utf8Json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(string json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(ReadOnlySpan<char> json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(ref Utf8JsonReader reader, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static string Serialize(object? value, Type inputType, JsonSerializerContext context) => ...; public static void Serialize(Utf8JsonWriter writer, object? value, Type inputType, JsonSerializerContext context) { } public static Task SerializeAsync(Stream utf8Json, object? value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static byte[] SerializeToUtf8Bytes(object? value, Type inputType, JsonSerializerContext context) => ...; public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static void Serialize<TValue>(Utf8JsonWriter writer, TValue value, JsonTypeInfo<TValue> jsonTypeInfo) { } public static string Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...; } }
برای معرفی تنظیمات serialization و deserialization، برای مثال تهیهی خروجیهای CamelCase، میتوان از ویژگی JsonSourceGenerationOptions به صورت زیر استفاده کرد:
using System.Text.Json.Serialization; namespace Test { [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] [JsonSerializable(typeof(Person))] internal partial class MyJsonContext : JsonSerializerContext { } }
string json = JsonSerializer.Serialize(person, MyJsonContext.Default.Person); Person person = JsonSerializer.Deserialize(json, MyJsonContext.Default.Person);
روش استفاده از JSON Source generators در برنامههای ASP.NET Core
در این نوع برنامهها، JsonSerializerContextها را میتوان توسط متد AddContext به صورت زیر به تنظیمات JSON برنامه معرفی کرد:
services.AddControllers().AddJsonOptions(options => options.AddContext<MyJsonContext>());
روش استفاده از JSON Source generators در برنامههای Blazor
البته در اینجا بیشتر منظور امکان استفادهی از آنها توسط HttpClient است که به صورت زیر توسط متد GetFromJsonAsync واقع در فضای نام System.Net.Http.Json، میسر شدهاست:
[JsonSerializable(typeof(WeatherForecast[]))] internal partial class MyJsonContext : JsonSerializerContext { } @code { private WeatherForecast[] forecasts; private static JsonSerializerOptions Options = new(JsonSerializerDefaults.Web); private static MyJsonContext Context = new MyJsonContext(Options); protected override async Task OnInitializedAsync() { forecasts = await Http.GetFromJsonAsync("sample-data/weather.json", Context.WeatherForecastArray); } }
namespace System.Net.Http.Json { public static partial class HttpClientJsonExtensions { public static Task<object?> GetFromJsonAsync(this HttpClient client, string? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<object?> GetFromJsonAsync(this HttpClient client, System.Uri? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, string? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; } public static partial class HttpContentJsonExtensions { public static Task<object?> ReadFromJsonAsync(this HttpContent content, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<T?> ReadFromJsonAsync<T>(this HttpContent content, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; } }