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)
-
0
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
-
0
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); }
-
0
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,
-
0
Hi,
contentGroup.Users.Add(new User());
can be ignored - it was for testing p.
-
0
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.
-
0
Thanks! Let me check again. If it keeps on failing I will sent requested code items.
-
0
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 }
-
0
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
-
0
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:
-
0
I couldn't figure out the difference actually ? Do you still have the problem with your real service ?
-
0
Hi,
It is fixed. Fixed because of the update to latest version.
Thanks for all hints and tips