Chat c = new Chat(); ChatLog m = new ChatLog(); public Guid NewObjects(Guid Id) { return Id; } public Guid idChat { get ; set; } public void CreateChat() { Guid Id = Guid.Parse(Context.ConnectionId); NewObjects(Id); idchat=Id; c.ChatId = Id; c.Time = 25; c.UserId = "87EC3AD1 - 53D1 - 4649 - 8CF3 - 2CD5ADB1938C"; var chat = db.Chats.FirstOrDefault(c => c.ChatId == Id); if (chat == null) { db.Chats.Add(c); db.SaveChanges(); } db.Dispose(); } public void broadcastMessage(string name, string message, Guid Id) { m.ChatText = message; m.Id = Id; m.ChatDate = DateTime.Now; m.UserId = "87EC3AD1 - 53D1 - 4649 - 8CF3 - 2CD5ADB1938C"; db.ChatLogs.Add(m); db.SaveChanges(); db.Dispose(); Clients.All.Msg(name, message,m.Id); }
ویژگی های پیشرفته ی AutoMapper - قسمت دوم
public class Kala {
[Key]
public int Kala_id { get; set; }
[DisplayName("نام کالا")]
public string Name { get; set; }
[DisplayName("قیمت خرید")]
public double Fee_Kharid { get; set; }
public virtual Brand Brand { get; set; }
}
public class Brand
{
[Key]
public int Brand_id { get; set; }
public string Brand_Name { get; set; }
public virtual ICollection<Kala> Kalas { get; set; }
}
public class KalaViewModel
{
public int Kala_Id { get; set; }
public string Name { get; set; }
public double Fee_Kharid { get; set; }
public string Brand_Name { get; set; }
}
//Controller
[HttpGet]
public ActionResult Index()
{
var kala = _Kala_Service.GetAllKalas();
var brand = _Brand_Service.GetAllBrands();
var kalaviewmodel = EntityMapper.Map<List<KalaViewModel>>(kala, brand);
return View(kalaviewmodel);
}
protected override void Configure()
{
Mapper.CreateMap<Kala, KalaViewModel>();
Mapper.CreateMap<Brand, KalaViewModel>()
.ForMember(des => des.Kala_Id, op => op.Ignore())
.ForMember(des => des.Name, op => op.Ignore())
.ForMember(x => x.Fee_Kharid, opt => opt.Ignore());
}
public class CmsUser : IdentityUser { public string DisplayName { get; set; } }
public class Post { private IList<string> _tags = new List<string>(); public int id { get; set; } public string name { get; set; } public string slug { get; set; } public string description { get; set; } public DateTime? publishTime { get; set; } public string content { get; set; } public IList<string> tags { get { return _tags; } set { _tags = value; } } public string CombineTags { get { return string.Join(",", _tags); } set { _tags = value.Split(',').Select(x => x.Trim()).ToList(); } } public string AuthorID { get; set; } // [ForeignKey("AuthorID")] // public CmsUser Author { get; set; } }
انواع ارجاعی
قبلا در مورد مقادیر ارجاعی صحبت کردیم. در اینجا نیز به این موضوع اشاره میکنیم که هر مقدار ارجاعی، نمونهای ایجاد شده از یک نوع ارجاعی میباشد. انواع ارجاعی در واقع ساختارهایی هستند که جهت گروه بندی دادهها و عملکرد بین آنها استفاده میشوند. در سایر زبانهای برنامه نویسی شیء گرا، به انواع ارجاعی، کلاس و به مقادیر ارجاعی، شیء میگویند. در جاوا اسکریپت نیز، به مقادیر ارجاعی و یا نمونههای ایجاد شده از انواع ارجاعی، شیء میگویند. به انواع ارجاعی، توصیف گر شیء نیز میگویند؛ زیرا ویژگیها و متدهای آن شیء را معرفی و توصیف مینماید.
نحوهی ایجاد شیء از نوع ارجاعی Object
از آنجاییکه نوع ارجاعی Object هیچ ویژگی و متد خاصی ندارد، متداولترین نوع ارجاعی جهت ایجاد اشیاء سفارشی میباشد. به دو روش میتوان نمونهای را از یک Object ایجاد نمود. روش اول استفاده از عملگر new و بصورت زیر میباشد:
var person = new Object (); person . name = "Meysam" ; person . age = 32 ;
با استفاده از عملگر new، شیء person را
از نوع Object
ایجاد نمودیم که شامل دو ویژگی (Property) به
نامهای name و age میباشد. در واقع شیء person
ساختاری را جهت نگهداری اطلاعات یک شخص معرفی میکند. این عمل موجب جلوگیری از
پراکندگی تعریف متغیرها و گروه بندی آنها در قالب یک شیء میشود. روش دوم استفاده
از Object Literal Notation یا
نماد تحت الفظی شیء و بصورت زیر میباشد:
var person = { name : "Meysam" , age : 32 };
Object Literal Notation ،
دستور میانبری برای ایجاد یک شیء از نوع Object میباشد. در مثال فوق هم، همانند
روش اول، شیء person را
با دو ویژگی name و age ایجاد
نمودهایم. در این روش، نام ویژگیها میتوانند به
صورت رشتهای و عددی نیز به یک شیء اختصاص یابد.
var person = { "name" : "Meysam" , "age" : 32 };
معمولا از دو روش فوق زمانی استفاده میشود که میخواهیم اشیایی را ایجاد نماییم که ویژگیهای آنها فقط خواندنی باشند. با استفاده
از روش دوم، حتی میتوان یک شیء خالی را ایجاد نمود که در ابتدا هیچ ویژگی ای
ندارد و در مراحل بعد، ویژگیهایی را به آن
اضافه نمود.
var person = {}; // var person = new Object(); person . name = "Meysam" ; person . age = 32 ;
در مثال فوق شیء person بدون ویژگیها تعریف شده است؛ سپس به آن ویژگیهایی را اضافه نمودهایم.
استفاده از روش Object Literal Notation ، یکی از روشهای محبوب برنامه نویسان جاوا اسکریپت میباشد. زیرا با کمترین کد و بصورت بصری، شیء ای را ایجاد نموده و مثلا به یک متد ارسال مینمایند. به مثال زیر توجه کنید:
function displayInfo ( arg ) { var output = "" ; if ( arg . name != undefined ) output += "Name: " + arg . name + "\n" ; if ( arg . age != undefined ) output += "Age: " + arg . age + "\n" ; return output ; } alert (displayInfo ({ name : "Meysam" , age : 32 })); alert (displayInfo ({ name : "Sohrab" }));
در این مثال یک تابع تعریف شده است که آرگومان
ورودی آن یک شیء میباشد. در تابع بررسی میشود که اگر ویژگی name و یا age
برای این آرگومان تعریف شده بود، خروجی تابع را تولید نماید. در واقع این ویژگیها اختیاری میباشند و میتوانند ارسال نگردند. در زمان فراخوانی تابع نیز شیء ای را
بصورت Object Literal Notation
ایجاد نموده و به تابع ارسال کردیم.
این الگو برای ارسال آرگومان، زمانی استفاده میشود که تعداد زیادی آرگومان اختیاری داریم و میخواهیم به تابع ارسال نماییم. معمولا کار با آرگومانهای نامی (Named Arguments) راحتتر است ولی زمانیکه تعداد آرگومانهای اختیاری زیاد باشند، مدیریت و نگهداری آنها سخت و طاقت فرسا میگردد و ظاهر زشتی را به تابع میدهد. بهترین راهکار جهت مدیریت چنین شرایطی این است که آرگومانهای اجباری را به صورت آرگومانهای نامی تعریف کنیم و آرگومانهای اختیاری را به صورت یک شیء به تابع ارسال کنیم.
نکتهی دیگری که باید به آن توجه نمود این است که جهت دسترسی به ویژگیهای یک شیء از (.) استفاده میشود. همچنین میتوان به یک ویژگی با استفاده از [] و بصورت یک آرایه دسترسی داشت که در این صورت نام ویژگی بصورت رشتهای در [] ذکر خواهد شد.
alert ( person . name ); alert ( person [ "name" ]);
در عمل تفاوتی بین دو مورد فوق وجود ندارد. مهمترین
مزیت استفاده از [] این است که میتوانید توسط یک متغیر به ویژگیهای یک شیء دسترسی
داشته باشید. همچنین اگر نام ویژگی شامل کاراکترهایی باشد که خطای گرامری رخ میدهد یا از اسامی رزرو شده استفاده کرده باشید، میتوانید از [] جهت دسترسی به
ویژگی استفاده نمایید.
var propertyName = "name" ; alert ( person [ propertyName ]); alert ( person [ "first name" ]);
در دستور alert اول، با استفاده از یک متغیر به ویژگی name از شیء person دسترسی پیدا نمودیم. در دستور آخر نیز، به دلیل وجود space در نام ویژگی، مجبور هستیم جهت دسترسی به ویژگی first name از [] استفاده نماییم.
بررسی نوع ارجاعی Function
توابع در واقع یک شیء و نمونهای از نوع ارجاعی Function میباشند که میتوانند همانند سایر اشیاء ویژگیها و متدهای خاص خود را داشته باشند. بنابراین میتوان در یک عبارت، تابعی را به یک شیء نسبت داد. به مثال زیر توجه کنید:
var sum = function ( a , b ) { return a + b ; }; alert ( sum ( 10 , 20 ));
خروجی :
30
شیء sum را تعریف نموده و یک تابع را به آن اختصاص دادیم که شامل دو آرگومان ورودی میباشد. حال میتوان با شیء sum همانند یک تابع رفتار نمود و تابع مورد نظر را فراخوانی کرد. توجه داشته باشید که هیچ نامی را در زمان تعریف تابع به آن اختصاص ندادهایم. به این شکل تعریف تابع، Function Expression میگویند.
همانند سایر اشیاء، نام تابع نیز اشارهگری به تابع میباشد. بنابراین میتوان توابع را نیز به یکدیگر نسبت داد. به مثال زیر توجه کنید:
function sum ( a , b ) { return a + b ; } alert ( sum ( 10 , 10 )); var anotherSum = sum ; alert ( anotherSum ( 10 , 10 )); sum = null ; alert ( anotherSum ( 10 , 10 )); alert ( sum ( 10 , 10 )); // Error: Object is not a function;
خروجی :
20
20
20
Error: Object is not a function
ابتدا تابعی را به نام sum ایجاد نمودیم که دو عدد را با هم جمع میکند. دقت داشته باشید که به این شکل تعریف تابع sum ، اعلان تابع یا Function Declaration میگویند. سپس متغیری را به نام anotherSum ، تعریف نموده و sum را به آن نسبت دادیم. توجه داشته باشید که در زمان انتساب یک تابع به یک متغیر نباید () را ذکر کنیم، زیرا ذکر () به منزلهی فراخوانی تابع و اختصاص خروجی آن به متغیر میباشد و نه انتساب اشاره گر تابع به آن متغیر. فراخوانی sum و anotherSum خروجی یکسانی را دارند؛ زیرا هر دو به یک تابع اشاره میکنند. در خطوط بعدی، شیء sum را با مقدار null تنظیم نمودیم. در واقع با این کار اشارهگر sum برابر null شده است؛ یعنی دیگر به هیچ تابعی اشاره نمیکند. ولی تابع همچنان در حافظه وجود دارد؛ زیرا اشارهگر دیگری به نام anotherSum در حال اشاره نمودن به آن میباشد. در مرحلهی بعدی که sum را فراخوانی نمودیم، به دلیل null بودن آن، خطا رخ خواهد داد.
بازنگری مجدد به مبحث Overloading
در اینجا فقط میخواهیم اشارهای کنیم به مبحث Overloading که قبلا در مورد آن بحث کردیم تا دلیل فنی عدم وجود Overloading را در جاوا اسکریپت درک کنیم. همانطور که قبلا بیان شد، نام تابع در واقع اشاره گری به تابع میباشد؛ بنابراین تعریف دو تابع هم نام، همانند اختصاص مجدد مقدار یا تغییر مقدار یک متغیر میباشد. به مثال زیر توجه کنید:
function calc ( num1 , num2 ) { return num1 + num2 ; } function calc ( num1 , num2 ) { return num1 - num2 ; }
همانطور که پیشتر نیز عنوان شد، تابع دوم جایگزین تابع اول میگردد. در واقع تعریف فوق همانند تعریف زیر میباشد:
var calc = function ( num1 , num2 ) { return num1 + num2 ; }; calc = function ( num1 , num2 ) { return num1 - num2 ; };
همانطور که میبینید، ابتدا متغیری به نام calc تعریف شدهاست و با یک تابع مقداردهی اولیه شده است. سپس با تابع دوم مقداردهی مجدد گردیده است و دیگر به تابع قبلی اشاره نمیکند. بنابراین همیشه تابع آخر جایگزین توابع قبلی میگردد.
Function Declaration در مقابل Function Expression
این دو روش تعریف و استفاده از توابع تقریبا مشابه هم میباشند و فقط یک تفاوت اصلی بین آنها وجود دارد و آن به نحوهی رفتار موتور پردازشی جاوا اسکریپت بر میگردد. Function Declaration قبل از اینکه کدهای جاوا اسکریپت خوانده و اجرا شوند، خوانده شده و در دسترس خواهند بود؛ اما Function Expression تا زمانی که روال اجرای کد به این خط از کد نرسد، اجرا نخواهد شد و در دسترس نخواهد بود. به مثال Function Declaration زیر توجه کنید:
alert ( sum ( 10 , 20 )); function sum ( a , b ) { return a + b ; }
خروجی :
30
قبل از اعلان تابع sum ، این تابع فراخوانی شده است؛ ولی هیچ خطایی رخ نمیدهد. زیرا قبل از اجرای دستورات، تابع sum خوانده و در دسترس خواهد بود. اما اگر تابع فوق بصورت Function Expression تعریف شده بود، خطا رخ میداد و برنامه اجرا نمیشد.
alert ( sum ( 10 , 20 )); // Error: undefined is not a function var sum = function ( a , b ) { return a + b ; };
خروجی :
Error: undefined is not a function
همانطور که میبینید، در خط اول برنامه، خطای اجرایی رخ داده است. زیرا هنوز روال اجرایی برنامه به خط تعریف تابع sum نرسیدهاست. بنابراین تابع sum در دسترس نخواهد بود و فراخوانی آن موجب بروز خطا میگردد.
ارسال تابع به عنوان ورودی یا خروجی توابع دیگر
همانطور که قبلا بیان شد، نام تابع در واقع یک متغیر میباشد که به تابع مورد نظر اشاره میکند. بنابراین میتوان همانند یک متغیر با آن رفتار نموده و به عنوان آرگومان ورودی و یا مقدار خروجی یک تابع آن را ارسال نمود. به مثال زیر توجه کنید:
function add ( a , b ) { return a + b ; } function mult ( a , b ) { return a * b ; } function calc ( a , b , fn ) { return a * b + fn ( a , b ); } alert ( calc ( 10 , 20 , add )); alert ( calc ( 10 , 20 , mult ));
خروجی :
230
400
دو تابع به نامهای add و mult با دو آرگومان ورودی تعریف شدهاند که به ترتیب حاصل جمع و حاصل ضرب دو آرگومان را بر میگردانند. تابع calc نیز با 3 آرگومان ورودی تعریف شدهاست که قصد داریم برای آرگومان سوم یک تابع را ارسال نماییم. تابع calc در 2 مرحله فراخوانی شدهاست که در یک مرحله تابع add و در مرحلهی دیگر تابع mult به عنوان آرگومان ورودی ارسال شدهاند. همانطور که از قبل میدانید، نام تابع اشارهگری به خود تابع میباشد. در تابع calc نیز با فراخوانی آرگومان fn در واقع داریم تابع ارسالی را فراخوانی مینماییم. حال به مثال زیر توجه کنید که یک تابع را به عنوان خروجی بر میگرداند:
function createFunction ( a , b ) { return function ( c ) { var d = ( a + b ) * c ; return d ; }; } var fn = createFunction ( 10 , 20 ); alert ( fn ( 30 ));
خروجی :
900
تابع createFunction دارای 2 آرگومان ورودی میباشد و یک تابع را با 1 آرگومان ورودی بر میگرداند. در ابتدا تابع createFunction را با آرگومانهای 10 و 20 فراخوانی نمودیم. خروجی این تابع که خود یک تابع با یک آرگومان ورودی میباشد، به عنوان خروجی برگردانده شده و در متغیر fn قرار میگیرد. سپس تابع fn با آرگومان ورودی 30 فراخوانی میگردد که مقادیر 10 و 20 را با هم جمع نموده و در 30 ضرب مینماید.
معرفی 3 ویژگی جدید در C# 8.0
public class BaseEntity : IBaseEntity { [JsonIgnore] int Id { get; set; } [JsonIgnore] string? Audit { get; set; } }
public class AuditSourceValues { [JsonProperty("hn")] public string? HostName { get; set; } [JsonProperty("mn")] public string? MachineName { get; set; } [JsonProperty("rip")] public string? RemoteIpAddress { get; set; } [JsonProperty("lip")] public string? LocalIpAddress { get; set; } [JsonProperty("ua")] public string? UserAgent { get; set; } [JsonProperty("an")] public string? ApplicationName { get; set; } [JsonProperty("av")] public string? ApplicationVersion { get; set; } [JsonProperty("cn")] public string? ClientName { get; set; } [JsonProperty("cv")] public string? ClientVersion { get; set; } [JsonProperty("o")] public string? Other { get; set; } }
public class EntityAudit<TEntity> { [JsonProperty("type")] [JsonConverter(typeof(StringEnumConverter))] public EntityEventType EventType { get; set; } [JsonProperty("user", NullValueHandling = NullValueHandling.Include)] public int? ActorUserId { get; set; } [JsonProperty("at")] public DateTime ActDateTime { get; set; } [JsonProperty("sources")] public AuditSourceValues? AuditSourceValues { get; set; } [JsonProperty("newValues", NullValueHandling = NullValueHandling.Include)] public TEntity NewEntity { get; set; } = default!; public string? SerializeJson() { return JsonSerializer.Serialize(this, options: new JsonSerializerOptions { WriteIndented = false, IgnoreNullValues = true }); } }
دقت کنید که این کلاس به صورت جنریک تعریف شده است تا اگر بعدا بخواهیم آن را Deserialize کنیم و مثلا از آن API بسازیم، یا استفادهی خاصی را از آن داشته باشیم، بهراحتی به Entity مد نظر تبدیل شود. در این مقاله فقط به ذخیرهی آن پرداخته میشود و استفاده از این فیلد که به راحتی و با کمک DbFunctionها در Entity Framework قابل انجام است به خواننده واگذار میشود.
public enum EntityEventType { Create = 0, Update = 1, Delete = 2 }
public interface IAuditSourcesProvider { AuditSourceValues GetAuditSourceValues(); }
public class AuditSourcesProvider : IAuditSourcesProvider { protected readonly IHttpContextAccessor HttpContextAccessor; public AuditSourcesProvider(IHttpContextAccessor httpContextAccessor) { HttpContextAccessor = httpContextAccessor; } public virtual AuditSourceValues GetAuditSourceValues() { var httpContext = HttpContextAccessor.HttpContext; return new AuditSourceValues { HostName = GetHostName(httpContext), MachineName = GetComputerName(httpContext), LocalIpAddress = GetLocalIpAddress(httpContext), RemoteIpAddress = GetRemoteIpAddress(httpContext), UserAgent = GetUserAgent(httpContext), ApplicationName = GetApplicationName(httpContext), ClientName = GetClientName(httpContext), ClientVersion = GetClientVersion(httpContext), ApplicationVersion = GetApplicationVersion(httpContext), Other = GetOther(httpContext) }; } protected virtual string? GetUserAgent(HttpContext httpContext) { return httpContext.Request?.Headers["User-Agent"].ToString(); } protected virtual string? GetRemoteIpAddress(HttpContext httpContext) { return httpContext.Connection?.RemoteIpAddress?.ToString(); } protected virtual string? GetLocalIpAddress(HttpContext httpContext) { return httpContext.Connection?.LocalIpAddress?.ToString(); } protected virtual string GetHostName(HttpContext httpContext) { return httpContext.Request.Host.ToString(); } protected virtual string GetComputerName(HttpContext httpContext) { return Environment.MachineName; } protected virtual string? GetApplicationName(HttpContext httpContext) { return Assembly.GetEntryAssembly()?.GetName().Name; } protected virtual string? GetApplicationVersion(HttpContext httpContext) { return Assembly.GetEntryAssembly()?.GetName().Version.ToString(); } protected virtual string? GetClientVersion(HttpContext httpContext) { return httpContext.Request?.Headers["client-version"]; } protected virtual string? GetClientName(HttpContext httpContext) { return httpContext.Request?.Headers["client-name"]; } protected virtual string? GetOther(HttpContext httpContext) { return null; } }
حالا برای تامین اطلاعات کلاس EntityAudit کار مشابهی میکنیم. ابتدا اینترفیس IEntityAuditProvider را به صورت زیر تعریف میکنیم:
public interface IEntityAuditProvider { string? GetAuditValues(EntityEventType eventType, object? entity, string? previousJsonAudit = null); }
و سپس کلاس EntityAuditProvider را ایجاد میکنیم:
public class EntityAuditProvider : IEntityAuditProvider { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IAuditSourcesProvider _auditSourcesProvider; #region Constructor Injections public EntityAuditProvider(IHttpContextAccessor httpContextAccessor, IAuditSourcesProvider auditSourcesProvider) { _httpContextAccessor = httpContextAccessor; _auditSourcesProvider = auditSourcesProvider; } #endregion public virtual string? GetAuditValues(EntityEventType eventType, object? newEntity, string? previousJsonAudit = null) { var httpContext = _httpContextAccessor.HttpContext; int? userId; var user = httpContext.User; if (!user.Identity.IsAuthenticated) userId = null; else userId = user.Claims.Where(x => x.Type == "UserID").Select(x => x.Value).First().ToInt(); var auditSourceValues = _auditSourcesProvider.GetAuditSourceValues(); var auditJArray = new JArray(); // Update & Delete if (eventType == EntityEventType.Update || eventType == EntityEventType.Delete) { auditJArray = JArray.Parse(previousJsonAudit!); } // Delete => No NewValues if (eventType == EntityEventType.Delete) { newEntity = null; } JObject newAuditJObject = JObject.FromObject(new EntityAudit<object?> { EventType = eventType, ActorUserId = userId, ActDateTime = DateTime.Now, AuditSourceValues = auditSourceValues, NewEntity = newEntity }, new JsonSerializer { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }); auditJArray.Add(newAuditJObject); return auditJArray.SerializeToJson(true); } }
public class AuditSaveChangesInterceptor : SaveChangesInterceptor { private readonly IEntityAuditProvider _entityAuditProvider; #region Constructor Injections public AuditSaveChangesInterceptor(IEntityAuditProvider entityAuditProvider) { _entityAuditProvider = entityAuditProvider; } #endregion public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result) { ApplyAudits(eventData.Context.ChangeTracker); return base.SavingChanges(eventData, result); } public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = new CancellationToken()) { ApplyAudits(eventData.Context.ChangeTracker); return base.SavingChangesAsync(eventData, result, cancellationToken); } private void ApplyAudits(ChangeTracker changeTracker) { ApplyCreateAudits(changeTracker); ApplyUpdateAudits(changeTracker); ApplyDeleteAudits(changeTracker); } private void ApplyCreateAudits(ChangeTracker changeTracker) { var addedEntries = changeTracker.Entries() .Where(x => x.State == EntityState.Added); foreach (var addedEntry in addedEntries) { if (addedEntry.Entity is IBaseEntity entity) { entity.Audit = _entityAuditProvider.GetAuditValues(EntityEventType.Create, entity); } } } private void ApplyUpdateAudits(ChangeTracker changeTracker) { var modifiedEntries = changeTracker.Entries() .Where(x => x.State == EntityState.Modified); foreach (var modifiedEntry in modifiedEntries) { if (modifiedEntry.Entity is IBaseEntity entity) { var eventType = entity.IsArchived ? EntityEventType.Delete : EntityEventType.Update; // Maybe Soft Delete entity.Audit = _entityAuditProvider.GetAuditValues(eventType, entity, entity.Audit); } } } private void ApplyDeleteAudits(ChangeTracker changeTracker) { var deletedEntries = changeTracker.Entries() .Where(x => x.State == EntityState.Deleted); foreach (var modifiedEntry in deletedEntries) { if (modifiedEntry.Entity is IBaseEntity entity) { entity.Audit = _entityAuditProvider.GetAuditValues(EntityEventType.Delete, entity, entity.Audit); } } } }
و سپس آن را به سیستم معرفی میکنیم:
services.AddDbContext<ATADbContext>((serviceProvider, options) => { options .UseSqlServer(...) // Interceptors var entityAuditProvider = serviceProvider.GetRequiredService<IEntityAuditProvider>(); options.AddInterceptors(new AuditSaveChangesInterceptor(entityAuditProvider)); });
نمونهی کامل فیلد Audit که در JsonFormatter قرار داده شده است، بعد از ایجاد شدن و یکبار آپدیت و سپس حذف نرم رکورد:
[ { "type":"Create", "user":1, "at":"2020-11-24T23:05:54.2692711+03:30", "sources":{ "hn":"localhost:44398", "mn":"DESKTOP-N1GAV2U", "rip":"::1", "lip":"::1", "ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", "an":"Server.Api", "av":"1.0.0.0" }, "newValues":{ "Name":"Farshad" } }, { "type":"Update", "user":1, "at":"2020-11-24T23:06:20.0838188+03:30", "sources":{ "hn":"localhost:44398", "mn":"DESKTOP-N1GAV2U", "rip":"::1", "lip":"::1", "ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", "an":"Server.Api", "av":"1.0.0.0" }, "newValues":{ "Name":"Edited Farshad" } }, { "type":"Delete", "user":null, "at":"2020-11-24T23:06:28.601837+03:30", "sources":{ "hn":"localhost:44398", "mn":"DESKTOP-N1GAV2U", "rip":"::1", "lip":"::1", "ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", "an":"Server.Api", "av":"1.0.0.0" }, "newValues":null } ]
ولی روش گفته شده در این مقاله، همین عملیات را به صورت کاملتری و فقط بر روی یک ستون همان جدول انجام میدهد که باعث ذخیرهی دیتای کمتر، یکپارچگی بهتر و دسترسیپذیری و راحتی استفاده از آن میشود.
اگر با SQL Server کار کرده باشید حتما با مفهوم و امکان Computed columns (فیلدهای محاسبه شده) آن آشنایی دارید. چقدر خوب میشد اگر این امکان برای سایر بانکهای اطلاعاتی که از تعریف فیلدهای محاسبه شده پشتیبانی نمیکنند، نیز مهیا میشد. زیرا یکی از اهداف مهم استفادهی صحیح از ORMs ، مستقل شدن برنامه از نوع بانک اطلاعاتی است. برای مثال امروز میخواهیم با MySQL کار کنیم، ماه بعد شاید بخواهیم یک نسخهی سبکتر مخصوص کار با SQLite را ارائه دهیم. آیا باید قسمت دسترسی به داده برنامه را از نو بازنویسی کرد؟ اینکار در NHibernate فقط با تغییر نحوهی اتصال به بانک اطلاعاتی میسر است و نه بازنویسی کل برنامه (و صد البته شرط مهم و اصلی آن هم این است که از امکانات ذاتی خود NHibernate استفاده کرده باشید. برای مثال وسوسهی استفاده از رویههای ذخیره شده را فراموش کرده و به عبارتی ORM مورد استفاده را به امکانات ویژهی یک بانک اطلاعاتی گره نزده باشید).
خوشبختانه در NHibernate امکان تعریف فیلدهای محاسباتی با کمک تعریف نگاشت خواص به صورت فرمول مهیا است. برای توضیحات بیشتر لطفا به مثال ذیل دقت بفرمائید:
در ابتدا کلاس کاربر تعریف میشود:
using System;
using NHibernate.Validator.Constraints;
namespace FormulaTests.Domain
{
public class User
{
public virtual int Id { get; set; }
[NotNull]
public virtual DateTime JoinDate { set; get; }
[NotNullNotEmpty]
[Length(450)]
public virtual string FirstName { get; set; }
[NotNullNotEmpty]
[Length(450)]
public virtual string LastName { get; set; }
[Length(900)]
public virtual string FullName { get; private set; } //از طریق تعریف فرمول مقدار دهی میگردد
public virtual int DayOfWeek { get; private set; }//از طریق تعریف فرمول مقدار دهی میگردد
}
}
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;
namespace FormulaTests.Domain
{
public class UserCustomMappings : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(u => u.Id).GeneratedBy.Identity(); //ضروری است
mapping.Map(x => x.DayOfWeek).Formula("DATEPART(dw, JoinDate) - 1");
mapping.Map(x => x.FullName).Formula("FirstName + ' ' + LastName");
}
}
}
اکنون اگر Fluent NHibernate را وادار به تولید اسکریپت متناظر با این دو کلاس کنیم حاصل به صورت زیر خواهد بود:
create table Users (
UserId INT IDENTITY NOT NULL,
JoinDate DATETIME not null,
FirstName NVARCHAR(450) not null,
LastName NVARCHAR(450) not null,
primary key (UserId)
)
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true"
name="FormulaTests.Domain.User, FormulaTests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Users">
<id name="Id" type="System.Int32" unsaved-value="0">
<column name="UserId" />
<generator class="identity" />
</id>
<property name="DayOfWeek" formula="DATEPART(dw, JoinDate) - 1" type="System.Int32" />
<property name="FullName" formula="FirstName + ' ' + LastName" type="System.String" />
<property name="JoinDate" type="System.DateTime">
<column name="JoinDate" />
</property>
<property name="FirstName" type="System.String">
<column name="FirstName" />
</property>
<property name="LastName" type="System.String">
<column name="LastName" />
</property>
</class>
</hibernate-mapping>
var list = session.Query<User>.ToList();
foreach (var item in list)
{
Console.WriteLine("{0}:{1}", item.FullName, item.DayOfWeek);
}
select
user0_.UserId as UserId0_,
user0_.JoinDate as JoinDate0_,
user0_.FirstName as FirstName0_,
user0_.LastName as LastName0_,
DATEPART(user0_.dw, user0_.JoinDate) - 1 as formula0_, --- همان فرمول تعریف شده است
user0_.FirstName + ' ' + user0_.LastName as formula1_ ---از طریق فرمول تعریف شده حاصل گردیده است
from
Users user0_
<input dir="ltr" class="form-control input-validation-error" type="email" data-val="true" data-val-email="'آدرس ایمیل' is not a valid email address." data-val-remote="این آدرس ایمیل هم اکنون مورد استفادهاست" data-val-remote-url="/Home/ValidateUniqueEmail" data-val-required="'آدرس ایمیل' must not be empty." id="Email" name="Email" >
افزودن آدرس ایمیل به مدل کاربران
به همان مدل قسمت قبل، قصد داریم خاصیت آدرس ایمیل را هم اضافه کنیم:
using System.ComponentModel.DataAnnotations; namespace FluentValidationSample.Models { public class UserModel { [Display(Name = "نام کاربری")] public string Username { get; set; } [Display(Name = "سن")] public int Age { get; set; } [Display(Name = "سابقه کار")] public int Experience { get; set; } [DataType(DataType.EmailAddress)] [Display(Name = "آدرس ایمیل")] public string Email { get; set; } } }
ایجاد سرویسی برای بررسی منحصربفرد بودن آدرس ایمیل
در ادامه قصد داریم سرویسی را ایجاد کنیم که برای مثال با بانک اطلاعاتی ارتباط برقرار کرده (در اینجا جهت سهولت ارائه، از یک آرایه استفاده شدهاست) و مشخص میکند که آیا ایمیل دریافتی پیشتر استفاده شدهاست یا خیر:
using System.Linq; namespace FluentValidationSample.Services { public interface IUsersService { bool IsUniqueEmail(string emailAddress); } public class UsersService : IUsersService { public bool IsUniqueEmail(string emailAddress) { string[] registedEmails = { "email@site.com", "test@gmail.com" }; return !registedEmails.Contains(emailAddress); } } }
namespace FluentValidationSample.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddScoped<IUsersService, UsersService>();
ایجاد اعتبارسنج سمت سرور بررسی منحصربفرد بودن آدرس ایمیل
در ادامه یک PropertyValidator جدید را ایجاد میکنیم تا بتوان توسط آن مقدار ایمیل دریافتی را در سمت سرور، تعیین اعتبار کرد:
using FluentValidation.Validators; using FluentValidationSample.Services; namespace FluentValidationSample.ModelsValidations { public class UniqueEmailValidator : PropertyValidator { private readonly IUsersService _usersService; public UniqueEmailValidator(IUsersService usersService) : base("این آدرس ایمیل هم اکنون مورد استفادهاست") { _usersService = usersService; } protected override bool IsValid(PropertyValidatorContext context) { return context.PropertyValue != null && _usersService.IsUniqueEmail((string)context.PropertyValue); } } }
پس از آن جهت سهولت استفادهی از آن، یک متد الحاقی جدید را نیز به نام UniqueEmail به نحو زیر تعریف میکنیم:
using FluentValidation; using FluentValidationSample.Services; namespace FluentValidationSample.ModelsValidations { public static class CustomValidatorExtensions { public static IRuleBuilderOptions<T, string> UniqueEmail<T>( this IRuleBuilder<T, string> ruleBuilder, IUsersService usersService) { return ruleBuilder.SetValidator(new UniqueEmailValidator(usersService)); } } }
یک نکته: اگر دقت کرده باشید، فضای نام این اعتبارسنج در این قسمت FluentValidationSample.ModelsValidations شدهاست:
علت اینجا است که اعتبارسنج تعریف شده نیاز دارد هم از مدلها استفاده کند و هم از سرویس کاربران. سرویس کاربران هم از مدلها استفاده میکند. به همین جهت اگر تعاریف اعتبارسنجی را داخل پروژهی مدلها قرار دهیم، یک وابستگی حلقوی رخ خواهد داد (وابستگی مدلها به سرویسها و برعکس). بنابراین بهتر است اعتبارسنجها را به یک پروژهی مجزا منتقل کنیم تا از بروز این cyclic dependency جلوگیری شود.
اعمال اعتبارسنجی منحصربفرد بودن ایمیل دریافتی به اعتبارسنج UserModel
پس از تهیهی متد الحاقی UniqueEmail، آنرا به RuleFor مخصوص خاصیت ایمیل اضافه میکنیم. در اینجا نیز تزریق وابستگی سرویس سفارشی IUsersService به سازندهی کلاس اعتبارسنج مجاز است:
using FluentValidation; using FluentValidationSample.Models; using FluentValidationSample.Services; namespace FluentValidationSample.ModelsValidations { public class UserModelValidator : AbstractValidator<UserModel> { public UserModelValidator(IUsersService usersService) { RuleFor(x => x.Username).NotNull(); RuleFor(x => x.Age).NotNull(); RuleFor(x => x.Experience).LowerThan(nameof(UserModel.Age)).NotNull(); RuleFor(x => x.Email).EmailAddress().NotNull().UniqueEmail(usersService); } } }
ایجاد متادیتای مورد نیاز جهت unobtrusive java script validation در سمت سرور
در ادامه نیاز است ویژگیهای data-val خاص unobtrusive java script validation را توسط FluentValidation ایجاد کنیم:
using FluentValidation; using FluentValidation.AspNetCore; using FluentValidation.Internal; using FluentValidation.Resources; using FluentValidation.Validators; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; namespace FluentValidationSample.ModelsValidations { public class RemoteClientValidator : ClientValidatorBase { public string RemoteUrl { set; get; } public RemoteClientValidator(PropertyRule rule, IPropertyValidator validator) : base(rule, validator) { } public override void AddValidation(ClientModelValidationContext context) { MergeAttribute(context.Attributes, "data-val", "true"); MergeAttribute(context.Attributes, "data-val-remote", GetErrorMessage(context)); MergeAttribute(context.Attributes, "data-val-remote-url", RemoteUrl); } private string GetErrorMessage(ClientModelValidationContext context) { var formatter = ValidatorOptions.MessageFormatterFactory().AppendPropertyName(Rule.GetDisplayName()); string messageTemplate; try { messageTemplate = Validator.Options.ErrorMessageSource.GetString(null); } catch (FluentValidationMessageFormatException) { messageTemplate = ValidatorOptions.LanguageManager.GetStringForValidator<NotEmptyValidator>(); } return formatter.BuildMessage(messageTemplate); } } }
<input dir="ltr" class="form-control input-validation-error" type="email" data-val="true" data-val-email="'آدرس ایمیل' is not a valid email address." data-val-remote="این آدرس ایمیل هم اکنون مورد استفادهاست" data-val-remote-url="/Home/ValidateUniqueEmail" data-val-required="'آدرس ایمیل' must not be empty." id="Email" name="Email" >
افزودن اعتبارسنجهای تعریف شده به تنظیمات برنامه
پس از تعریف UniqueEmailValidator و RemoteClientValidator، روش افزودن آنها به تنظیمات FluentValidation به صورت زیر است:
namespace FluentValidationSample.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddScoped<IUsersService, UsersService>(); services.AddControllersWithViews().AddFluentValidation( fv => { fv.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly()); fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>(); fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false; fv.ConfigureClientsideValidation(clientSideValidation => { // ... clientSideValidation.Add( validatorType: typeof(UniqueEmailValidator), factory: (context, rule, validator) => new RemoteClientValidator(rule, validator) { RemoteUrl = "/Home/ValidateUniqueEmail" }); }); } ); }
namespace FluentValidationSample.Web.Controllers { public class HomeController : Controller { private readonly IUsersService _usersService; public HomeController(IUsersService usersService) { _usersService = usersService; } // ... public IActionResult ValidateUniqueEmail(string email) { return Ok(_usersService.IsUniqueEmail(email)); } } }
تعریف کدهای جاوا اسکریپتی مورد نیاز
پیش از هرکاری، اسکریپتهای فایل layout برنامه باید چنین تعریفی را داشته باشند:
<script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script> <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script> <script src="~/js/site.js" asp-append-version="true"></script>
آزمایش برنامه
View این قسمت نیز همانند قسمت قبل است که فقط یک آدرس ایمیل به آن اضافه شدهاست:
برای آزمایش آن اگر برای مثال یکی از آدرسهای ایمیل از پیش تعریف شدهی در متد IsUniqueEmail سرویس کاربران را وارد کنیم، با خطای اعتبارسنجی سمت کلاینت فوق روبرو خواهیم شد.
کدهای کامل این سری را تا این قسمت از اینجا میتوانید دریافت کنید: FluentValidationSample-part04.zip
[Column(TypeName = "xml")] public string XmlValue { get; set; } [NotMapped] public XElement XmlValueWrapper { get { return XElement.Parse(XmlValue); } set { XmlValue = value.ToString(); } }
روش عمومی کار با نوعهای خاصی که در EF تعریف نشدن، استفاده از ویژگی Column و مشخص کردن Type آن است؛ مانند مثالی که در بالا ملاحظه میکنید. البته این نوع خاص، در سمت کدها باید به صورت رشته تعریف شود. مثلا از سال 2005 به این طرف فیلد XML به SQL Server اضافه شده. اما نمیشود ازش در EF به همون شکل XML استفاده کرد. باید تبدیلش کنی به String تا قابل استفاده بشه. یک نمونه دیگرش نوع خاص Spatial هست که در نگارشهای اخیر SQL Server اضافه شده (geography و geometry). این مورد فقط از EF 5.0 به بعد پشتیبانی توکاری ازش ارائه شده. یا برای hierarchyID در EF معادلی وجود نداره. برای تعریف این مورد نیز در یک مدل باید از string استفاده کرد.
بعد اگر این نوع خاص (که الان به صورت رشته دریافت شده) قابل نگاشت به نوعی مشخص در سمت کدهای برنامه بود (یعنی صرفا یک رشته ساده نبود) مثلا میشود از ویژگی NotMapped برای تبدیل آن و تعریف آن به شکل یک فیلد محاسباتی استفاده کرد.
internal class BinaryTreeNode<T> : IComparable<BinaryTreeNode<T>> where T : IComparable<T> { // مقدار گره internal T value; // شامل گره پدر internal BinaryTreeNode<T> parent; // شامل گره سمت چپ internal BinaryTreeNode<T> leftChild; // شامل گره سمت راست internal BinaryTreeNode<T> rightChild; /// <summary>سازنده</summary> /// <param name="value">مقدار گره ریشه</param> public BinaryTreeNode(T value) { if (value == null) { // از آن جا که نال قابل مقایسه نیست اجازه افزودن را از آن سلب میکنیم throw new ArgumentNullException( "Cannot insert null value!"); } this.value = value; this.parent = null; this.leftChild = null; this.rightChild = null; } public override string ToString() { return this.value.ToString(); } public override int GetHashCode() { return this.value.GetHashCode(); } public override bool Equals(object obj) { BinaryTreeNode<T> other = (BinaryTreeNode<T>)obj; return this.CompareTo(other) == 0; } public int CompareTo(BinaryTreeNode<T> other) { return this.value.CompareTo(other.value); } }
T : IComparable<T>
public interface IComparable<T> { int CompareTo(T other); }
public class BinarySearchTree<T> where T : IComparable<T> { /// کلاسی که بالا تعریف کردیم internal class BinaryTreeNode<T> : IComparable<BinaryTreeNode<T>> where T : IComparable<T> { // … } /// <summary> /// ریشه درخت /// </summary> private BinaryTreeNode<T> root; /// <summary> /// سازنده کلاس /// </summary> public BinarySearchTree() { this.root = null; } //پیاده سازی متدها مربوط به افزودن و حذف و جست و جو }
افزودن یک عنصر جدید
- اگر عنصر جدید کوچکتر از ریشه است، با یک تابع بازگشتی عنصر جدید را به زیر درخت چپ اضافه کن.
- اگر عنصر جدید بزرگتر از ریشه است، با یک تابع بازگشتی عنصر جدید را به زیر درخت راست اضافه کن.
- اگر عنصر جدید برابر ریشه هست، هیچ کاری نکن و خارج شو.
پیاده سازی الگوریتم بالا در کلاس اصلی:
public void Insert(T value) { this.root = Insert(value, null, root); } /// <summary> /// متدی برای افزودن عنصر به درخت /// </summary> /// <param name="value">مقدار جدید</param> /// <param name="parentNode">والد گره جدید</param> /// <param name="node">گره فعلی که همان ریشه است</param> /// <returns>گره افزوده شده</returns> private BinaryTreeNode<T> Insert(T value, BinaryTreeNode<T> parentNode, BinaryTreeNode<T> node) { if (node == null) { node = new BinaryTreeNode<T>(value); node.parent = parentNode; } else { int compareTo = value.CompareTo(node.value); if (compareTo < 0) { node.leftChild = Insert(value, node, node.leftChild); } else if (compareTo > 0) { node.rightChild = Insert(value, node, node.rightChild); } } return node; }
- اگر عنصر جدید برابر با گره فعلی باشد، همان گره را بازگشت بده.
- اگر عنصر جدید کوچکتر از گره فعلی است، گره سمت چپ را بردار و عملیات را از ابتدا آغاز کن (در کد زیر به ابتدای حلقه برو).
- اگر عنصر جدید بزرگتر از گره فعلی است، گره سمت راست را بردار و عملیات را از ابتدا آغاز کن.
حذف یک عنصر
- اگر گره برگ هست و والد هیچ گرهای نیست، به راحتی گره مد نظر را حذف میکنیم و ارتباط گره والد با این گره را نال میکنیم.
- اگر گره تنها یک فرزند دارد (هیچ فرقی نمیکند چپ یا راست) گره مدنظر حذف و فرزندش را جایگزینش میکنیم.
- اگر گره دو فرزند دارد، کوچکترین گره در زیر درخت سمت راست را پیدا کرده و با گره مدنظر جابجا میکنیم. سپس یکی از دو عملیات بالا را روی گره انجام میدهیم.
بعد از جابجایی، یکی از دو عملیات اول بالا را روی گره 11 اعمال میکنیم و در این حالت گره 11 که یک گره برگ است، خیلی راحت حذف و ارتباطش را با والد، با یک نال جایگزین میکنیم.
/// عنصر مورد نظر را جست و جوی میکند و اگر مخالف نال بود گره برگشتی را به تابع حذف ارسال میکند public void Remove(T value) { BinaryTreeNode<T> nodeToDelete = Find(value); if (nodeToDelete != null) { Remove(nodeToDelete); } } private void Remove(BinaryTreeNode<T> node) { //بررسی میکند که آیا دو فرزند دارد یا خیر // این خط باید اول همه باشد که مرحله یک و دو بعد از آن اجرا شود if (node.leftChild != null && node.rightChild != null) { BinaryTreeNode<T> replacement = node.rightChild; while (replacement.leftChild != null) { replacement = replacement.leftChild; } node.value = replacement.value; node = replacement; } // مرحله یک و دو اینجا بررسی میشه BinaryTreeNode<T> theChild = node.leftChild != null ? node.leftChild : node.rightChild; // اگر حداقل یک فرزند داشته باشد if (theChild != null) { theChild.parent = node.parent; // بررسی میکند گره ریشه است یا خیر if (node.parent == null) { root = theChild; } else { // جایگزینی عنصر با زیر درخت فرزندش if (node.parent.leftChild == node) { node.parent.leftChild = theChild; } else { node.parent.rightChild = theChild; } } } else { // کنترل وضعیت موقعی که عنصر ریشه است if (node.parent == null) { root = null; } else { // اگر گره برگ است آن را حذف کن if (node.parent.leftChild == node) { node.parent.leftChild = null; } else { node.parent.rightChild = null; } } } }
در کد بالا ابتدا جستجو انجام میشود و اگر جواب غیر نال بود، گره برگشتی را به تابع حذف ارسال میکنیم. در تابع حذف اول از همه برسی میکنیم که آیا گره ما دو فرزند دارد یا خیر که اگر دو فرزنده بود، ابتدا گرهها را تعویض و سپس یکی از مراحل یک یا دو را که در بالاتر ذکر کردیم، انجام دهیم.
دو فرزندی
اگر گره ما دو فرزند داشته باشد، گره سمت راست را گرفته و از آن گره آن قدر به سمت چپ حرکت میکنیم تا به برگ یا گره تک فرزنده که صد در صد فرزندش سمت راست است، برسیم و سپس این دو گره را با هم تعویض میکنیم.
تک فرزندی
در مرحله بعد بررسی میکنیم که آیا گره یک فرزند دارد یا خیر؛ شرط بدین صورت است که اگر فرزند چپ داشت آن را در theChild قرار میدهیم، در غیر این صورت فرزند راست را قرار میدهیم. در خط بعدی باید چک کرد که theChild نال است یا خیر. اگر نال باشد به این معنی است که غیر از فرزند چپ، حتی فرزند راست هم نداشته، پس گره، یک برگ است ولی اگر مخالف نال باشد پس حداقل یک گره داشته است.
اگر نتیجه نال نباشد باید این گره حذف و گره فرزند ارتباطش را با والد گره حذفی برقرار کند. در صورتیکه گره حذفی ریشه باشد و والدی نداشته باشد، این نکته باید رعایت شود که گره فرزند بری متغیر root که در سطح کلاس تعریف شده است، نیز قابل شناسایی باشد.
در صورتی که خود گره ریشه نباشد و والد داشته باشد، غیر از اینکه فرزند باید با والد ارتباط داشته باشد، والد هم باید از طریق دو خاصیت فرزند چپ و راست با فرزند ارتباط برقرار کند. پس ابتدا برسی میکنیم که گره حذفی کدامین فرزند بوده: چپ یا راست؟ سپس فرزند گره حذفی در آن خاصیت جایگزین خواهد شد و دیگر هیچ نوع اشارهای به گره حذفی نیست و از درخت حذف شده است.
بدون فرزند (برگ)
حال اگر گره ما برگ باشد مرحله دوم، کد داخل else اجرا خواهد شد و بررسی میکند این گره در والد فرزند چپ است یا راست و به این ترتیب با نال کردن آن فرزند در والد ارتباط قطع شده و گره از درخت حذف میشود.
پیمایش درخت به روش DFS یا LVR یا In-Order
public void PrintTreeDFS() { PrintTreeDFS(this.root); Console.WriteLine(); } private void PrintTreeDFS(BinaryTreeNode<T> node) { if (node != null) { PrintTreeDFS(node.leftChild); Console.Write(node.value + " "); PrintTreeDFS(node.rightChild); } }
در مقاله بعدی درخت دودویی متوازن را که پیچیدهتر از این درخت است و از کارآیی بهتری برخوردار هست، بررسی میکنیم.