نمایش نتایج 1 تا 4 از 4

نام تاپیک: ایجاد History سفارسی در متد SaveChanges جهت ثبت تغییرات

  1. #1
    کاربر دائمی آواتار mmbguide
    تاریخ عضویت
    اسفند 1386
    محل زندگی
    منظومه شمسی
    پست
    1,172

    ایجاد History سفارسی در متد SaveChanges جهت ثبت تغییرات

    سلام دوستان

    می خواستم تغییرات یک ردیف در یک جدول از بانک اطلاعاتی را بصورت سفارشی و با توجه به نیاز خودم انجام بدم. آنچه که در اینترنت دیدم override کردن متد Savechangegs در کلاس Context بود. که با توجه به State یک Entity اقدام لازم را انجام میداد. روشی که خودم میخوام پیاده کنم به این صورت هستش:


    1. یک جدول با نام History جهت ثبت ایجاد کردم که شامل UserId, EntityFullName, ReferenceId, DataAsJson, State هستش.
    2. تمام جداول دارای ستون هایی جهت ثبت تاریخ CreateDate, UpdateDate و DeleteDate هستند.
    3. اگر یک ردیف توسط کاربر Delete شود فقط تیک گزینه IsDeleted فعال می شود و دیگر در دسترس نخواهد بود و مقدار DeleteDate نیز بروزرسانی خواهد شد. و در انتهای نام کاربر، نام کامل Entity و شماره ردیف و State در جدول History ثبت خواهد شد.
    4. اگر ردیف اضافه شود. مطابق روش Delete فعالیت کاربر ثبت خواهد شد.
    5. اگر یک ردیف ویرایش شود برنامه قبل از ذخیره اطلاعات جدید ابتدا اطلاعات قدیمی را بصورت Json در جدول History ذخیر خواهد کرد و سپس اقدام به بروزرسانی اطلاعات جدید خواهد کرد. هر زمان که تغییرات اطلاعات را بخوام بررسی کنم اطلاعت json را به کلاسی که در ستون EntityFullName ثبت شده تبدیل میکنم و نتیجه را برای کاربر ارسال میکنم که اگر تعداد دفعات تغییر یک ردیف زیاد باشه نتیجه را بصورت یک لیست ارسال میکنم.


    دوستان اگر روشی برای کار با تاریخچه تغییرات میدونید راهنمایی کنید. از CDC موجود در SQL Server نمیخوام استفاده کنم.

    تشکر

  2. #2
    کاربر دائمی آواتار mmbguide
    تاریخ عضویت
    اسفند 1386
    محل زندگی
    منظومه شمسی
    پست
    1,172

    نقل قول: ایجاد History سفارسی در متد SaveChanges جهت ثبت تغییرات

    سلام دوستان

    کسی با روشی که توضیح دادم و یا مشابه اون کار کرده؟ اگر نظری هم در خصوص این روش دارید ممنون میشم مطرح کنید.

    تشکر

  3. #3
    کاربر دائمی آواتار hamzehsh
    تاریخ عضویت
    مرداد 1385
    محل زندگی
    https://samanhis.ir
    سن
    42
    پست
    166

    نقل قول: ایجاد History سفارسی در متد SaveChanges جهت ثبت تغییرات

    سلام
    روش AuditFields
    که شما گفتید دقیقا همین هست

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {


    AuditFields();
    return (await base.SaveChangesAsync(true, cancellationToken));
    }


    private void AuditFields()
    {
    var userId = _accessor.HttpContext?.User?.Identity?.GetUserId() ;
    var auditUser = _accessor.HttpContext?.User?.Identity?.Name;
    var auditDate = DateTime.Now;
    var ipAddress = HttpContextExtensionsGetRemoteIP.GetRemoteIPAddres s(_accessor.HttpContext).ToString();
    foreach (var entry in this.ChangeTracker.Entries<BaseEntity>())
    {
    switch (entry.State)
    {
    case EntityState.Added:
    entry.Entity.InsertDate = auditDate;
    entry.Entity.InsertBy = auditUser;


    entry.Entity.InsertedBy = userId;
    entry.Entity.IsActive = true;
    entry.Entity.InsertIpAddress = ipAddress;
    break;


    case EntityState.Modified:
    entry.Entity.UpdateDate = auditDate;
    entry.Entity.UpdateBy = auditUser;
    entry.Entity.UpdatedBy = userId;
    entry.Entity.UpdateIpAddress = ipAddress;
    break;
    }
    }
    }






    من هم همین روش را پیاده سازی کردم
    در زمان ایجاد رکورد فیلدهای InsertBy , InsertDate
    و هر نوع ویرایش دو تا فیلد UpdateBy , UpdateDate مقدار دهی میشوند.
    IsDelete هم شامل ویرایش میشود.


    تنها حالتی که بهش فکر کردم ولی هنوز پیاده نکردم در قسمت




    case EntityState.Modified:






    راه حلهای زیادی رو بررسی کردم ولی این موضوع که کدام فیلد تغییر کرده بهترین روش هست ولی هنوز به نتیجه نرسیده ام.



    var changeInfo = context.ChangeTracker.Entries()
    .Where (t => t.State == EntityState.Modified)
    .Select (t => new {
    Original = t.OriginalValues.Properties.ToDictionary (pn => pn, pn => t.OriginalValues[pn]),
    Current = t.CurrentValues.Properties.ToDictionary (pn => pn, pn => t.CurrentValues[pn]),
    });




    برای هر جدول اگر بخواهیم یک جدول سایه ایجاد کنیم مقدار داده ها زیاد میشود و عملا مقایسه هم بی معناست
    در واقع داده های تغییر نکرده اهمیتی ندارند و لزوما فیلدهای تغییر داده شده ملاک هستند.


    تبدیل همه داده ها به Json و نگهداری نوع کلاس در یک فیلد از نظر من روش مناسبی نیست. منطقی برایش پیدا نکردم و این سوالات به وجود آمد:
    هر بار که نیاز به گزارش گیری یا بررسی داریم باید Cast انجام بدیم؟
    آیا احتیاج داریم که بدانیم اینها از چه کلاسی مشتق شده اند؟
    اگر یک روزی این کلاس تغییر کرد نتیجه داده هایی که داریم چه سرانجامی خواهند داشت؟
    و ....


    خوشحال میشوم اگر روش مناسبی پیدا کردید بنده را هم در جریان قرار بدهید
    آخرین ویرایش به وسیله hamzehsh : شنبه 10 دی 1401 در 18:00 عصر دلیل: اصلاح املایی و قالب بندی

  4. #4
    کاربر دائمی آواتار hamzehsh
    تاریخ عضویت
    مرداد 1385
    محل زندگی
    https://samanhis.ir
    سن
    42
    پست
    166

    نقل قول: ایجاد History سفارسی در متد SaveChanges جهت ثبت تغییرات

    تکمیلی مورد قبل:
    عذرخواهی میکنم این مورد را کلا فراموش کرده بودم یکی از دوستان پرسید ادامه اش را اینجا میگذارم:
    این نسخه کامل شده همون موارد بالایی هست

    دو کلاس ایجاد کردم به نام Audit و AuditEntry



    public class Audit
    {
    public long Id { get; set; }
    public string CommitValue { get; set; }
    public DateTime DateTime { get; set; }


    public string UserId { get; set; }
    public string UserName { get; set; }
    public string Type { get; set; }
    public string TableName { get; set; }

    public string OldValues { get; set; }
    public string NewValues { get; set; }
    public string AffectedColumns { get; set; }
    public string PrimaryKey { get; set; }
    public string IdKey { get; set; }
    public string ActionName { get; set; }

    public string PCommitValue { get; set; }
    public string PersianTableName { get; set; }
    public string PAffectedColumns { get; set; }
    }


    public class AuditEntry
    {
    public AuditEntry(EntityEntry entry)
    {
    Entry = entry;
    }
    public EntityEntry Entry { get; }
    public string UserId { get; set; }
    public string UserName { get; set; }
    public string TableName { get; set; }
    public string PersianTableName { get; set; }
    public Dictionary<string, object> KeyValues { get; } = new Dictionary<string, object>();
    public Dictionary<string, object> OldValues { get; } = new Dictionary<string, object>();
    public Dictionary<string, object> NewValues { get; } = new Dictionary<string, object>();
    public AuditType AuditType { get; set; }
    public List<string> ChangedColumns { get; } = new List<string>();
    public List<string> PChangedColumns { get; } = new List<string>();
    public Audit ToAudit()
    {
    var audit = new Audit();
    audit.UserId = UserId;
    audit.UserName = UserName;
    audit.Type = AuditType.ToString();
    audit.TableName = TableName;
    audit.PersianTableName= PersianTableName;
    audit.DateTime = DateTime.Now;
    audit.PrimaryKey = JsonSerializer.Serialize(KeyValues);
    audit.OldValues = OldValues.Count == 0 ? null : JsonSerializer.Serialize(OldValues);
    audit.NewValues = NewValues.Count == 0 ? null : JsonSerializer.Serialize(NewValues);
    audit.AffectedColumns = ChangedColumns.Count == 0 ? null : JsonSerializer.Serialize(ChangedColumns);
    audit.PAffectedColumns = ChangedColumns.Count == 0 ? null : JsonSerializer.Serialize(PChangedColumns);


    audit.IdKey = KeyValues.Values.FirstOrDefault()?.ToString();
    foreach (var newItem in NewValues)
    {
    foreach (var oldItem in OldValues.Where(w=>w.Key == newItem.Key).Where(w=>!object.Equals(w.Value , newItem.Value)))
    {
    //var xxx = object.ReferenceEquals(oldItem.Value, newItem.Value);
    audit.CommitValue = audit.CommitValue + $" {oldItem.Key} : " + oldItem.Value + $" -> " + newItem.Value + ";\n";
    audit.PCommitValue = audit.PCommitValue + $" {EnumExtensions.GetDisplayProperty(oldItem.Key.Get Type())} : "
    + oldItem.Value + $" -> " + newItem.Value + "; \n";
    }

    }
    return audit;
    }
    }


    این کلاسها برای ثبت موارد تغییر کرده / ویرایش شده در بانک هست.
    در حالت کلی هر رکوردی که ایجاد میشه خودکار ایجاد کننده و ویرایش کننده در کنار همان رکورد ثبت میشود.
    این کلاس جدید برای این هست که بدانیم هر رکورد چند بار ویرایش شده و چه کسانی ویرایش را انجام داده اند. در حالت قبل فقط آخرین نفر قابل رهگیری بود ولی با این روش همه کسانی که ویرایش کرده اند را رصد میکنید.

    به همین ترتیب متد AuditFields که در تاپیک قبلی هست را هم تغییر میدهیم:



    private void AuditFields()
    {
    ChangeTracker.DetectChanges();


    var userId = _accessor.HttpContext?.User?.Identity?.GetUserId() ;
    var auditUser = _accessor.HttpContext?.User?.Identity?.Name;
    var auditDate = DateTime.Now;
    var ipAddress = HttpContextExtensionsGetRemoteIP.GetRemoteIPAddres s(_accessor.HttpContext).ToString();


    var auditEntries = new List<AuditEntry>();


    foreach (var entry in this.ChangeTracker.Entries<BaseEntity>())
    {
    var auditEntry = new AuditEntry(entry);
    switch (entry.State)
    {
    case EntityState.Added:
    entry.Entity.InsertDate = auditDate;
    entry.Entity.InsertBy = auditUser;


    entry.Entity.InsertedBy = userId;
    entry.Entity.IsActive = true;
    entry.Entity.InsertIpAddress = ipAddress;
    break;


    case EntityState.Modified:
    entry.Entity.UpdateDate = auditDate;
    entry.Entity.UpdateBy = auditUser;
    entry.Entity.UpdatedBy = userId;
    entry.Entity.UpdateIpAddress = ipAddress;


    foreach (var property in entry.Properties)
    {
    auditEntry.TableName = entry.Entity.GetType().Name;
    auditEntry.PersianTableName = EnumExtensions.GetClassDescription(entry.Entity.Ge tType());
    auditEntries.Add(auditEntry);


    string propertyName = property.Metadata.Name;
    string propertyDisplayName = propertyName;
    string propertyPersianDisplayName = EnumExtensions.GetDisplayProperty(property.GetType ());
    if (property.Metadata.IsPrimaryKey())
    {
    auditEntry.KeyValues[propertyName] = property.CurrentValue;
    continue;
    }
    if (property.IsModified)
    {
    auditEntry.UserId = userId;
    auditEntry.UserName = auditUser;
    var clx = auditEntries.Select(x => x.Entry.Entity).FirstOrDefault();
    auditEntry.ChangedColumns.Add(propertyDisplayName) ;
    auditEntry.PChangedColumns.Add(propertyPersianDisp layName);
    auditEntry.AuditType = AuditType.Update;
    auditEntry.OldValues[propertyName] = property.OriginalValue;
    auditEntry.NewValues[propertyName] = property.CurrentValue;
    }
    }


    break;
    case EntityState.Deleted:
    entry.Entity.DeleteDate = auditDate;
    entry.Entity.DeleteBy = auditUser;
    entry.Entity.UpdateIpAddress = ipAddress;
    break;
    }
    }


    if (auditEntries.Any())
    {
    foreach (var auditEntry in auditEntries)
    {
    ChangeLogs.Add(auditEntry.ToAudit());
    }
    }
    }



    متد SaveChanges که قبلا override کرده بودیم به حالت قبل باقی میماند و تغییری ندارد.

    یک نکته اینجا هست جهت یادآوردی عرض میکنم. کلاس BaseEntity یک کلاس عمومی هست که همه کلاسها از اون ارث بری میکنند. همان ستونهای Audit که میبینید به این طریق ایجاد شده اند.



    public class BaseEntity
    {
    [MaxLength(500), Display(Name = "توضیحات : "), JsonPropertyName("توضیحات")]
    public string Description { get; set; }


    [MaxLength(450), Display(Name = "ایجادکننده:")]
    public string InsertBy { get; set; }




    [MaxLength(450), Display(Name = "InsertedByUser")]
    public string InsertedBy { get; set; }


    [MaxLength(450), Display(Name = "UpdatedBy")]
    public string UpdatedBy { get; set; }




    [Display(Name = "زمان ایجاد:")]
    [DefaultValue("CONVERT(DATETIME, CONVERT(VARCHAR(20),GetDate(), 120))")]
    public DateTime InsertDate { get; set; }




    [MaxLength(450), Display(Name = "بروز رسانی کننده:")]
    public string UpdateBy { get; set; }


    [Display(Name = "زمان بروزرسانی:")]
    public DateTime? UpdateDate { get; set; }


    [MaxLength(450), Display(Name = "حذف کننده:")]
    public string DeleteBy { get; set; }


    [Display(Name = "زمان حذف:")]
    public DateTime? DeleteDate { get; set; }


    [Display(Name = "وضعیت فعال / غیر فعال"), JsonPropertyName("IsActive")]
    public bool IsActive { get; set; }


    [Timestamp, JsonIgnore()]
    public byte[] RowVersion { get; set; }


    public string GuidClient { get; set; }


    public string GuidController { get; set; }


    [MaxLength(50)]
    public string InsertIpAddress { get; set; }

    [MaxLength(50)]
    public string UpdateIpAddress { get; set; }
    }




    ممکنه سوال بشه که چرا UpdateBy , UpdateDate روی همه رکوردها مجدد مقدار دهی میشه! دلیلش این هست که من میخواهم هر کاربری که توی فرم داره کار میکنه هم بتونه ببینه این رکورد توسط چه کسی ایجاد شده و هم آخریش ویرایش را بتواند ببیند.
    اون جدول AuditEntry که بعد از هر ویرایش ساخته میشه در واقع یک قسمت مدیریتی هست و قرار نیست که کاربران عادی بهش دسترسی داشته باشند ولی برای اینکه مراجعه به مدیریت را کم کنم این فیلدها رو هم قرار دادم.

    امیدوارم که شفاف توضیح داده باشم و کمکی کرده باشم.
    موفق باشید

