Base solution for your next web application
Open Closed

adding users to a custom entity with a many to many relation #1047


User avatar
0
patrickglaudemans created

Hi There!

As mentioned in the header I have a custom entity which is some sort of group to which I want to be able to add abpusers. In public class User : AbpUser<Tenant, User> (part of zero in core lib) I added: public List<ContentGroup> ContentGroups { get; set; } and in the custom entity: public List<User> Users { get; set; }

Entity framework created link tables and so far so good. But - no records are inserted after a contentGroup.Users.Add(existingUser) check this piece of code: var user = await UserManager.GetUserByIdAsync(System.Convert.ToInt64(selection)); contentGroup.Users.Add(user); if (contentGroup != null) { await _contentGroupRepository.UpdateAsync(contentGroup);
}

etc.

In the same entity I have another property Reports that is setup exact the same way: it has Reports and Report has ContentGroups (many to many). It works with the report entity..

I think it has something to do with the entity user which is not attached to the build in repository. Can you help me out?

Thanks, P


11 Answer(s)
  • User Avatar
    0
    patrickglaudemans created

    My last sentence was not quite clear. What I ment to say is that is has something to do with the user entity not being monitored for changes by the Entity Framework.

    Halil any comments? Thanks, Patrick

  • User Avatar
    0
    hikalkan created
    Support Team

    Hi Partick,

    User has not a special case. I should know if this code runs in an app service? How do you get contentGroup object? Can you share the complete method body?

    Also, your code seems has another problem: You have this line:

    contentGroup.Users.Add(user);
    

    Which contentGroup can not be null (if it's null, you get an exception as you know). But after that you are checking if it's null. One thing more; No need to use _contentGroupRepository.UpdateAsync since ABP automatically updates when UOW complete.

    var user = await UserManager.GetUserByIdAsync(System.Convert.ToInt64(selection));
    contentGroup.Users.Add(user);
    if (contentGroup != null)
    {
        await _contentGroupRepository.UpdateAsync(contentGroup); 
    }
    
  • User Avatar
    0
    patrickglaudemans created

    Hi,

    Thanks for your reply. Last time I provided incomplete code for the example - that's where the null check failed but that is irrelevant. Here is, however, the complete method code. Still the users are not get saved, al other properties like reports, do! THis code is in exact the same appservice structure like recommended in manual etc.

    public async Task UpdateContentGroupMembers(UpdateContentGroupMembersInput input)
            {
    
                if (input != null)
                {
                    ContentGroup contentGroup = null;
                    if (input.ReportMembers != null)
                    {
                        if (input.ReportMembers.Any())
                        {
                            contentGroup = _contentGroupRepository.GetAll()
                                .Include(c=>c.Reports)
                                .Include(c=>c.Users).FirstOrDefault();
                            if (contentGroup != null)
                            {
                                //check if selection is in saved selection - if not add
                                foreach (var selection in input.ReportMembers)
                                {
                                    if (contentGroup.Reports.Find(r => r.Id == selection) == null)
                                    {
                                        //selected but not yet in saved selection: add
                                        contentGroup.Reports.Add(_reportRepository.Get(selection));
                                    }
                                }
                                //check if selection is in saved
                                foreach (var savedSelection in contentGroup.Reports)
                                {
                                    if (!input.ReportMembers.Contains(savedSelection.Id))
                                    {
                                        //in saved but not selected anymore: delete
                                        contentGroup.Reports.Remove(savedSelection);
                                    }
                                }  
                            }
                        }
    
                        if (input.UserMembers != null)
                        {
                            if (input.UserMembers.Any())
                            {
                                if (contentGroup == null)
                                {
                                    contentGroup = _contentGroupRepository.GetAll()
                                        .Include(c => c.Reports)
                                        .Include(c => c.Users).FirstOrDefault();
                                }
                                if (contentGroup != null)
                                {
                                    //check if selection is in saved selection - if not add
                                    foreach (var selection in input.UserMembers)
                                    {
                                        if (contentGroup.Users.Find(r => r.Id == selection) == null)
                                        {
                                            //selected but not yet in saved selection: add
                                            var user = await UserManager.GetUserByIdAsync(System.Convert.ToInt64(selection));
                                            contentGroup.Users.Add(user);
                                        }
                                    }
                                    //check if selection is in saved
                                    foreach (var savedSelection in contentGroup.Users)
                                    {
                                        if (!input.UserMembers.Contains(System.Convert.ToInt32(savedSelection.Id)))
                                        {
                                            //in saved but not selected anymore: delete
                                            contentGroup.Users.Remove(savedSelection);
                                        }
                                    }
    
                                }
                            }
                            contentGroup.Users.Add(new User());
                        }
                    }
    
                   
    
    
    
                    if (contentGroup != null)
                    {
                        await _contentGroupRepository.UpdateAsync(contentGroup);    
                    }
                    else
                    {
                        //nothing to do - bye
                    }
                }
    
            }
    

    Best,

  • User Avatar
    0
    patrickglaudemans created

    Hi,

    contentGroup.Users.Add(new User());

    can be ignored - it was for testing p.

  • User Avatar
    0
    hikalkan created
    Support Team

    Hi,

    Your code seems fine when I check it by eyes. Are you sure that your many-to-many configuration is properly done in entity framework side. If you are sure them all, we will simulate a similar code in our project. In that case, please send your entitiy definitions and dbcontext code to our email.

    Side notes for a better and shorter code:

    • input can not be null in ABP, it's automatically checked.
    • input.ReportMembers != null && input.ReportMembers.Any() can be combined as !input.ReportMembers.IsNullOrEmpty() it's an extension method of ABP.
  • User Avatar
    0
    patrickglaudemans created

    Thanks! Let me check again. If it keeps on failing I will sent requested code items.

  • User Avatar
    0
    patrickglaudemans created

    Hi,

    This becomes severe. Need to install this onto production.

    remember: for one contentgroup: reports are saved properly, users don't..

    I hope you can help me out!

    You requested for code. Here it is:

    entity contentgroup

    public class ContentGroup : EntityBase
        {
            public virtual List<Report> Reports { get; set; }
            public virtual List<User> Users { get; set; }
            [MaxLength(LongStringSizeMax)]
            [Required]
            public string Purpose { get; set; }
            //todo: This should be a list containing all site reports admins -> request FO change
            public string DataOwner { get; set; }
            public virtual SiteOffice Site { get; set; }
            
            public int SiteId { get; set; }
        }
    

    entity user (abp, changed)

    public class User : AbpUser<Tenant, User>
        {
    
            public User()
            {
                
            }
            public const int MinPlainPasswordLength = 6;
    
            public virtual Guid? ProfilePictureId { get; set; }
    
            public virtual bool ShouldChangePasswordOnNextLogin { get; set; }
            
            /// <summary>
            /// Creates admin <see cref="User"/> for a tenant.
            /// </summary>
            /// <param name="tenantId">Tenant Id</param>
            /// <param name="emailAddress">Email address</param>
            /// <param name="password">Password</param>
            /// <returns>Created <see cref="User"/> object</returns>
            public static User CreateTenantAdminUser(int tenantId, string emailAddress, string password)
            {
                return new User
                       {
                           TenantId = tenantId,
                           UserName = AdminUserName,
                           Name = AdminUserName,
                           Surname = AdminUserName,
                           EmailAddress = emailAddress,
                           Password = new PasswordHasher().HashPassword(password)
                       };
            }
    
            public static string CreateRandomPassword()
            {
                return Guid.NewGuid().ToString("N").Truncate(16);
            }
            public virtual List<ContentGroup> ContentGroups { get; set; }
        }
    }
    

    report entity

    public class Report : EntityBase
        {
    
            public Report()
            {
                MetaDataItems = new EditableList<MetaDataItem>();
                ReportTableCommands = new List<ReportTableCommand>();
                ReportParameters = new List<ReportParameter>();
            }
    
            public DateTime? Uploaded { get; set; }
            
            public User UploadBy { get; set; }
    
            public virtual List<ContentGroup> ContentGroups { get; set; }
    
            [Required]
            public virtual SiteOffice Site { get; set; }
            public int SiteId { get; set; }
            public  List<MetaDataItem> MetaDataItems { get; set; }
            public virtual List<ReportTableCommand> ReportTableCommands { get; set; }
            public virtual List<ReportParameter> ReportParameters { get; set; }
    
            [MaxLength(MidStringSizeMax)]
            [Required]
            public string ReportApplication { get; set; }
    
            public ReportApplicationType ReportApplicationType { get; set; }
    
            public bool Active { get; set; }
    
            public DateTime? ReportOriginLastModifDate { get; set; }
            public string ReportOriginFileName { get; set; }
            public virtual Guid? ReportBlobId { get; set; }
            
    
        }
    

    and EF code

    public class BIDbContext : AbpZeroDbContext<Tenant, Role, User>
        {
            /* Define an IDbSet for each entity of the application */
    
            public virtual IDbSet<Report> Reports { get; set; }
    
            public virtual IDbSet<ContentGroup> ContentGroups { get; set; }
            public virtual IDbSet<BinaryObject> BinaryObjects { get; set; }
            public virtual IDbSet<DataSource> DataSources { get; set; }
            public virtual IDbSet<MetaDataItem> MetaDataItems { get; set; }
            public virtual IDbSet<ReportTableCommand>  ReportTableCommands { get; set; }
            public virtual IDbSet<SiteOffice> Sites { get; set; }
            public virtual IDbSet<ReportParameter> ReportParameters { get; set; }
            public virtual IDbSet<Job> Jobs { get; set; }
    
            //leave this entity at the end of the list
            public virtual IDbSet<ExtendedAuditLog> ExtendedAuditLogs { get; set; }
    
            /* Setting "Default" to base class helps us when working migration commands on Package Manager Console.
             * But it may cause problems when working Migrate.exe of EF. ABP works either way.         * 
             */
            public BIDbContext()
                : base("Default")
            {
    
            }
    
            /* This constructor is used by ABP to pass connection string defined in BIDataModule.PreInitialize.
             * Notice that, actually you will not directly create an instance of BIDbContext since ABP automatically handles it.
             */
            public BIDbContext(string nameOrConnectionString)
                : base(nameOrConnectionString)
            {
    
            }
    
            /* This constructor is used in tests to pass a fake/mock connection.
             */
            public BIDbContext(DbConnection dbConnection)
                : base(dbConnection, true)
            {
    
            }
    
    #if DEBUG
    
            protected override bool ShouldValidateEntity(DbEntityEntry entityEntry)
            {
                return base.ShouldValidateEntity(entityEntry);
            }
    
    
            public override int SaveChanges()
            {
                var result = 0;
                try
                {
                    result = base.SaveChanges();
                }
                catch (DbEntityValidationException ex)
                {
                    StringBuilder sb = new StringBuilder();
    
                    foreach (var failure in ex.EntityValidationErrors)
                    {
                        sb.AppendFormat("{0} failed validation\n", failure.Entry.Entity.GetType());
                        foreach (var error in failure.ValidationErrors)
                        {
                            sb.AppendFormat("- {0} : {1}", error.PropertyName, error.ErrorMessage);
                            sb.AppendLine();
                        }
                    }
                    var s = sb.ToString();
                    throw new DbEntityValidationException(
                        "Entity Validation Failed - errors follow:\n" +
                        sb.ToString(), ex
                    ); // Add the original exception as the innerException
                }
    
                return result;
            }
    
    
            public override Task<int> SaveChangesAsync()
            {
                try
                {
                    return base.SaveChangesAsync();
                }
                catch (DbEntityValidationException ex)
                {
                    StringBuilder sb = new StringBuilder();
    
                    foreach (var failure in ex.EntityValidationErrors)
                    {
                        sb.AppendFormat("{0} failed validation\n", failure.Entry.Entity.GetType());
                        foreach (var error in failure.ValidationErrors)
                        {
                            sb.AppendFormat("- {0} : {1}", error.PropertyName, error.ErrorMessage);
                            sb.AppendLine();
                        }
                    }
    
                    throw new DbEntityValidationException(
                        "Entity Validation Failed - errors follow (erin gefrot):\n" +
                        sb.ToString(), ex
                    ); // Add the original exception as the innerException
                }
            }
    
    #endif
        }
    
  • User Avatar
    0
    patrickglaudemans created

    Hi,

    Sorry but this was not solved yet :-( It seems at first sight because it's actually working fine in de entity framework seed. From within the application layer, it's not! :shock:

    What do you guys need from me to solve this?

    by debugging EF I can tell EF is not registering a change after adding a user to an entity group. I tested this with intelli trace and with:

    public BIDbContext(string nameOrConnectionString)
                : base(nameOrConnectionString)
            {
                this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
            }
    

    As you can see it is adding the reports but no go for the users :!: :

    UPDATE [dbo].[ContentGroups]
    SET [Purpose] = @0, [DataOwner] = NULL, [SiteId] = @1, [Description] = @2, [IsDeleted] = @3, [DeleterUserId] = NULL, [DeletionTime] = NULL, [LastModificationTime] = @4, [LastModifierUserId] = @5, [CreationTime] = @6, [CreatorUserId] = NULL
    WHERE ([Id] = @7)
    
    -- @0: 'First testing of crud operations' (Type = String, Size = 255)
    
    -- @1: '1' (Type = Int32)
    
    -- @2: 'General First ContentGroups 5' (Type = String, Size = 255)
    
    -- @3: 'False' (Type = Boolean)
    
    -- @4: '7/1/2016 2:48:54 PM' (Type = DateTime2)
    
    -- @5: '2' (Type = Int64)
    
    -- @6: '6/20/2016 10:37:33 AM' (Type = DateTime2)
    
    -- @7: '1' (Type = Int32)
    
    -- Executing asynchronously at 7/1/2016 4:48:54 PM +02:00
    
    -- Completed in 1 ms with result: 1
    
    
    
    INSERT [dbo].[ReportContentGroups]([Report_Id], [ContentGroup_Id])
    VALUES (@0, @1)
    
    -- @0: '3' (Type = Int32)
    
    -- @1: '1' (Type = Int32)
    
    -- Executing asynchronously at 7/1/2016 4:48:54 PM +02:00
    
    -- Completed in 1 ms with result: 1
    
    
    
    INSERT [dbo].[ReportContentGroups]([Report_Id], [ContentGroup_Id])
    VALUES (@0, @1)
    
    -- @0: '4' (Type = Int32)
    
    -- @1: '1' (Type = Int32)
    
    -- Executing asynchronously at 7/1/2016 4:48:54 PM +02:00
    
    -- Completed in 0 ms with result: 1
    
    
    
    Closed connection at 7/1/2016 4:48:54 PM +02:00
    
  • User Avatar
    0
    patrickglaudemans created

    Hi,

    Did a little test.

    public class TestAppService : BIAppServiceBase, ITestAppService
        {
            private IRepository<ContentGroup> _contentRepository;
    
            
    
            public TestAppService(IRepository<ContentGroup> contentRepository
                )
            {
                _contentRepository = contentRepository;
            }
    
            public async Task TestIt()
            {
                //some plain test
    
    
                //take contentgroup
                var contentGroup = _contentRepository.GetAll().FirstOrDefault();
                //take user
                var user = await UserManager.GetUserByIdAsync(3); //known user ;-)
                //add user to group
                contentGroup.Users.Add(user);
                //update
                _contentRepository.Update(contentGroup);
    
                //very simple - this works! What's the difference?
    
            }
        }
    

    That worked - what's the difference? Mixing async with non async :roll:

  • User Avatar
    0
    ismcagdas created
    Support Team

    I couldn't figure out the difference actually ? Do you still have the problem with your real service ?

  • User Avatar
    0
    patrickglaudemans created

    Hi,

    It is fixed. Fixed because of the update to latest version.

    Thanks for all hints and tips