Hi, In the UOW there are some deletes of child objects from report. These deletes should be deleted, not soft deleted.
I should I force to delete these in stead of softdelete?
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
}
Hi,
Yep.
namespace Intertek.BI.ApplicationService
{
[DependsOn(
typeof(BIDataModule),
typeof(BIApplicationModule),
typeof(BIReportingCoreModule)
)]
public class ApplicationServiceModule : AbpModule
{
public override void PreInitialize()
{
//Suppress authentication and permission checking for the console application
IocManager.Register<IAbpSession, ApplicationServiceSession>();
IocManager.Register<IPermissionChecker, NullPermissionChecker>();
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
}
}
The error disappeared after having commented out the registration of that particular nul checker in above code! (strange?)
But I fixed it by using log4net directly. It works better that way with Quartz common logging.
Thanks anyway!
Hi,
Don't see any shortcomings in your code.
I'have the same issue for one of my other entities with two many to many relations. One list got saved and the other does not! Check: [http://forum.aspnetboilerplate.com/viewtopic.php?f=5&t=2458&p=5695&hilit=many#p5695])
Perhaps hikalkan can help us out!
Hi,
what code do you use to actually add the objects?
Hi,
In your scenario it should be enough to define property public virtual ICollection<Country> Countries { get; set; } in Provider class and public virtual ICollection<Provider> Providers { get; set; } in Country class
EF will setup an extra link table by itsself and you're able to add many countries to provider and many providers to country just by adding them in the collection normally save them.
Former error got solved by adding ExtendedAuditLog to BIDbContext
I'm now capable of logging custommade errors in the same table without having to create new entity and appservice etc.
Thanks,
Hi,
I extended entity AuditLog:
public class ExtendedAuditLog : AuditLog,
{
public string Entity { get; set; }
public int EntityId { get; set; }
public Guid EntityGuid { get;}
}
This wil cause an inheritance situation in EF with discriminator field - works ok. But the AuditLogAppService is now broken :? with dep error: Can't create component 'Intertek.BI.Auditing.AuditLogAppService' as it has dependencies to be satisfied.
'Intertek.BI.Auditing.AuditLogAppService' is waiting for the following dependencies:
appservice code (only with changed entity auditlog > extendedauditlog)
[DisableAuditing]
[AbpAuthorize(AppPermissions.Pages_Administration_AuditLogs)]
public class AuditLogAppService : BIAppServiceBase, IAuditLogAppService
{
private readonly IRepository<ExtendedAuditLog, long> _auditLogRepository;
private readonly IRepository<User, long> _userRepository;
private readonly IAuditLogListExcelExporter _auditLogListExcelExporter;
public AuditLogAppService(IRepository<ExtendedAuditLog, long> auditLogRepository, IRepository<User, long> userRepository, IAuditLogListExcelExporter auditLogListExcelExporter)
{
_auditLogRepository = auditLogRepository;
_userRepository = userRepository;
_auditLogListExcelExporter = auditLogListExcelExporter;
}
public async Task<PagedResultOutput<AuditLogListDto>> GetAuditLogs(GetAuditLogsInput input)
{
var query = CreateAuditLogAndUsersQuery(input);
var resultCount = await query.CountAsync();
var results = await query
.AsNoTracking()
.OrderBy(input.Sorting)
.PageBy(input)
.ToListAsync();
var auditLogListDtos = ConvertToAuditLogListDtos(results);
return new PagedResultOutput<AuditLogListDto>(resultCount, auditLogListDtos);
}
public async Task<FileDto> GetAuditLogsToExcel(GetAuditLogsInput input)
{
var auditLogs = await CreateAuditLogAndUsersQuery(input)
.AsNoTracking()
.OrderByDescending(al => al.AuditLog.ExecutionTime)
.ToListAsync();
var auditLogListDtos = ConvertToAuditLogListDtos(auditLogs);
return _auditLogListExcelExporter.ExportToFile(auditLogListDtos);
}
private static List<AuditLogListDto> ConvertToAuditLogListDtos(List<AuditLogAndUser> results)
{
return results.Select(
result =>
{
var auditLogListDto = result.AuditLog.MapTo<AuditLogListDto>();
auditLogListDto.UserName = result.User == null ? null : result.User.UserName;
auditLogListDto.ServiceName = StripNameSpace(auditLogListDto.ServiceName);
return auditLogListDto;
}).ToList();
}
private IQueryable<AuditLogAndUser> CreateAuditLogAndUsersQuery(GetAuditLogsInput input)
{
var query = from auditLog in _auditLogRepository.GetAll()
join user in _userRepository.GetAll() on auditLog.UserId equals user.Id into userJoin
from joinedUser in userJoin.DefaultIfEmpty()
where auditLog.ExecutionTime >= input.StartDate && auditLog.ExecutionTime < input.EndDate
select new AuditLogAndUser {AuditLog = auditLog, User = joinedUser};
query = query
.WhereIf(!input.UserName.IsNullOrWhiteSpace(), item => item.User.UserName.Contains(input.UserName))
.WhereIf(!input.ServiceName.IsNullOrWhiteSpace(), item => item.AuditLog.ServiceName.Contains(input.ServiceName))
.WhereIf(!input.MethodName.IsNullOrWhiteSpace(), item => item.AuditLog.MethodName.Contains(input.MethodName))
.WhereIf(!input.BrowserInfo.IsNullOrWhiteSpace(), item => item.AuditLog.BrowserInfo.Contains(input.BrowserInfo))
.WhereIf(input.MinExecutionDuration.HasValue && input.MinExecutionDuration > 0, item => item.AuditLog.ExecutionDuration >= input.MinExecutionDuration.Value)
.WhereIf(input.MaxExecutionDuration.HasValue && input.MaxExecutionDuration < int.MaxValue, item => item.AuditLog.ExecutionDuration <= input.MaxExecutionDuration.Value)
.WhereIf(input.HasException == true, item => item.AuditLog.Exception != null && item.AuditLog.Exception != "")
.WhereIf(input.HasException == false, item => item.AuditLog.Exception == null || item.AuditLog.Exception == "");
return query;
}
private static string StripNameSpace(string serviceName)
{
if (serviceName.IsNullOrEmpty() || !serviceName.Contains("."))
{
return serviceName;
}
var lastDotIndex = serviceName.LastIndexOf('.');
return serviceName.Substring(lastDotIndex + 1);
}
public void SaveEventExcecutionResult(AuditLogEditDto input)
{
var auditLog = input.MapTo<ExtendedAuditLog>();
_auditLogRepository.Insert(auditLog);
}
}
Can I do something about that? Or should I reverse this and trying to change AuditlogProvider instead (which is nog quitte obvious to me yet).
I alway answer my own questions, it seems.. :mrgreen:
I came across this post concerning the same issue [ #143@9631572b-914f-48a0-b397-5fc4249f15fb ])
Hi,
Way to go! Yes, we should not use entity objects in dto's and yes, you can add the Id of an entity in the definition of an entity like copied below. Then you can set the proper id yourself without the use of instances of the object, in this case report.
Furthermore, set the property Report to virtual when you want it to be lazy loaded!
public class Job : EntityBase
{
public int ReportId { get; set; }
public virtual Report Report { get; set; }
public string CronSchedule { get; set; }
public ReportOutputType OutputType { get; set; }
public DateTime LastRun { get; set; }
public bool Active { get; set; }
public string Destination { get; set; }
}