تاپیک های مشابه

  1. علت خطای SaveChanges در فرم دوم
    نوشته شده توسط mohamad_torabi در بخش C#‎‎
    پاسخ: 6
    آخرین پست: پنج شنبه 29 اسفند 1392, 15:26 عصر
  2. سوال: خطا در متد SaveChanges
    نوشته شده توسط vira1368 در بخش دسترسی به داده ها (ADO.Net و LINQ و ...)
    پاسخ: 2
    آخرین پست: سه شنبه 25 تیر 1392, 20:28 عصر
  3. پاک کردن متغییرهای post و history بعد از لاگین
    نوشته شده توسط armintirand در بخش PHP
    پاسخ: 9
    آخرین پست: شنبه 13 خرداد 1391, 12:42 عصر
  4. سوال: طراحی History مانند History مرورگر Apple Safari
    نوشته شده توسط NitroPlus در بخش VB.NET
    پاسخ: 3
    آخرین پست: شنبه 26 فروردین 1391, 17:28 عصر
  5. آموزش: کسی می تونه ساخت یه history رو توضیح بده؟
    نوشته شده توسط loseramour در بخش برنامه نویسی در 6 VB
    پاسخ: 2
    آخرین پست: پنج شنبه 17 تیر 1389, 08:18 صبح

قوانین ایجاد تاپیک در تالار

  • شما نمی توانید تاپیک جدید ایجاد کنید
  • شما نمی توانید به تاپیک ها پاسخ دهید
  • شما نمی توانید ضمیمه ارسال کنید
  • شما نمی توانید پاسخ هایتان را ویرایش کنید
  •