ب) نوع قالب گرید را transparent انتخاب کنید (تا دیگر خطوط گرید بر روی کاغذ از پیش آماده شده چاپ نشود).
ج) margin ساختار صفحه رو دقیقا اندازه گیری و اعمال کنید
د) اندازه سلولها را چون ثابت است به همین نحو اندازه گیری کرده و مشخص کنید
کلاس ساده زیر را در نظر بگیرید:
using System.Collections.Generic;
namespace testWinForms87
{
class CDbgDisplay
{
public struct Person
{
public string Name;
public int Id;
}
public static List<Person> GetData()
{
List<Person> data = new List<Person>();
for (int i = 0; i < 40; i++)
data.Add(new Person { Name = "P" + i, Id = i });
return data;
}
}
}
همانطور که مشاهده میکنید، خروجی پیش فرض آنچنان دلپذیر نیست. به ازای هر کدام از 40 موردی که در این لیست قرار دارد، یکبار باید آن آیتم مورد نظر را انتخاب کرد، بر روی علامت + کنار آن کلیک نمود و سپس محتوای آنرا مشاهده کرد.
برای سفارشی سازی خروجی دیباگر ویژوال استودیو میتوان از ویژگی DebuggerDisplay استفاده کرد. سطر زیر را به بالای ساختار person اضافه کنید:
[DebuggerDisplay("Name:{Name},Id={Id}")]
اکنون یکبار دیگر بر روی data یک break point قرار داده و نتیجه را ملاحظه نمائید (شکل زیر):
بهتر شد؛ نه؟!
در اینجا یک رشته را با محتوای فیلدهای ساختار Person ایجاد کردیم و سپس خروجی پیش فرض دیباگر VS.Net را با آن جایگزین نمودیم. ویژوال استودیو محتوای عبارت داخل {} را با مقدار آن فیلد جایگزین خواهد کرد.
معرفی Lex.Db
نکته مهم آن سازگاری با برنامههای دات نت 4 به بعد، همچنین برنامههای ویندوز 8، سیلورلایت 5، ویندوز فون 8 و همچنین اندروید (از طریق Mono) است. به علاوه چون با دات نت تهیه شده است، دیگر نیازی نیست دو نگارش 32 بیتی و 64 بیتی آن توزیع شوند و به این ترتیب مشکلات توزیع بانکهای اطلاعاتی native مانند SQLite را ندارد ( و مطابق ادعای نویسنده آلمانی آن، از SQLite سریعتر است).
API این بانک اطلاعاتی، هر دو نوع متدهای synchronous و asynchronous را شامل میشود؛ به همین جهت با برنامههای ویندوز 8 و سیلورلایت نیز سازگاری دارد.
Lex.Db از برنامههای چندریسمانی و همچنین استفاده از یک بانک اطلاعاتی آن توسط چندین پروسه همزمان نیز پشتیبانی میکند.
در ادامه مروری خواهیم داشت بر نحوه استفاده از آن در حالت طراحی رابطهای؛ از این جهت که فعلا به ظاهر این بانک اطلاعاتی روابط را پشتیبانی نمیکند، اما در عمل پیاده سازی آن مشکل نیست.
دریافت Lex.Db
برای دریافت Lex.Db، دستور ذیل را در خط فرمان پاورشل نیوگت وارد نمائید:
PM> Install-Package Lex.Db
مدلهای برنامه
public class Product { public int Id { get; set; } public string Name { get; set; } } public class Customer { public int Id { get; set; } public string Name { get; set; } public string City { get; set; } } public class Order { public int Id { get; set; } public int? CustomerFK { get; set; } public int[] ProductsFK { get; set; } }
آغاز بانک اطلاعاتی
public static class Database { public static DbInstance Instance { get; private set; } public static DbTable<Product> Products { get; private set; } public static DbTable<Order> Orders { get; private set; } public static DbTable<Customer> Customers { get; private set; } /// <summary> /// سازنده استاتیکی که در طول عمر برنامه فقط یکبار اجرا میشود /// </summary> static Database() { createDb(); getTables(); } private static void getTables() { Products = Instance.Table<Product>(); Customers = Instance.Table<Customer>(); Orders = Instance.Table<Order>(); } private static void createDb() { Instance = new DbInstance(Path.Combine(Environment.CurrentDirectory, "LexDbTests")); Instance.Map<Product>() .WithIndex("NameIdx", x => x.Name) .Automap(i => i.Id, true); Instance.Map<Order>() .Automap(i => i.Id, true); Instance.Map<Customer>() .WithIndex("NameIdx", x => x.Name) .WithIndex("CityIdx", x => x.City) .Automap(i => i.Id, true); Instance.Initialize(); } }
سپس توسط وهلهای از بانک اطلاعاتی که ایجاد کردیم، کار معرفی خواص مدلهای برنامه توسط متد Map و Automap انجام میشود. متد Automap خاصیت primary key کلاس را دریافت کرده و همچنین پارامتر دوم آن مشخص میکند که آیا این کلید اصلی به صورت خودکار ایجاد شود یا خیر. به علاوه در همینجا میتوان روی فیلدهای مختلف، ایندکس نیز ایجاد کرد. متد WithIndex یک نام دلخواه را دریافت کرده و سپس خاصیتی را که باید بر روی آن ایندکس ایجاد شود، دریافت میکند.
در نهایت متد Initialize باید فراخوانی گردد. البته اگر برنامه شما WinRT است، این متد Initialize Async خواهد بود.
جداول نیز بر اساس مدلهای برنامه از طریق متد Instance.Table در دسترس قرار گرفتهاند.
افزودن اطلاعات به بانک اطلاعاتی
private static void addData() { var customer1 = new Customer { Name = "customer1", City = "City1" }; var customer2 = new Customer { Name = "customer2", City = "City2" }; Database.Instance.Save(customer1, customer2); // automatic Id assignment after Save var product1 = new Product { Name = "product1" }; var product2 = new Product { Name = "product2" }; Database.Instance.Save(product1, product2); // automatic Id assignment after Save var order1 = new Order { CustomerFK = customer1.Id, ProductsFK = new[] { product1.Id } }; var order2 = new Order { CustomerFK = customer2.Id, ProductsFK = new[] { product1.Id, product2.Id } }; Database.Instance.Save(order1, order2); // automatic Id assignment after Save }
در اینجا نیازی به ذکر Id نمونههای ساخته شده نیست؛ از این جهت که در حین عملیات Save، به صورت خودکار انتساب خواهند یافت.
همچنین نحوه مقدار دهی کلیدهای خارجی نیز با استفاده از همین کلیدهای اصلی آماده شده است.
واکشی تمام اطلاعات
private static void loadAll() { var orders = Database.Orders.LoadAll(); foreach (var order in orders) { // نحوه دریافت اطلاعات مشتری بر اساس کلید خارجی ثبت شده var orderCustomer = Database.Customers.LoadByKey(order.CustomerFK.Value); Console.WriteLine("Order Id: {0}, Customer: {1} ({2}) {3}", order.Id, orderCustomer.Name, orderCustomer.Id, orderCustomer.City); // نحوه بازیابی لیستی از اشیاء مرتبط از طریق آرایهای از کلیدهای خارجی ثبت شده var orderProducts = Database.Products.LoadByKeys(order.ProductsFK); foreach (var product in orderProducts) { Console.WriteLine(" Product Id: {0}, Name: {1}", product.Id, product.Name); } } }
سپس با استفاده از متدهای LoadByKey و LoadByKeys، به سادگی میتوان اشیاء مرتبط با هر سفارش را نیز واکشی کرد.
استفاده از ایندکسها برای کوئری گرفتن
private static void queryingByAnIndex() { var name = "customer1"; var customersList = Database.Customers .IndexQueryByKey("NameIdx", name) .ToList(); foreach (var person in customersList) { Console.WriteLine(person.Name); } }
// Using Take and Skip var list1 = Database.Orders.Query<int>() // primary idx .Take(1).Skip(2).ToList(); // Querying Between Ranges var list2 = Database.Customers .IndexQuery<string>("NameIdx") .GreaterThan("a", orEqual: true).LessThan("d").ToList();
حذف رکوردها
private static void deletingRecords() { Database.Customers.DeleteByKey(key: 1); var customers = Database.Customers.LoadByKeys(new[] { 1, 2 }); Database.Customers.Delete(customers); }
و اگر خواستید کل بانک اطلاعاتی را خالی کنید، متد Database.Instance.Purge اینکار را انجام خواهد داد.
کدهای کامل این مثال را از اینجا نیز میتوانید دریافت کنید:
Program-LexDb.cs
MongoDB #6
پارامتر | نوع | توضیحات |
name | رشته | نام مجموعهای است که باید ساخته شود |
Option | سند | (اختیاری) تعیین اختیارات برای اندازه حافظه و ایندکس گذاری |
فیلد | نوع | توضیحات |
capped | Boolean | (اختیاری) اگر مقدار آن true باشد یک مجموعهی پوشیده (capped) در اختیار میگذارد. مجموعهی پوشیده یک مجموعه با اندازه ثابت است که وقتی به حداکثر اندازه خود برسد، دادههای جدید را بصورت اتوماتیک جایگزین قدیمیترین دادهها میکند. اگر این پارامتر را true تنظیم کرده باشید، باید پارامتر size راهم مقداردهی کنید. |
AutoIndexID | Boolean | (اختیاری) اگر true باشد، بصورت اتوماتیک روی فیلد _id ایندکس میسازد. مقدار پیش فرض این پارامتر false است. |
size | number | (اختیاری) تعیین کنندهی حداکثر اندازه به بایت برای مجموعه پوشیده. اگر پارامتر capped برابر true باشد آنگاه نیاز دارید این پارامتر را نیز مقداردهی کنید. |
max | number | (اختیاری) تعیین کننده حداکثر تعداد سندهای مجاز در یک مجموعه پوشیده |
هنگام درج یک سند، MongoDB ابتدا فیلد capped و سپس فیلد max را بررسی میکند.
مثال:
گرامر پایه متد ()createCollection بدون اختیارات به شکل زیر است:
>use test switched to db test >db.createCollection("mycollection") { "ok" : 1 } >
با استفاده از دستور show collection میتوانید مجموعه ساخته شده را بررسی کنید:
>show collections mycollection system.indexes
مثال زیر گرامر متد ()createCollection، با اختیارات مهمتر را نمایش میدهد:
>db.createCollection("mycol", { capped : true, autoIndexID : true, size : 6142800, max : 10000 } ) { "ok" : 1 } >
در MongoDB، نیازی به ساخت مجموعه ندارید. وقتی یک سند را درج کنید، MongoDB بصورت اتوماتیک مجموعه را میسازد.
>db.tutorialspoint.insert({"name" : "tutorialspoint"}) >show collections mycol mycollection system.indexes tutorialspoint >
حذف مجموعهها در MongoDB
متد ()drop
دستور ()db.collection.drop برای حذف یک مجموعه از پایگاه داده استفاده میشود.
گرامر:
گرامر پایه دستور ()drop به شکل زیر است:
db.COLLECTION_NAME.drop()
مثال:
ابتدا همه مجموعههای موجود در پایگاه داده mydb را بررسی کنید:
>use mydb switched to db mydb >show collections mycol mycollection system.indexes tutorialspoint >
>db.mycollection.drop() true >
>show collections mycol system.indexes tutorialspoint >
در مطلب پیشنیاز فوق، تنظیمات روابط ارث بری را تا EF 6.x، میتوانید مطالعه کنید. در EF Core 1.0 RTM، فقط رابطهی TPH که در آن تمام کلاسهای سلسه مراتب ارث بری، به یک جدول در بانک اطلاعاتی نگاشت میشوند، پشتیبانی میشود. سایر روشهای ارث بری که در EF 6.x وجود دارند، مانند TPT و TPC، قرار است به نگارشهای پس از 1.0 RTM آن اضافه شوند:
- لیست مواردی که قرار است به نگارشهای بعدی اضافه شوند
- پیگیری وضعیت پیاده سازی TPT
- پیگیری وضعیت پیاده سازی TPC
طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط
مرسوم است که یک کلاس ویژه را به نام BaseEntity، به شکل زیر تعریف کنند؛ که اهدف آن حداقل سه مورد ذیل است:
الف) کاهش ذکر فیلدهای تکراری در سایر کلاسهای دومین برنامه، مانند فیلد Id
ب) نشانه گذاری موجودیتهای برنامه، جهت یافتن سریع آنها توسط Reflection (برای مثال افزودن خودکار موجودیتها به Context برنامه با یافتن تمام کلاسهایی که از نوع BaseEntity هستند)
ج) مقدار دهی خودکار یک سری از فیلدهای ویژه، مانند زمان افزوده شدن رکورد و آخرین زمان ویرایش شدن رکورد و امثال آن
public class BaseEntity { public int Id { set; get; } public DateTime? DateAdded { set; get; } public DateTime? DateUpdated { set; get; } }
public class Person : BaseEntity { public string FirstName { get; set; } public string LastName { get; set; } }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BaseEntity>();
مشکل! اگر بر روی کلاس پایهی تعریف شده تنظیماتی را اعمال کنید (هر نوع تنظیمی را)، با توجه به فراخوانی متد Ignore، این تنظیمات نیز ندید گرفته خواهند شد.
اگر علاقمند بودید تا این تنظیمات را به تمام کلاسهای مشتق شدهی از BaseEntity به صورت خودکار اعمال کنید، روش کار به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BaseEntity>(); foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { var dateAddedProperty = entityType.FindProperty("DateAdded"); dateAddedProperty.ValueGenerated = ValueGenerated.OnAdd; dateAddedProperty.SqlServer().DefaultValueSql = "getdate()"; var dateUpdatedProperty = entityType.FindProperty("DateUpdated"); dateUpdatedProperty.ValueGenerated = ValueGenerated.OnAddOrUpdate; dateUpdatedProperty.SqlServer().ComputedColumnSql = "getdate()"; }
خلاصهی آن نیز به این صورت است:
الف) نیازی نیست تا در حین ثبت اطلاعات موجودیتهای خود، فیلدهای DateAdded و یا DateUpdated را مقدار دهی کنید.
ب) فیلد DateAdded فقط در زمان اولین بار ثبت در بانک اطلاعاتی، به صورت خودکار توسط متد getdate مقدار دهی میشود.
ج) فیلد DateUpdated در هر بار فراخوانی متد SaveChanges (یعنی در هر دو حالت ثبت و یا به روز رسانی) به صورت خودکار توسط متد getdate مقدار دهی میشود.
تذکر! بدیهی است متد getdate، یک متد بومی سمت SQL Server است و این روش خاص تعیین مقدار پیش فرض فیلدها، فقط با SQL Server کار میکند. همچنین این getdate، به معنای دریافت تاریخ و ساعت سروری است که SQL Server بر روی آن نصب شدهاست و نه سروری که برنامهی وب شما در آن قرار دارد و برنامه کوچکترین دخالتی را در مقدار دهی این مقادیر نخواهد داشت.
در قسمتهای بعدی که مباحث Tracking را بررسی خواهیم کرد، روش دیگری را برای طراحی کلاسهای پایه و مقدار دهی خواص ویژهی آنها مطرح میکنیم که مستقل است از نوع بانک اطلاعاتی مورد استفاده.
بررسی تنظیمات رابطهی Table per Hierarchy یا TPH
رابطهی TPH یا تشکیل یک جدول بانک اطلاعاتی، به ازای تمام کلاسهای دخیل در سلسه مراتب ارث بری تعریف شده، بسیار شبیه است به حالت BaseEntity فوق که در آن نیز ارث بری تعریف شده، در نهایت منجر به تشکیل یک جدول، در سمت بانک اطلاعاتی میگردد. با این تفاوت که در حالت TPH، فیلد جدیدی نیز به نام Discriminator، به تعریف نهایی جدول ایجاد شده، اضافه میشود. از فیلد Discriminator جهت درج نام کلاسهای متناظر با هر رکورد، استفاده شده است. به این ترتیب EF در حین کار با اشیاء، دقیقا میداند که چگونه باید خواص متناظر با کلاسهای مختلف را مقدار دهی کند و نوع ردیف درج شدهی در بانک اطلاعاتی چیست؟
باید دقت داشت که تنظیمات TPH، شیوه برخورد پیش فرض EF Core با ارث بری کلاسها است و نیاز به هیچگونه تنظیم اضافهتری را ندارد. اما اگر علاقمند بودید تا نام فیلد خودکار Discriminator و مقادیری را که در آن درج میشوند، سفارشی سازی کنید، روش کار صرفا توسط Fluent API میسر است و به صورت زیر میباشد:
public class Blog { public int BlogId { get; set; } public string Url { get; set; } } public class RssBlog : Blog { public string RssUrl { get; set; } } class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasDiscriminator<string>("blog_type") .HasValue<Blog>("blog_base") .HasValue<RssBlog>("blog_rss"); } }
اگر این تنظیمات سفارشی صورت نگیرند، از نامهای پیش فرض نوعها برای مقدار دهی ستون Discriminator، مانند تصویر ذیل استفاده خواهد شد:
برای کوئری نوشتن در این حالت میتوان از متد الحاقی OfType جهت فیلتر کردن اطلاعات بر اساس کلاسی خاص، کمک گرفت:
var blog1 = db.Blogs.OfType<RssBlog>().FirstOrDefault(x => x.RssUrl == "………");
زمانیکه تصمیم میگیریم کدهای زده شده را بهینه کنیم، اکثرا دنبال راه حلهای جدید نمیگردیم. این مورد کاملا غریزی است؛ چرا که بهدنبال کمترین انرژی و بیشترین بازدهی هستیم؛ این طبیعت انسان است. صرفا کدهای قبلی را بازبینی میکنیم و سعی میکنیم نحوهی نوشتن منطقهای موجود را بهینه کنیم. در همین راستا درک عملکرد Task و ValueTask ها شاید قدمی مهم در مورد بهینه کردن کدها باشد؛ چرا استفاده درست و بجای این دو مورد میتواند تاثیر زیادی بر روی سرعت و استفاده از مصرف حافظه داشته باشد؟ در این مقاله سعی میکنیم تا درک درستی از این دو داشته باشیم.
Task<T> چیست؟
Task یک کلاس در فضای نام System.Threading.Tasks است؛ بهطوریکه کمک میکند تا یک قسمت از برنامه به صورت مستقل از Thread اصلی اجرا شود. بهبیان دیگر میتواند یک Thread Pool را ایجاد و با توجه به روند کار، از یک مرحلهی اجرایی به مرحلهای دیگر منتقل میکند. همچنین هر Task میتواند یک مقدار برگشتی نیز داشته باشد.
این درحالیاست که میتواند صرفا یک فرآیند را اجرا کند، بدون اینکه خروجی داشته باشد. بهعبارتی دیگر اگر فرآیندی داشته باشیم که در نهایت یک شناسه را برمیگرداند، از Task<int> و اگر فرآیندی داشته باشیم که صرفا فرآیند همگام سازی دادههای قدیمی به جدید را انجام میدهد، میتواند از نوع Task باشد.
همانطور که اشاره شد، Task یک کلاس است که شامل متدها و فیلدهای مختلفی میباشد. با استفاده از این اعضا میتوان نحوهی اجرای کدها و وضعیتهای مختلف اجرای آن را مدیریت کرد، تا در نهایت اجرای آن کامل شود.
به دلیل اینکه Task یک class است و class ها از نوع ReferenceType میباشند، روی حافظهی Heap ذخیره میشوند و بهازای هر بار فراخوانی متدی که خروجی Task دارد، شیء Task را روی Heap ذخیره میکند. این شیء وضعیت اجرای قسمتی از کد ما را که میتواند sync یا async باشد، در خود ذخیره میکند تا در نهایت اجرای آن کامل شود.
نحوه استفاده از Task<T>
برای درک بهتر، یک تکه کد را با بهره بردن از Task ایجاد میکنیم :
public static class DummyWeatherProvider { public static async Task<Weather> Get(string city) { await Task.Delay(10); var weather = new Weather { City = city, Date = DateTime.Now, AvgTempratureF = new Random().Next(5, 70) }; return weather; } }
static async Task CheckTaskStatus() { var task = DummyWeatherProvider.Get("Stockholm"); LogTaskStatus(task.Status); await task; LogTaskStatus(task.Status); } static void LogTaskStatus(TaskStatus status) { Console.WriteLine($"Task Status: {Enum.GetName(typeof(TaskStatus), status)}"); }
ValueTask<T> چیست؟
همانند Task ، ValueTask هم برای مدیریت وضعیت فرآیند استفاده میشود؛ با این تفاوت که ValueTask ها از نوع struct هستند. بهطوریکه نحوهی ذخیره سازی آنها در حافظه به نسبت class ها کاملا متفاوت است. از نقطه نظر سرعت، تشخیص دادن اینکه کدامیک باید استفاده شود، باید با توجه به سناریو، بررسی و انتخاب شود؛ چرا که از نظر تخصیص حافظه متفاوت عمل میکنند. برای درک بهتر عملکرد ValueTask ها کد زیر را بررسی میکنیم :
public class WeatherService { private readonly ConcurrentDictionary<string, Weather> _cache; public WeatherService() { _cache = new(); } public async Task<Weather> GetWeatherTask(string city) { if (!_cache.ContainsKey(city)) { var weather = await DummyWeatherProvider.Get(city); _cache.TryAdd(city, weather); } return _cache[city]; } public async ValueTask<Weather> GetWeatherValueTask(string city) { if (!_cache.ContainsKey(city)) { var weather = await DummyWeatherProvider.Get(city); _cache.TryAdd(city, weather); } return _cache[city]; }
کلاس WeatherService شامل یک فیلد private از نوع collection و دو متد است. ما از _cache جهت نگهداری اطلاعاتی که قبلا دریافت شده، استفاده میکنیم و به نوعی in-memory cache را پیاده سازی میکنیم. پیاده سازی منطق هر دو متد GetWeatherTask و GetWeatherValueTask کاملا شبیه به هم است؛ بهطوریکه اول بررسی میکنیم اطلاعات آب و هوای شهر مورد نظر در _cache وجود دارد یا خیر؟ اگر وجود داشت، اطلاعات به صورت مستقیم برگشت داده میشود؛ در غیر این صورت DummyWeatherProvider.Get() فراخوانی خواهد شد.
در قدم بعدی اطلاعات بهدست آمده را در _cache ذخیره میکنیم. سپس مقدار ذخیره شده را برگشت میدهیم. در واقع تنها تفاوت دو متد ذکر شده، نوع خروجی آن میباشد؛ یکی از Taskو دیگری از ValueTask استفاده میکند.
برای مقایسهی مصرف حافظهی این دو روی هر دو متد، Benchmark میگیریم. برای پیاده سازی نیار به کدهای زیر داریم :
[MemoryDiagnoser] public class TaskAndValueTaskBenchmark { private readonly WeatherService _weatherService; public TaskAndValueTaskBenchmark() { _weatherService = new(); } [Benchmark] [Arguments("Denver")] public async Task<Weather> TaskBenchmark(string city) { return await _weatherService.GetWeatherTask(city); } [Benchmark] [Arguments("London")] public async ValueTask<Weather> ValueTaskBenchmark(string city) { return await _weatherService.GetWeatherValueTask(city); } }
نتیجه به دست آمده به شرح زیر است :
Allocated | Gen0 | Method |
144 B | 0.0229 | TaskBenchmark |
------ | ---- | ValueTaskBenchmark |
مزیت ValueTask<T>
بهدلیل اینکه از نوع struct هستند، بر روی حافظه، در قسمت Stack ذخیره میشوند و به صورت خودکار بعد از اینکه نیازی به آنها نباشد، از حافظه حذف میشوند . به همین دلیل به شکل قابل توجهی، فشار را از روی GC کاهش میدهد .
علاوه بر این، در سناریویی که اکثر کدها به صورت sync اجرا میشوند، در این مواقع استفاده از ValueTask، بهتر از Task میباشد .
این سری متد GetWeatherValueTask
را جهت تشخص اینکه اغلب کدها به صورت sync یا async اجرا میشوند، بررسی میکنیم. در
متد ذکر شده اگر اطلاعات شهر مورد نظر وجود داشته باشد، کار به صورت sync اجرا میشود و اگر شهر وجود
نداشته باشد، کار به صورت async اجرا میشود. با بررسی دقیقتر متوجه میشویم اکثر مواقع در این متد کار به صورت sync
اجرا میشود؛ چرا که بعد ازدریافت
اطلاعات، مجدد آن را دریافت نمیکند، بلکه از حافظه میخواند (همان _cache ) .
محدودیتهای استفاده از ValueTask<T>
1. در اینجا تنها یکبار امکان استفاده از await وجود دارد. وقتی یکبار valueTask را await میکنیم، بهتر است کار دیگری بر روی آن انجام ندهیم؛ چراکه ممکن است از حافظه پاک شده باشد.
2. اگر در سناریویی لازم دارید چندین بار await را بر روی valueTask اجرا کنید، لازم است ابتدا آن را به Task تبدیل کنیم. برای این کار متد AsTask را فراخوانی میکنیم (بهتر است صرفا یکبار متد AsTask را فراخوانی کنیم).
3. نمیتوانیم به یک ValueTask به صورت هم زمان در حالت Multi threads دسترسی داشته باشیم.
4. به صورت پیش فرض خروجی عملیات async، نوع Task میباشد؛ مگر اینکه اغلب مراحل کار به صورت sync اجرا شود، مانند مثالی که بالاتر اشاره شد.
منابع :
در مقالهی قبل توانستیم یک سری
از مدلهای مربوط به وبلاگ را آماده کنیم. در ادامه به تکمیل آن و همچین
آغاز تهیهی مدلهای مربوط به اخبار و پیغام خصوصی میپردازیم.
همکاران این قسمت:
سلمان معروفی
مدل گزارش دهی
/// <summary> /// Repersents a Report template for every cms section /// </summary> public class Report { #region Ctor /// <summary> /// Create one instance for <see cref="Report"/> /// </summary> public Report() { ReportedOn = DateTime.Now; Id = SequentialGuidGenerator.NewSequentialGuid(); } #endregion #region Properties /// <summary> /// gets or sets identifier for Report /// </summary> public virtual Guid Id { get; set; } /// <summary> /// gets or sets reason of report /// </summary> public virtual string Reason { get; set; } /// <summary> /// gets or sets section that is reported /// </summary> public virtual ReportSection Section { get; set; } /// <summary> /// gets or sets sectionid that is reported /// </summary> public virtual long SectionId { get; set; } /// <summary> /// gets or sets type of report /// </summary> public virtual ReportType Type{ get; set; } /// <summary> /// gets or sets report's datetime /// </summary> public virtual DateTime ReportedOn { get; set; } /// <summary> /// indicate this report is read by admin /// </summary> public virtual bool IsRead { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets id of user that is reporter /// </summary> public virtual long ReporterId { get; set; } /// <summary> /// gets or sets id of user that is reporter /// </summary> public virtual User Reporter { get; set; } #endregion } /// <summary> /// Represents Report Section /// </summary> public enum ReportSection { News, Poll, Announcement, ForumTopic, BlogComment, BlogPost, NewsComment, PollComment, AnnouncementComment, ForumPost, User, ... } /// <summary> /// Represents Type of Report /// </summary> public enum ReportType { Spam, Abuse, Advertising, ... }
قصد داریم در این سیستم به کاربران خاصی دسترسی گزارش دادن در بخشهای مختلف را بدهیم. این دسترسیها در بخش تنظیمات سیستم قابل تغییر خواهند بود (برای مثال براساس امتیاز ، براساس تعداد پست و ... ) . این امکان میتواند برای مدیریت سیستم مفید باشد.
برای سیستم گزارش دهی به مانند سیستم امتیاز دهی عمل خواهیم کرد. در کلاس Report، خصوصیت ReportSection از نوع دادهی شمارشی میباشد که در بالا تعریف آن نیز آماده است و مشخص کنندهی بخشهایی میباشد که لازم است امکان گزارش دهی داشته باشند. خصوصیت Type هم که از نوع شمارشی ReportType میباشد، مشخص کنندهی نوع گزارشی است که داده شده است.
علاوه بر نوع گزارش، میتوان دلیل گزارش را هم ذخیره کرد که برای این منظور خصوصیت Reason در نظر گرفته شدهاست. خصوصیت IsRead هم برای مدیریت این گزارشات در پنل مدیریت در نظر گرفته شده است. اگر در مقالهی قبل دقت کرده باشید، متوجه وجود خصوصیتی به نام ReportsCount در کلاس BaseContent و BaseComment خواهید شد که برای نشان دادن تعداد گزارشهایی است که برای آن مطلب یا نظر داده شده است، استفاده میشود.
کلاس پایه فایلهای ضمیمه
/// <summary> /// Represents a base class for every attachment /// </summary> public abstract class BaseAttachment { #region Ctor public BaseAttachment() { Id = SequentialGuidGenerator.NewSequentialGuid(); AttachedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// sets or gets identifier for attachment /// </summary> public virtual Guid Id { get; set; } /// <summary> /// sets or gets name for attachment /// </summary> public virtual string FileName { get; set; } /// <summary> /// sets or gets type of attachment /// </summary> public virtual string ContentType { get; set; } /// <summary> /// sets or gets size of attachment /// </summary> public virtual long Size { get; set; } /// <summary> /// sets or gets Extention of attachment /// </summary> public virtual string Extension { get; set; } /// <summary> /// sets or gets bytes of data /// </summary> //public byte[] Data { get; set; } /// <summary> /// sets or gets Creation Date /// </summary> public virtual DateTime AttachedOn { get; set; } /// <summary> /// gets or sets counts of download this file /// </summary> public virtual long DownloadsCount { get; set; } /// <summary> /// gets or sets datetime that is modified /// </summary> public virtual DateTime ModifiedOn { get; set; } /// <summary> /// gets or sets section that this file attached there /// </summary> public virtual AttachmentSection Section { get; set; } /// <summary> /// gets or sets information of user agent /// </summary> public virtual string Agent { get; set; } #endregion #region NavigationProperties /// <summary> /// sets or gets identifier of attachment's owner /// </summary> public virtual long OwnerId { get; set; } /// <summary> /// sets or gets identifier of attachment's owner /// </summary> public virtual User Owner { get; set; } #endregion } public enum AttachmentSection { News, Announcement, ForumTopic, Conversation, BlogComment, NewsComment, PollComment, AnnouncementComment, ForumPost, BlogPost, Group, ... }
کلاس بالا اکثر خصوصیات لازم برای مدل Attachment ما را در خود دارد. قصد داریم از ارث بری TPH برای مدیریت فایلهای ضمیمه استفاده کنیم. در سیستم بستهی ما، تنها کاربران احراز هویت شده میتوانند فایل ضمیمه کنند و برای همین منظور OwnerId را که همان ارسال کنندهی فایل میباشد، به صورت Nullable در نظر نگرفتهایم.
یک سری از مشخصات که نیاز به توضیح اضافی ندارند، ولی خصوصیت AttachmentSection که از نوع شمارشی AttachmentSection است، برای دسترسی راحت کاربر به فایلهای ارسالی خود در پنل کاربری در نظر گرفته شده است. برای بخشهای (وبلاگ - اخبار - نظرسنجیها - آگهیها - انجمن) که نیاز به Privacy خاصی نیست و احراز هویت کفایت میکند، مدل زیر را در نظر گرفته ایم:
مدل فایلهای ضمیمه عمومی
/// <summary> /// Repersent the attachment for file /// </summary> public class Attachment : BaseAttachment { }
/// <summary> /// Represents one news item /// </summary> public class NewsItem : BaseContent { #region Ctor /// <summary> /// create one instance of <see cref="NewsItem"/> /// </summary> public NewsItem() { Rating = new Rating(); PublishedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// indicating that this news show on sidebar /// </summary> public virtual bool ShowOnSideBar { get; set; } /// <summary> /// indicate this NewsItem is approved by admin if NewsItem.Moderate==true /// </summary> public virtual bool IsApproved { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets newsitem's Reviews /// </summary> public ICollection<NewsComment> Comments { get; set; } #endregion }
کلاس بالا نشان دهندهی اشتراکهای ما خواهند بود. این مدل ما هم از کلاس پایهی BaseContent بحث شده در مقالهی قبل، ارث بری کرده و علاوه بر آن دو خصوصیت دیگر تحت عنوان IsApproved برای اعمال مدیریتی در نظر گرفته شده است (اگر در بخش تنظیمات سیستم اخبار، مدیریت تصمیم گرفته باشد تا اخبار جدید به اشتراک گذاشته شده با تأیید مدیریتی منتشر شوند) و خصوصیت ShowOnSideBar هم به عنوان یک تنظیم مدیریتی برای خبر خاصی در نظر گرفته شده که لازم است به صورت sticky در سایدبار نمایش داده شود.
برای اخبار نیز امکان ارسال نظر خواهیم داشت که برای این منظور لیستی از مدل زیر (NewsComment) در مدل بالا تعریف شده است .
مدل نظرات اخبار
public class NewsComment : BaseComment { #region Ctor public NewsComment() { Rating = new Rating(); CreatedOn = DateTime.Now; } #endregion #region NavigationProperties /// <summary> /// gets or sets body of blog NewsItem's comment /// </summary> public virtual long? ReplyId { get; set; } /// <summary> /// gets or sets body of blog NewsItem's comment /// </summary> public virtual NewsComment Reply { get; set; } /// <summary> /// gets or sets body of blog NewsItem's comment /// </summary> public virtual ICollection<NewsComment> Children { get; set; } /// <summary> /// gets or sets NewsItem that this comment sent to it /// </summary> public virtual NewsItem NewsItem { get; set; } /// <summary> /// gets or sets NewsItem'Id that this comment sent to it /// </summary> public virtual long NewsItemId { get; set; } #endregion }
/// <summary> /// Indicate one conversation /// </summary> public class Conversation { #region Ctor /// <summary> /// create one instance of <see cref="Conversation"/> /// </summary> public Conversation() { Id = SequentialGuidGenerator.NewSequentialGuid(); SentOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets identifier of record /// </summary> public virtual Guid Id { get; set; } /// <summary> /// represents this conversaion is seen /// </summary> public virtual bool IsRead { get; set; } /// <summary> /// gets or sets subject of this conversation /// </summary> public virtual string Subject { get; set; } /// <summary> /// gets or sets Date that this record added /// </summary> public virtual DateTime SentOn { get; set; } /// <summary> /// indicate this record deleted by sender /// </summary> public virtual bool DeletedBySender { get; set; } /// <summary> /// indicate this record deleted by receiver /// </summary> public virtual bool DeletedByReceiver { get; set; } /// <summary> /// gets or sets Messagescount that Unread by sender of this conversation /// </summary> public virtual int UnReadSenderMessagesCount { get; set; } /// <summary> /// gets or sets Messagescount that Unread by receiver of this conversation /// </summary> public virtual int UnReadReceiverMessagesCount { get; set; } /// <summary> /// gets or sets Messagescount of this conversation for increase performance /// </summary> public virtual int MessagesCount { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets if of user that start this conversation /// </summary> public virtual long SenderId { get; set; } /// <summary> /// gets or sets user that start this conversation /// </summary> public virtual User Sender { get; set; } /// <summary> /// gets or sets id of user that is recipient /// </summary> public virtual long ReceiverId { get; set; } /// <summary> /// gets or sets user that is recipient /// </summary> public virtual User Receiver { get; set; } /// <summary> /// get or set Messages of this conversation /// </summary> public virtual ICollection<ConversationReply> Messages { get; set; } /// <summary> /// get or set Attachments that attached in this conversation /// </summary> public virtual ICollection<ConversationAttachment> Attachments { get; set; } #endregion
مدل بالا نشان دهندهی گفتگوی بین دو کاربر میباشد. هر گفتگو امکان دارد با موضوع خاصی ایجاد شود و مسلما یک کاربر بهعنوان دریافت کننده و کاربر دیگری بعنوان ارسال کننده خواهد بود. برای این منظور خصوصیات Receiver و Sender که از نوع User هستند را در این کلاس در نظر گرفتهایم.
خصوصیات DeletedBySender و DeletedByReceiver هم برای این در نظر گفته شدهاند که اگر یک طرف این گفتگو خواهان حذف آن باشد، برای آن کاربر حذف نرم انجام دهیم و فعلا برای کاربر مقابل قابل دسترسی باشد.
UnReadSenderMessagesCount و UnReadReceiverMessagesCount هم برای بالا بردن کارآیی سیستم در نظر گفته شدهاند و در واقع تعداد پیغامهای خوانده نشده در یک گفتگو به صورت متمایز برای هر دو طرف، ذخیره میشود. هر گفتگو شامل یکسری پیغام رد و بدل شده خواهد بود که بدین منظور لیستی از ConversationReplyها را در مدل بالا تعریف کردهایم.
در هر گفتگو یکسری فایل هم ممکن است ضمیمه شود ، برای این منظور هم یک لیستی از کلاس ConversationAttachment در مدل گفتگو تعریف شده است که در ادامه پیاده سازی کلاس ConversationAttachment را هم خواهیم دید.
مدل ConversationReply به شکل زیر میباشد:
/// <summary> /// Represents One Reply to Conversation /// </summary> public class ConversationReply { #region Ctor /// <summary> /// create one instance of <see cref="ConversationReply"/> /// </summary> public ConversationReply() { Id = SequentialGuidGenerator.NewSequentialGuid(); SentOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets identifier of record /// </summary> public virtual Guid Id { get; set; } /// <summary> /// represents this conversaionReply is seen /// </summary> public virtual bool IsRead { get; set; } /// <summary> /// gets or sets body of this conversationReply /// </summary> public virtual string Body { get; set; } /// <summary> /// gets or sets Date that this record added /// </summary> public virtual DateTime SentOn { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets Parent's Id Of this ConversationReply /// </summary> public virtual Guid? ParentId { get; set; } /// <summary> /// gets or sets Parent Of this ConversationReply /// </summary> public virtual ConversationReply Parent { get; set; } /// <summary> /// get or set Children Of this ConversationReply /// </summary> public virtual ICollection<ConversationReply> Children { get; set; } /// <summary> /// gets or sets if of user that start this conversationReply /// </summary> public virtual long SenderId { get; set; } /// <summary> /// gets or sets user that start this conversationReply /// </summary> public virtual User Sender { get; set; } /// <summary> /// gets or sets Conversation that this message sent in it /// </summary> public virtual Conversation Conversation{ get; set; } /// <summary> /// gets or sets Id of Conversation that this message sent in it /// </summary> public virtual Guid ConversationId { get; set; } #endregion }
مدل بالا نشان دهندهی پیغامهای داده شده در یک گفتگو با موضوعی خاص میباشد. ساختار درختی آن هم برای ایجاد امکان جواب دهی برای پیغامها در نظر گرفته شده است (الزامی نیست). هر پیغام در یک گفتگو ارسال شده و یک ارسال کننده نیز دارد که برای این منظور به ترتیب دو خصوصیت Conversation از نوع کلاس Conversation و Sender از نوع User در نظر گرفتهایم.
با توجه به وجود Privacy در گفتگو نیاز است تا مدل فایل ضمیمه بخش گفتگوها به شکل زیر باشد:
/// <summary> /// Represents the attachment That attached in Conversation /// </summary> public class ConversationAttachment : BaseAttachment { #region NavigationProperties public virtual Conversation Conversation { get; set; } public virtual Guid? ConversationId { get; set; } #endregion }
همانطور که کمی بالاتر بحث شد، قصد اعمال ارث بری TPH را برای مدیریت فایلهای ضمیمه داریم. برای این منظور مدل بالا نیز از کلاس BaseAttachment ارث بری کرده و دو خصوصیت اضافه هم برای اعمال ارتباط یک به چند با گفتگو خواهد داشت. توجه کنید که ConversationId به صورت Nullable تعریف شدهاست.
نتیجه این قسمت
ASP.NET MVC #11
شرح مسئله :
View من به شکل زیره :
<% using (Html.BeginForm("Forget", "Account", FormMethod.Post, new { encType = "multipart/form-data", id = "forgetForm", name = "forgetForm" })) { %> <%: Html.AntiForgeryToken() %> <%: Html.ValidationSummary(true) %> <input id="User_Name" name="User_Name" type="text" data-original-title="لطفاً نام کاربری انتخابی خود را وارد کنید" data-toggle="tooltip" data-placement="top"> <button id="submitForgetForm" type="button"><i></i> بازیابی کلمه عبور </button> <% } %>
کجای کار اشتباه است ؟ با تشکر
چرا drawpreview به صورت استاتیک تعریف شده؟ چرا دوبار تعریف شده؟ و چرا این کلاس پایه اطلاعات زیادی در مورد رسم زیر مجموعههای خودش داره؟ آیا بهتر نیست جرئیات ترسیم هر شیء با override شدن به زیر کلاسهای مشتق شده واگذار بشن؟ چرا این متدها از خاصیتهای کلاس تعریف شده استفاده نمیکنن و دوباره این خاصیتها رو به صورت پارامتر دریافت کردن؟ (همون بحث اعلام استقلال متد تعریف شده به صورت استاتیک و اینکه چرا؟) و اگر این کلاس پایه تا این اندازه لازم هست در مورد رسم دایره و سایر اشکال اطلاعات داشته باشد، چه ضرورتی به abstarct بودن آن هست؟ اصلا چه ضرورتی به تعریف اشیاء مشتق شده از آن هست؟
مروری بر کاربردهای Action و Func - قسمت اول
- در متن MSDN فوق نوشته شده که استفاده از delegate در این حالت خاص نیز معتبر است؛ اما ایدهآل نیست. دلیلی که آورده از نظر من ساختگی است. ضرورتی ندارد تعریف یک delegate معرفی شده در runtime عوض شود. یا عنوان کرده که IComparable پایه مرتب سازی یک سری از متدها است. خوب ... بله زمانیکه از روز اول اینطور طراحی کردید همه چیز به هم مرتبط خواهند بود.
پ.ن.
قسمت نظرات MSDN یک زمانی باز بود ولی ... بعد از مدتی پشیمان شدند و به نظر این قابلیت منسوخ شده در این سایت!