Base solution for your next web application
Open Closed

Entity History #7140


User avatar
0
jtallon created

I created a new entity. I would like to view the change logs for it.

It currently looks like this:

public class CourseDocument : FullAuditedEntity
    {
        public int CourseId { get; set; }
        public Course Course { get; set; }
        public int DocId { get; set; }
        public Doc Doc { get; set; }
    }

I've added the type to the tracked types:

namespace Bowie.Framework.Portal.EntityHistory
{
    public static class EntityHistoryHelper
    {
        public const string EntityHistoryConfigurationName = "EntityHistory";

        public static readonly Type[] HostSideTrackedTypes =
        {
            typeof(CourseDocument)
        };

        public static readonly Type[] TenantSideTrackedTypes =
        {
            typeof(CourseDocument)
        };

        public static readonly Type[] TrackedTypes =
            HostSideTrackedTypes
                .Concat(TenantSideTrackedTypes)
                .GroupBy(type => type.FullName)
                .Select(types => types.First())
                .ToArray();
    }
}

I then added the migration and updated the DB.

I was hoping to see the DocId and CourseID with their new values. Instead I just get no sign of them:

The DocId and CourseID appear correctly in the DB:


9 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team

    What is your abp version? What is the Zero version? Ef 6 or ef core?

    Please share the relevant code for the inserted entity.

  • User Avatar
    0
    ryancyq created
    Support Team

    Hi @jtallon, have you uncommented these lines for EntityHistory as part of the configuration?

    https://github.com/aspnetzero/aspnet-zero-core/blob/e70c1dfad2f5f634a975948792239cc0bd1bfc10/aspnet-core/src/MyCompanyName.AbpZeroTemplate.EntityFrameworkCore/EntityFrameworkCore/AbpZeroTemplateEntityFrameworkCoreModule.cs#L43-L45

  • User Avatar
    0
    jtallon created

    Hi @ryancyq,

    In my portalEntityFrameworkCoreModule I have uncommented the two lines:

    Configuration.EntityHistory.Selectors.Add("PortalEntities", EntityHistoryHelper.TrackedTypes);
    Configuration.CustomConfigProviders.Add(new EntityHistoryConfigProvider(Configuration));
    

    Hi @maliming,

    I am using ASPNetZero version: 6.8.0 EF: core

    The create/edit code is:

    [AbpAuthorize(AppPermissions.Pages_Courses_Create)]
    private async Task<int> Create(CreateOrEditCourseDto input)
    {
        var course = ObjectMapper.Map<Course>(input);
    
        var newCourseId = await _courseRepository.InsertAndGetIdAsync(course);
        return newCourseId;
    }
    
    [AbpAuthorize(AppPermissions.Pages_Courses_Edit)]
    private async Task<int> Update(CreateOrEditCourseDto input)
    {      
        var courseEntity = ObjectMapper.Map<Course>(input);
        var course = await _courseRepository.UpdateAsync(courseEntity);                
        return (int)input.Id;
    }
    

    The CreateOrEditCourseDto in the code has public IEnumerable<int> DocumentIds { get; set; }

    within my PortalDBContext I have the following:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<CourseDocument>()
         .HasKey(bc => new { bc.CourseId, bc.DocId });
            modelBuilder.Entity<CourseDocument>()
                .HasOne(bc => bc.Course)
                .WithMany(b => b.CourseDocuments)
                .HasForeignKey(bc => bc.CourseId);
            modelBuilder.Entity<CourseDocument>()
                .HasOne(bc => bc.Doc)
                .WithMany(c => c.CourseDocuments)
                .HasForeignKey(bc => bc.DocId);
            modelBuilder.Entity<CourseDocument>().Ignore(c => c.Id);
            
            ///
     }
    
  • User Avatar
    0
    maliming created
    Support Team

    hi @jtallon

    Please share the complete code for the relevant entity and dto class.

    Course
    CourseDocument
    Doc
    CreateOrEditCourseDto
    
  • User Avatar
    0
    jtallon created

    hi @maliming, no problem:

    Course

    using Bowie.Framework.Portal.General;
    using System;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using Abp.Domain.Entities.Auditing;
    using System.Collections.Generic;
    
    namespace Bowie.Framework.Portal.Training
    {
        [Table("Courses")]
        public class Course : FullAuditedEntity
        {
    
            [Required]
            public virtual string CourseName { get; set; }
            public virtual string Description { get; set; }
            
            public ICollection<CourseTrainer> CourseTrainers { get; set; }
            public ICollection<CourseDocument> CourseDocuments { get; set; }
            public ICollection<UserCourse> UserCourses { get; set; }
            public CourseCategory CourseCategory { get; set; }
            public virtual int? CourseCategoryId { get; set; }
        }
    }
    

    CourseDocument

    using Abp.Domain.Entities.Auditing;
    using Bowie.Framework.Portal.Document;
    
    namespace Bowie.Framework.Portal.Training
    {
        public class CourseDocument : FullAuditedEntity
        {
            public int CourseId { get; set; }
            public Course Course { get; set; }
            public int DocId { get; set; }
            public Doc Doc { get; set; }
        }
    }
    

    Doc

    using System;
    using Bowie.Framework.Portal.Authorization.Users;
    using Bowie.Framework.Portal.Training;
    using Bowie.Framework.Portal.General;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using Abp.Domain.Entities.Auditing;
    using System.Collections.Generic;
    using System.Linq;
    using Bowie.Framework.Portal.CommentThreads;
    using JetBrains.Annotations;
    
    namespace Bowie.Framework.Portal.Document
    {
        [Table("Docs")]
        public class Doc : FullAuditedEntity
        {
            [Required]
            public virtual string ReferenceID { get; set; }
            [Required]
            public virtual string Title { get; set; }
            public ICollection<CourseDocument> CourseDocuments { get; set; }
            public ICollection<DocumentTrainer> DocumentTrainers { get; set; }
            public ICollection<DocumentPosition> DocumentPositions { get; set; }
            public ICollection<DocumentApprover> DocumentApprovers { get; set; }
        }
    }
    

    CreateOrEditCourseDto

    
    using System;
    using Abp.Application.Services.Dto;
    using System.ComponentModel.DataAnnotations;
    using System.Collections.Generic;
    
    namespace Bowie.Framework.Portal.Training.Dtos
    {
        public class CreateOrEditCourseDto : EntityDto<int?>
        {
            [Required]
            public string CourseName { get; set; }
            public string Description { get; set; }
            
            public IEnumerable<int> DocumentIds { get; set; }
            public IEnumerable<int> TraineeIds { get; set; } //user
            public IEnumerable<int> TrainerIds { get; set; }
            public int? CourseCategoryId { get; set; }
    
        }
    }
    
  • User Avatar
    0
    maliming created
    Support Team

    ObjectMapper.Map<Course>(input);

    Please share the mapping configuration of automapper

  • User Avatar
    0
    jtallon created

    thanks @maliming, here it is:

    configuration.CreateMap<Course, CreateOrEditCourseDto>()
        .ForSourceMember(course => course.UserCourses, options => options.DoNotValidate())
        .ForSourceMember(course => course.CourseTrainers, options => options.DoNotValidate())
        .ForSourceMember(course => course.CourseDocuments, options => options.DoNotValidate())
        ;
    
    configuration.CreateMap<CreateOrEditCourseDto, Course>()
        .ForMember(course => course.UserCourses, opt => opt.MapFrom(input => input.TraineeIds
            .Select(traineeId => new UserCourse
            {
                CourseId = input.Id ?? 0,
                UserId = traineeId
            }).ToList()))
        .ForMember(course => course.CourseDocuments, opt => opt.MapFrom(src => src.DocumentIds
            .Select(documentId => new CourseDocument
            {
                CourseId = src.Id ?? 0,
                DocId = documentId
            })))
        .ForMember(course => course.CourseTrainers, opt => opt.MapFrom(src => src.TrainerIds
            .Select(trainerId => new CourseTrainer
            {
                CourseId = src.Id ?? 0,
                TrainerId = trainerId
            })))
        ;
        
     configuration.CreateMap<Course, CourseDto>()
        .ForSourceMember(course => course.UserCourses, options => options.DoNotValidate())
        .ForSourceMember(course => course.CourseTrainers, options => options.DoNotValidate())
        .ForSourceMember(course => course.CourseDocuments, options => options.DoNotValidate())
        ;
    
  • User Avatar
    0
    maliming created
    Support Team

    hi @jtallon

    var newCourseId = await _courseRepository.InsertAndGetIdAsync(course);

    CourseDocument won't get CourseId until the transaction is complete. You can consider inserting CourseDocument after getting the CourseId.

    Fake code :

    /*configuration.CreateMap<CreateOrEditCourseDto, Course>().ForMember(course => course.CourseDocuments, opt => opt.Ignore());*/
    
    var course = ObjectMapper.Map<Course>(input);
    var newCourseId = await _courseRepository.InsertAndGetIdAsync(course);
    
    var courseDocuments = input.DocumentIds.Select(x => new CourseDocument()
    {
    	CourseId = newCourseId
    }).ToList();
    foreach (var courseDocument in courseDocuments)
    {
    	await _courseDocumentRepository.InsertAsync(courseDocument);
    }
    
  • User Avatar
    1
    jtallon created

    Thanks maliming, that makes sense. I will try that. I appreciate your patience.