🔸 الگوهای طراحی راه
حلهای معمولی برای مشکلات رایج در طراحی نرمافزار هستند. آنها مانند
نقشههای از پیش ساخته شدهای هستند که میتوانید آنها را برای حل یک مشکل
طراحی تکراری در کد خود سفارشی کنید.
🔸اینطور نیست که مثلا یک الگو
را پیدا کنید و آن را در برنامه خود کپی کنید. الگو، یک قطعه کد خاص نیست،
بلکه یک مفهوم کلی برای حل یک مشکل خاص است. شما میتوانید جزئیات الگو را
دنبال کنید و راه حلی متناسب با واقعیتهای برنامه خود را پیادهسازی
کنید.
🔸 الگوها اغلب با الگوریتمها اشتباه گرفته میشوند، زیرا هر
دو مفهوم راه حلهای معمولی برای برخی از مسائل شناخته شده را توصیف
میکنند. در حالی که یک الگوریتم همیشه مجموعه واضحی از اقدامات را تعریف
میکند که میتواند به هدفی دست یابد، یک الگو راه حلهای سطح بالا برای
مسائل سطح بالا هستند. کد یک الگوی اعمال شده برای دو برنامه مختلف ممکن
است متفاوت باشد.
🔸 همیشه منظور هر الگو را در ذهن خود مرور کنید و هنگام برخورد با یک مسئله به دنبال مناسبترین الگو بگردید.
🔸 شما نمیتوانید یک الگو را به کار بگیرید مگر آنکه آن را به خوبی فهمیده باشید. بنابراین در اولین گام باید اصول و الگوهای طراحی را هم به شکل انتزاعی و هم به شکل واقعی خوانده و تمرین کنید. دقت کنید که
یک الگو را به شکلهای مختلفی میتوان پیادهسازی کرد. هر چه پیاده
سازیهای بیشتری ببینید، به هدف و چگونگی استفاده از آن بهتر مسلط
میشوید.
راهنمای شروع سریع برای مطالعه الگوهای طراحی 👇🏻
PDF Cards: designpatternscard.pdf
DesignPatterns-online.pdf
- Hello World براساس سن و شغل! | پویا | zombie-geek.net
- بهبود رتبه سایت - جدول تناوبی سئو | (مجتبی بنائی) | www.banaie.ir
- راهنمای تنظیم فایرفاکس ۷ برای نشان دادن http:// در آدرس صفحات | مدیریت مجله نودایران | nodiran.com
- مدیر پروژه باید منبع آرامش باشد، مثل خولیو بلاسکو | مهدی عرب عامری | pmplus.ir
- Babel Obfuscator 4.3.0.0 منتشر شد | babelobfuscator.blogspot.com
- Lambdas در C++11 | geekswithblogs.net
- Visual Studio 2010 Feature Pack 2 منتشر شد | geekswithblogs.net
- Windows Phone SDK 7.1 منتشر شد | windowsteamblog.com
- Windows Simulator جهت توسعه برنامههای مخصوص ویندوز 8 | blogs.msdn.com
- Windows Simulator و دیباگ برنامههای لمسی | blogs.msdn.com
- به روز رسانی اول RAD Studio XE2 و یک سری مشکلات کپی رایت | www.itwriting.com
- پشتیبانی از ایجاد سایههای متنی در IE10 | blogs.msdn.com
- توابع جدید کار با رشتهها در نسخهی بعدی SQL Server | beyondrelational.com
- خلاصهای از مواردی که در نگارش بعدی سی شارپ شاهد خواهیم بود | weblogs.thinktecture.com
- مروری بر قابلیت آنالیز کدهای سی++ در نگارش بعدی ویژوال استودیو | channel9.msdn.com
- - لیست تازههای سرویس پک 2 آفیس 2007 (فقط برای نصب، درایوی که ویندوز بر روی آن نصب است باید نزدیک به 2 گیگ فضای خالی داشته باشد و گرنه در میانه نصب، متوقف خواهد شد؛ همانند نصب سرویس پک یک اس کیوال سرور 2008)
- - سرویس پکهایی که به زودی ارائه میشوند: Windows Server 2008 Service Pack 2 and Windows Vista Service Pack 2
- - دریافت Microsoft® Visual Studio Team System 2008 Database Edition GDR R2 (نگارش دوم همان ابزاری که جهت پیدا کردن تفاوتهای ساختاری دو دیتابیس از آن میتوان استفاده نمود)
- - پیش نمایش MySQL 5.4 توسط شرکت سان ارائه شد. این شرکت مدعی است که response times آن 90 درصد نسبت به نگارش قبلی سریعتر شده (+ و +)
- - معرفی 10 ادیتور متنی تحت وب مبتنی بر jQuery و همچنین سایر کتابخانهها
- - TestDriven.Net 2.20 ارائه شد، جزئیات بیشتر
- - سایت GeoCities بسته شد. سایت Google pages هم قرار است تا یکی دو ماه دیگر بسته شود (به عبارت دیگر شکل و شمایل این وبلاگ در آن تاریخ کلا به هم خواهد ریخت چون فایلهای سایت را در آنجا هاست کردهام ... به دریا هم که برویم ...)
ابزارهای زیادی برای محافظت و یا فشرده سازی و رمزنگاری اسمبلیهای دات نت موجود هستند که اکثر آنها تجاری هستند. برنامه netz نمونهای است سورس باز و رایگان که تنها کار فشرده سازی اسمبلی موجود را انجام میدهد. همچنین با استفاده از آن سورس اسمبلی شما بهوسیله برنامه reflector قابل مرور نخواهد بود. هر چند این برنامه سورس باز است و امکان unpack کردن نتیجه آن نیز احتمالا با اندکی سعی میسر خواهد بود اما باز هم یک مرحله پیشرفت محسوب میشود! خصوصا اینکه میتوان برای آن Custom Compression Provider نوشت و برای مثال فایل زیپ شده نهایی را رمزنگاری نیز کرد.
قبل از عمل:
بعد از عمل:
نحوه استفاده:
فشردن کردن یک فایل exe توسط آن
netz app.exe
الحاق کردن فایل zip.dll همراه با فایل exe (بدون نیاز به توزیع فایل zip.dll):
netz -z app.exe
یکی کردن تمام dll های برنامه با فایل exe در قالب یک فایل نهایی:
netz -s app.exe lib1.dll lib2.dll
نکته:
در اینجا به صورت پیش فرض از فایل zip.dll برای فشرده سازی استفاده میشود (که برای تمام نگارشهای دات نت قابل استفاده است). در نگارشهای جدید دات نت، فشرده سازی نیز به کلاسهای استاندارد اضافه شده است که امکان استفاده از آن نیز در اینجا مهیا است (و دیگر نیازی به استفاده از zip.dll آن نخواهد بود).
netz.exe -r net20comp.dll app.exe
نحوه برنامه نویسی یک compression provider سفارشی برای آن در آدرس زیر توضیح داده شده است. (اعمال موارد امنیتی دلخواه و استفاده از آن)
http://madebits.com/netz/compress.php
و موارد دیگری که در راهنمای سایت آن توضیح داده شدهاند.
اکثر قلمهای فارسی، فاقد تعاریف مرتبط با حروف انگلیسی هستند. البته عموم کاربران متوجه این امر نمیشوند چون ویندوز دو مفهوم Font Fallback و Font Linking را جهت پوشش glyph های تعریف نشده، در پشت صحنه اعمال خواهد کرد. جزئیات بیشتر در اینجا: (^ و ^)
به صورت خلاصه کار Font Fallback در ویندوز جایگزینی خودکار قلم مورد استفاده است؛ تحت شرایط زیر:
- فونت تعریف شده در برنامه، در سیستم کاربر وجود نداشته باشد.
- تعاریف Glyphهای بکارگرفته شده در متن جاری، در قلم انتخابی وجود نداشته باشند.
در WPF این مساله کاملا قابل کنترل است. قلمی که به صورت خودکار به عنوان جایگزین مطرح میشود در قلمی به نام "Global User Interface" تعریف شده است. تعاریف این قلم ترکیبی هم در فایلی به نام GlobalUserInterface.CompositeFont در پوشه فونتهای سیستم موجود است (برای مثال، مسیر c:\windows\fonts حاوی این فایل متنی است).
اگر این فایل XML را با یک ادیتور متنی باز کنید، مشاهده خواهید کرد که بازههای مختلف کاراکترهای یونیکد، به فونتهای پیش فرضی نگاشت شدهاند. بنابراین اگر این سؤال وجود دارد که در متن مخلوط فارسی و انگلیسی من، فونت پیش فرض حروف انگلیسی از کجا تامین و مشخص میشود، پاسخ را در این فایل میتوانید مشاهده کنید.
روش دیگری هم برای تعیین Fallback font در WPF وجود دارد. یک مثال:
<Window x:Class="WpfFontTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock
Text="نمایش مخلوطی از متن فارسی و متن English با هم"
Margin="7"
FontFamily="Fonts/BNazanin.ttf#B Nazanin, Comic Sans Ms"
FontSize="25"
FlowDirection="RightToLeft"
VerticalAlignment="Top" HorizontalAlignment="Center" />
</Grid>
</Window>
در این مثال فونت B Nazanin در برنامه قرار داده شده است (embedded font). همچنین در کنار آن پس از علامت کاما، Fallback font مشخص است. به این معنا که تاجایی که میسر است لطفا از فونت B Nazanin برای نمایش متن مورد نظر استفاده شود؛ اگر نشد از قلم Comic Sans Ms استفاده گردد. قلم B Nazanin حاوی تعاریف حروف انگلیسی نیست. بنابراین WPF جهت نمایش آنها از فونت دوم معرفی شده کمک میگیرد. توضیحات بیشتر در اینجا: (^)
PowerShell 7.x - قسمت سیزدهم - ساخت یک Static Site Generator ساده توسط PowerShell و GitHub Actions
dotnet tool update -g docfx
docfx docs/docfx.json --serve
- run: dotnet tool update -g docfx - run: docfx docs/docfx.json - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: docs/_site
@Html.AntiForgeryToken()
//Post in Ajax services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
function initDataTables() { table.destroy(); table = $("#tblJs").DataTable({ processing: true, serverSide: true, filter: true, ajax: { url: '@Url.Page("yourPage","yourHandler")', beforeSend: function (xhr) { xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val()); }, type: "POST", datatype: "json" }, language: { url: "/Persian.json" }, responsive: true, select: true, columns: scheme, select: true, }); }
processing: true, serverSide: true, filter: true, ajax: { url: '@Url.Page("yourPage","yourHandler ")', beforeSend: function (xhr) { xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val()); }, type: "POST", datatype: "json" },
public class FiltersFromRequestDataTable { public string length { get; set; } public string start { get; set; } public string sortColumn { get; set; } public string sortColumnDirection { get; set; } public string sortColumnIndex { get; set; } public string draw { get; set; } public string searchValue { get; set; } public int pageSize { get; set; } public int skip { get; set; } }
public static void GetDataFromRequest(this HttpRequest Request, out FiltersFromRequestDataTable filtersFromRequest) { //TODO: Make Strings Safe String filtersFromRequest = new(); filtersFromRequest.draw = Request.Form["draw"].FirstOrDefault(); filtersFromRequest.start = Request.Form["start"].FirstOrDefault(); filtersFromRequest.length = Request.Form["length"].FirstOrDefault(); filtersFromRequest.sortColumn = Request.Form["columns[" + Request.Form["order[0][column]"].FirstOrDefault() + "][name]"].FirstOrDefault(); filtersFromRequest.sortColumnDirection = Request.Form["order[0][dir]"].FirstOrDefault(); filtersFromRequest.searchValue = Request.Form["search[value]"].FirstOrDefault(); filtersFromRequest.pageSize = filtersFromRequest.length != null ? Convert.ToInt32(filtersFromRequest.length) : 0; filtersFromRequest.skip = filtersFromRequest.start != null ? Convert.ToInt32(filtersFromRequest.start) : 0; filtersFromRequest.sortColumnIndex = Request.Form["order[0][column]"].FirstOrDefault(); filtersFromRequest.searchValue = filtersFromRequest.searchValue?.ToLower(); }
Request.GetDataFromRequest(out FiltersFromRequestDataTable filtersFromRequest);
public static PaginationDataTableResult<T> ToDataTableJs<T>(this IEnumerable<T> source, FiltersFromRequestDataTable filtersFromRequest) { int recordsTotal = source.Count(); CofingPaging(ref filtersFromRequest, recordsTotal); var result = new PaginationDataTableResult<T>() { draw = filtersFromRequest.draw, recordsFiltered = recordsTotal, recordsTotal = recordsTotal, data = source.OrderByIndex(filtersFromRequest).Skip(filtersFromRequest.skip).Take(filtersFromRequest.pageSize).ToList() }; return result; } private static void CofingPaging(ref FiltersFromRequestDataTable filtersFromRequest, int recordsTotal) { if (filtersFromRequest.pageSize == -1) { filtersFromRequest.pageSize = recordsTotal; filtersFromRequest.skip = 0; } }
private static IEnumerable<T> OrderByIndex<T>(this IEnumerable<T> source, FiltersFromRequestDataTable filtersFromRequest) { var props = typeof(T).GetProperties(); string propertyName = ""; for (int i = 0; i < props.Length; i++) { if (i.ToString() == filtersFromRequest.sortColumnIndex) propertyName = props[i].Name; } System.Reflection.PropertyInfo propByName = typeof(T).GetProperty(propertyName); if (propByName is not null) { if (filtersFromRequest.sortColumnDirection == "desc") source = source.OrderByDescending(x => propByName.GetValue(x, null)); else source = source.OrderBy(x => propByName.GetValue(x, null)); } return source; }
var result = query.ToDataTableJs(filtersFromRequest); return new JsonResult(result);
public static IEnumerable<TSource> WhereSearchValue<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { return source.Where(predicate); } public static bool ContainsSearchValue(this string source, string toCheck) { return source != null && toCheck != null && source.IndexOf(toCheck, StringComparison.OrdinalIgnoreCase) >= 0; }
if (!string.IsNullOrEmpty(filtersFromRequest.searchValue)) query = query.WhereSearchValue(x => x.title.ContainsSearchValue(filtersFromRequest.searchValue) || x.id.ToString().ContainsSearchValue(filtersFromRequest.searchValue)).AsQueryable();
public JsonResult OnPostList() { Request.GetDataFromRequest(out FiltersFromRequestDataTable filtersFromRequest); var query = _Repo.GetQueryable().Select(x => new VmAdminList() { title = x.Title, } ); if (!string.IsNullOrEmpty(filtersFromRequest.searchValue)) query = query.WhereSearchValue(x => x.title.ContainsSearchValue(filtersFromRequest.searchValue) || x.id.ToString().ContainsSearchValue(filtersFromRequest.searchValue)).AsQueryable(); var result = query.ToDataTableJs(filtersFromRequest); return new JsonResult(result); }
data: function (d) { d.parentId = parentID; d.StartDateTime= StartDateTime; },
if (!int.TryParse(Request.Form["parentId"].FirstOrDefault(), out int parentId)) throw new NullReferenceException();
dataSrc: function (json) { $("#count").val(json.data.length); var sum = 0; json.data.forEach(function (item) { if (!isNullOrEmpty(item.credit)) sum += parseInt(item.credit); }) $("#sum").val(separate(sum)); return json.data; }
columns: [ { data: 'name' }, { data: 'position' }, { data: 'salary' }, { data: 'office' } ]
public class JsDataTblGeneretaor<T> { public readonly DataTableSchemaResult DataTableSchemaResult = new(); public JsDataTblGeneretaor<T> CreateTableSchema() { var props = typeof(T).GetProperties(); foreach (var prop in props) { DataTableSchemaResult.SchemaResult.Add(new() { data = prop.Name, sortable = (prop.PropertyType == typeof(int)) || (prop.PropertyType == typeof(bool)) || (prop.PropertyType == typeof(DateTime)), width = "", visible = (prop.PropertyType != typeof(DateTime)) }); } return this; } public JsDataTblGeneretaor<T> CreateTableColumns() { var props = typeof(T).GetProperties(); CustomAttributeData displayAttribute; foreach (var prop in props) { string displayName = prop.Name; displayAttribute = prop.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == "DisplayAttribute"); if (displayAttribute != null) { displayName = displayAttribute.NamedArguments.FirstOrDefault().TypedValue.Value.ToString(); } DataTableSchemaResult.Colums.Add(displayName); } return this; } public JsDataTblGeneretaor<T> AddCustomSchema(string data, bool? sortable = null, bool? visible = null, string width = null, string className = null) { if (DataTableSchemaResult.SchemaResult == null || !DataTableSchemaResult.SchemaResult.Any()) return this; foreach (var item in DataTableSchemaResult.SchemaResult.Where(x => x.data == data)) { if (sortable != null) item.sortable = sortable.Value; if (visible != null) item.visible = visible.Value; if (width != null) item.width = width; if (className != null) item.className = className; } return this; } public JsDataTblGeneretaor<T> SerializeSchema() { if (DataTableSchemaResult.SchemaResult == null || !DataTableSchemaResult.SchemaResult.Any()) return this; DataTableSchemaResult.SerializedSchemaResult = JsonSerializer.Serialize(DataTableSchemaResult.SchemaResult); return this; } } public class DataTableSchema { public string data { get; set; } public bool sortable { get; set; } public string width { get; set; } public bool visible { get; set; } public string className { get; set; } } public class DataTableSchemaResult { public readonly List<DataTableSchema> SchemaResult = new(); public readonly List<string> Colums = new(); public string SerializedSchemaResult = ""; }
public void OnGet() { //Create Data Table Js Schema and Columns Dynamicly JsDataTblGeneretaor<yourVM> tblGeneretaor = new(); DataTableSchemaResult = tblGeneretaor.CreateTableColumns().CreateTableSchema().SerializeSchema().DataTableSchemaResult; }
.AddCustomSchema("yourProperty",visible:false)
var scheme = JSON.parse('@Html.Raw(Model.DataTableSchemaResult.SerializedSchemaResult)')
@foreach (var col in Model.DataTableSchemaResult.Colums) { <th>@col</th> }
یکی از چالشهایی که در طراحی زیرساخت برای Domain هایی که تعداد زیادی عملیات CRUD را در back office سیستم خود دارند، داشتن مکانیزمی برای ذخیره سازی اطلاعات Master-Detail یا چه بسا Master-Detail-DetailOfDetail میباشد. در ادامه نحوه برخورد با چنین سناریوهایی را در EF Core و همچنین با استفاده از AutoMapper و FluentValidation بررسی خواهیم کرد.
موجودیتهای فرضی
public abstract class Entity : IHaveTrackingState { public long Id { get; set; } [NotMapped] public TrackingState TrackingState { get; set; } } public class Master : Entity { public string Title { get; set; } public ICollection<Detail> Details { get; set; } } public class Detail : Entity { public string Title { get; set; } public ICollection<DetailOfDetail> Details { get; set; } public Master Master { get; set; } public long MasterId { get; set; } } public class DetailOfDetail : Entity { public string Title { get; set; } public Detail Detail { get; set; } public long DetailId { get; set; } }
DbContext برنامه
public class ProjectDbContext : DbContext { public DbSet<Master> Masters { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseInMemoryDatabase("SharedDatabaseName"); } }
public interface IHaveTrackingState { TrackingState TrackingState { get; set; } //ICollection<string> ModifiedProperties { get; set; } } public enum TrackingState { Unchanged = 0, Added = 1, Modified = 2, Deleted = 3 }
با استفاده از پراپرتی TrackingState بالا، امکان مشخص کردن صریح State رکورد ارسالی توسط کلاینت مهیا میشود. قبلا نیز مطلبی در راستای STE یا همان Self-Tracking Entity تهیه شده است؛ و همچنین نظرات ارسالی این مطلب نیز میتواند مفید واقع شود.
DTOهای متناظر با موجودیتهای فرضی
public abstract class Model : IHaveTrackingState { public long Id { get; set; } public TrackingState TrackingState { get; set; } } public class MasterModel : Model { public string Title { get; set; } public ICollection<DetailModel> Details { get; set; } } public class DetailModel : Model { public string Title { get; set; } public ICollection<DetailOfDetailModel> Details { get; set; } } public class DetailOfDetailModel : Model { public string Title { get; set; } }
Mapper.Initialize(expression => { expression.CreateMap<MasterModel, Master>(MemberList.None).ReverseMap(); expression.CreateMap<DetailModel, Detail>(MemberList.None).ReverseMap(); expression.CreateMap<DetailOfDetailModel, DetailOfDetail>(MemberList.None).ReverseMap(); });
البته بهتر است این تنظیمات در درون Profileهای مرتبط با AutoMapper کپسوله شوند و در زمان مورد نیاز نیز برای انجام نگاشتها، واسط IMapper تزریق شده و استفاده شود.
تهیه داده ارسالی فرضی توسط کلاینت
var masterModel = new MasterModel { Title = "Master-Title", TrackingState = TrackingState.Added, Details = new List<DetailModel> { new DetailModel { Title = "Detail-Title", TrackingState = TrackingState.Added, Details = new List<DetailOfDetailModel> { new DetailOfDetailModel { Title = "DetailOfDetail-Title", TrackingState = TrackingState.Added, } } } } };
ذخیره سازی اطلاعات
در EF Core، متد جدید context.ChangeTracker.TrackGraph برای به روز رسانی وضعیت یک گراف از اشیاء مشابه به اطلاعات ارسالی ذکر شده در بالا، اضافه شده است. این مکانیزم مفهوم کاملا جدیدی در EF Core میباشد که امکان کنترل نهایی برروی اشیایی را که قرار است توسط Context ردیابی شوند، مهیا میکند. با پیمایش یک گراف، امکان اجرای عملیات مورد نظر شما را برروی تک تک اشیاء، مهیا میسازد.
using (var context = new ProjectDbContext()) { Console.WriteLine("################ Create Master and Details and DetailsOfDetail ##################"); Print(masterModel); var masterEntity = Mapper.Map<Master>(masterModel); context.ChangeTracker.TrackGraph( masterEntity, n => { var entity = (IHaveTrackingState) n.Entry.Entity; n.Entry.State = entity.TrackingState.ToEntityState(); }); context.SaveChanges(); }
در تکه کد بالا، پس از انجام عملیات نگاشت، توسط متد TrackGraph به صورت صریح، وضعیت موجودیتها مشخص شده است؛ این کار با تغییر State ارسالی توسط کلاینت به State قابل فهم توسط EF انجام شدهاست. برای این منظور دو متد الحاقی زیر را میتوان در نظر گرفت:
public static class TrackingStateExtensions { public static EntityState ToEntityState(this TrackingState trackingState) { switch (trackingState) { case TrackingState.Added: return EntityState.Added; case TrackingState.Modified: return EntityState.Modified; case TrackingState.Deleted: return EntityState.Deleted; case TrackingState.Unchanged: return EntityState.Unchanged; default: return EntityState.Unchanged; } } public static TrackingState ToTrackingState(this EntityState state) { switch (state) { case EntityState.Added: return TrackingState.Added; case EntityState.Modified: return TrackingState.Modified; case EntityState.Deleted: return TrackingState.Deleted; case EntityState.Unchanged: return TrackingState.Unchanged; default: return TrackingState.Unchanged; } } }
//GetForEditAsync var masterModel = context.Masters .ProjectTo<MasterModel>() .AsNoTracking().Single(a => a.Id == 1); //Client var detail1 = masterModel.Details.First(); detail1.Title = "Details-EditedTitle"; detail1.TrackingState = TrackingState.Modified; foreach (var detail in detail1.Details) { detail.TrackingState = TrackingState.Deleted; //detail.Title = "DetailOfDetails-EditedTitle"; }
متدی تحت عنوان GetForEditAsync که یک MasterModel را بازگشت میدهد، در نظر بگیرید؛ کلاینت از طریق API، این Object Graph را دریافت میکند و تغییرات خود را اعمال کرده و همانطور که مشخص میباشد به دلیل اینکه تنظیمات نگاشت بین Detail و DetailModel در ابتدای بحث نیز انجام شده است، این بار دیگر نیاز به استفاده از متد Include نمیباشد و این عملیات توسط متد ProjectTo خودکار میباشد. در نهایت داده ارسالی توسط کلاینت را دریافت کرده و به شکل زیر عملیات به روز رسانی انجام میشود:
using (var context = new ProjectDbContext()) { Console.WriteLine( "################ Unchanged Master and Modified Details and Deleted DetailsOfDetail ##################"); Print(masterModel); var masterEntity = Mapper.Map<Master>(masterModel); context.ChangeTracker.TrackGraph( masterEntity, n => { var entity = (IHaveTrackingState) n.Entry.Entity; n.Entry.State = entity.TrackingState.ToEntityState(); }); context.SaveChanges(); }
برای بحث اعتبارسنجی هم میتوان به شکل زیر عمل کرد:
public class MasterValidator : AbstractValidator<MasterModel> { public MasterValidator() { RuleFor(a => a.Title).NotEmpty(); RuleForEach(a => a.Details).SetValidator(new DetailValidator()); } } public class DetailValidator : AbstractValidator<DetailModel> { public DetailValidator() { RuleFor(a => a.Title).NotEmpty(); RuleForEach(a => a.Details).SetValidator(new DetailOfDetailValidator()); } } public class DetailOfDetailValidator : AbstractValidator<DetailOfDetailModel> { public DetailOfDetailValidator() { RuleFor(a => a.Title).NotEmpty(); } }
با استفاده از متد RuleForEach و SetValidator موجود در کتابخانه FluentValidation، امکان مشخص کردن اعتبارسنج برای Detail موجود در شیء Master را خواهیم داشت.
همچنین با توجه به این که برای عملیات Create و Edit از یک مدل (DTO) استفاده خواهیم کرد، شاید لازم باشد اعتبارسنجی خاصی را فقط در زمان ویرایش لازم داشته باشیم، که در این صورت میتوان از امکانات RuleSet استفاده کنید. در مطلب «طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها» با استفاده ValidateWithRuleAttribute امکان مشخص کردن RuleSet مورد نظر برای اعتبارسنجی ورودی متد سرویس نیز در نظر گرفته شده است.
منابع تکمیلی
- ChangeTracker.TrackGraph() in Entity Framework Core
- Disconnected entities
- https://msdn.microsoft.com/magazine/mt694083
- https://msdn.microsoft.com/magazine/mt767693
- https://blog.tonysneed.com/2017/10/01/trackable-entities-for-ef-core/
- Tracking Individually Modified Properties
string greeting = "Hello, C#";
که در این حالت مجموعهای از کاراکترها را ایجاد خواهد کرد:
- خانههای آن یک ضرب پر نمیشوند بلکه به ترتیب، خانه به خانه پر میشوند.
- قبل از انتساب متن باید باید از طول متن مطمئن شویم تا بتوانیم تعداد خانهها را بر اساس آن ایجاد کنیم.
- همه عملیات آرایهها از پر کردن ابتدای کار گرفته تا هر عملی، نیاز است به صورت دستی صورت بگیرد و تعداد خطوط کد برای هر کاری هم بالا میرود.
string str = "abcde"; char ch = str[1]; // ch == 'b' str[1] = 'a'; // Compilation error! ch = str[50]; // IndexOutOfRangeException
string a="Hello \"C#\""; string b="Hello \r\n C#"; //مساوی با اینتر string c="C:\\a.jpg"; //چاپ خود علامت \ -مسیردهی
string c=@"C:\a.jpg";// == "C:\\a.jpg"
string source = "Some source"; string assigned = source;
string hel = "Hel"; string hello = "Hello"; string copy = hel + "lo";
string hello = "Hello"; string same = "Hello";
برای اطلاعات بیشتر در این زمینه این لینک را مطالعه نمایید.
Console.WriteLine(word1.Equals(word2, StringComparison.CurrentCultureIgnoreCase));
string score = "sCore"; string scary = "scary"; Console.WriteLine(score.CompareTo(scary)); Console.WriteLine(scary.CompareTo(score)); Console.WriteLine(scary.CompareTo(scary)); // Console output: // 1 // -1 // 0
string alpha = "alpha"; string score1 = "sCorE"; string score2 = "score"; Console.WriteLine(string.Compare(alpha, score1, false)); Console.WriteLine(string.Compare(score1, score2, false)); Console.WriteLine(string.Compare(score1, score2, true)); Console.WriteLine(string.Compare(score1, score2, StringComparison.CurrentCultureIgnoreCase)); // Console output: // -1 // 1 // 0 // 0
نکته : برای مقایسه برابری دو رشته از متد Equals یا == استفاده کنید و فقط برای تعیین کوچک یا بزرگ بودن از compareها استفاده نمایید. دلیل آن هم این است که برای مقایسه از فرهنگ culture فعلی سیستم استفاده میشود و نظم جدول یونیکد را رعایت نمیکنند و ممکن است بعضی رشتههای نابرابر با یکدیگر برابر باشند. برای مثال در زبان آلمانی دو رشته "SS" و "ß " با یکدیگر برابر هستند.
عبارات با قاعده Regular Expression
string doc = "Smith's number: 0898880022\nFranky can be " + "found at 0888445566.\nSteven's mobile number: 0887654321"; string replacedDoc = Regex.Replace( doc, "(08)[0-9]{8}", "$1********"); Console.WriteLine(replacedDoc); // Console output: // Smith's number: 08******** // Franky can be found at 08********. // Steven' mobile number: 08********
اتصال رشتهها در Loop
DateTime dt = DateTime.Now; string s = ""; for (int index = 1; index <= 20000; index++) { s += index.ToString(); } Console.WriteLine(s); Console.WriteLine(dt); Console.WriteLine(DateTime.Now); Console.ReadKey();
- قسمتی از حافظه به طور موقت برای این دور جدید حلقه، گرفته میشود که به آن بافر میگوییم.
- رشته قبلی به بافر انتقال میابد که بسته به مقدار آن زمان بر و کند است؛ 5 کیلو یا 5 مگابایت یا 50 مگابایت و ...
- شماره تولید شده جدید به بافر چسبانده میشود.
- بافر به یک رشته تبدیل میشود وجایی برای خود در حافظه Heap میگیرد.
- حافظه رشته قدیمی و بافر دیگر بلا استفاده شدهاند و توسط GC پاکسازی میشوند که ممکن است عملیاتی زمان بر باشد.
String Builder
string declared = "Intern pool"; string built = new StringBuilder("Intern pool").ToString();
StringBuilder sb = new StringBuilder(); sb.Append("Numbers: "); DateTime dt = DateTime.Now; for (int index = 1; index <= 200000; index++) { sb.Append(index); } Console.WriteLine(sb.ToString()); Console.WriteLine(dt); Console.WriteLine(DateTime.Now); Console.ReadKey();
StringBuilder sb = new StringBuilder(15); sb.Append("Hello, C#!");
استفاده از متد ایستای string.Format
DateTime date = DateTime.Now; string name = "David Scott"; string task = "Introduction to C# book"; string location = "his office"; string formattedText = String.Format( "Today is {0:MM/dd/yyyy} and {1} is working on {2} in {3}.", date, name, task, location); Console.WriteLine(formattedText);