Activities of "selinkoykiran"

Of course , could be , Here are our configurations :

**OdmsDbContextModelCreatingExtensions inside: **

            /* Configure all entities here. */
            builder.Entity<Model>(b =>
            {
                b.ToTable(OdmsDbProperties.DbTablePrefix + "Models", OdmsDbProperties.DbSchema);
                b.ConfigureByConvention();
                b.Property(x => x.SchemaName).HasMaxLength(ModelConsts.MaxSchemaNameLength).HasColumnName(nameof(Model.SchemaName)).IsRequired();
                b.Property(x => x.ServerName).HasMaxLength(ModelConsts.MaxServerNameLength).HasColumnName(nameof(Model.ServerName)).IsRequired();
                b.Property(x => x.DatabaseType).HasMaxLength(ModelConsts.MaxDatabaseTypeLength).HasColumnName(nameof(Model.DatabaseType));
                b.Property(x => x.Password).HasMaxLength(ModelConsts.MaxEncryptedPasswordLength).HasColumnName(nameof(Model.Password));
                b.Property(x => x.Version).HasMaxLength(ModelConsts.MaxVersionLength).HasColumnName(nameof(Model.Version));
                // Relations
                b.HasMany<Export>(m => m.Exports).WithOne(e => e.Model).HasForeignKey(e => e.ModelId).IsRequired();
                b.HasMany<Import>(m => m.Imports).WithOne(i => i.Model).HasForeignKey(i => i.ModelId).IsRequired();
                b.HasMany<Source>(m => m.Sources).WithOne(s => s.Model).HasForeignKey(s => s.ModelId).IsRequired();
                // Index
                b.HasIndex(x => new { x.SchemaName });
                b.Navigation(x => x.Exports).HasField("_exports");
                b.Metadata.FindNavigation("Exports").SetPropertyAccessMode(PropertyAccessMode.Field);
                
            });

            builder.Entity<Export>(b =>
            {
                b.ToTable(OdmsDbProperties.DbTablePrefix + "Exports", OdmsDbProperties.DbSchema);
                b.ConfigureByConvention();
                b.Property(x => x.ModelId).HasColumnName(nameof(Export.ModelId)).IsRequired();
                b.Property(x => x.OperationId).HasColumnName(nameof(Export.OperationId)).IsRequired();
                b.Property(x => x.ExportType).HasMaxLength(ExportConsts.MaxExportTypeLength).HasColumnName(nameof(Export.ExportType)).IsRequired();
                b.Property(x => x.Result).HasMaxLength(ExportConsts.MaxResultLength).HasColumnName(nameof(Export.Result)).IsRequired();
                // Value object
                b.OwnsOne(x => x.ExportFile, p =>
                {
                    p.Property(x => x.StorageId).HasColumnName(ExportConsts.ExportFileIdColumnName);
                    p.Property(x => x.Name).HasColumnName(ExportConsts.ExportFileNameColumnName);
                    p.Ignore(x => x.NameOnly);
                    p.Ignore(x => x.FullName);
                    p.Ignore(x => x.ModelType);
                    // Index
                    p.HasIndex(x => x.Name);
                }).Navigation(x => x.ExportFile).IsRequired();

                  
            });

**Domain Manager layer inside : **

        public virtual async Task HardDeleteExportAsync(string schemaName, string serverName, Guid fileId)
        {
            Check.NotNullOrWhiteSpace(schemaName, nameof(schemaName), ModelConsts.MaxSchemaNameLength);
            Check.NotNullOrWhiteSpace(serverName, nameof(serverName), ModelConsts.MaxServerNameLength);

            // Get model from database with conditional exports 
            var model = await ModelRepository.FindWithExportDetailAsync(
                schemaName,
                serverName,
                x => x.IsDeleted == true && x.ExportFile.StorageId == fileId,
                includeDetails: true // includeDetails: Set true to include all children of this aggregate
            );

            if (model == null)
            {
                throw new ModelDoesNotExistException(
                    schemaName: schemaName,
                    serverName: serverName
                );
            }

            //NOTE => below code not working if we have a cascade delete relation but we want to delete only children , not with parent. we need to do it from repository layer
            model.RemoveAllExports(); //model.HardDeleteExport(fileId);

            await ModelRepository.UpdateAsync(model,true);
        }

FindWithExportDetailAsync inside which is inside repository layer :

        public virtual async Task<Model> FindWithExportDetailAsync(string schemaName, string serverName, Expression<Func<Export, bool>> expression, bool includeDetails = true, CancellationToken cancellationToken = default)
        {
            return await (await GetDbSetAsync())
                .IncludeExportDetail(expression, includeDetails)  // Include only exports
                .Where(x => x.SchemaName == schemaName && x.ServerName == serverName)
                .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); // Returns null if not found
        }

IncludeExportDetail method inside :


        public static IQueryable<Model> IncludeExportDetail(this IQueryable<Model> queryable, Expression<Func<Export, bool>> predicate, bool include = true)
        {
            if (!include)
            {
                return queryable;
            }

            return queryable
                .Include(
                x => x.Exports.AsQueryable()
                .Where(predicate));
        }```

Model aggregate root and removeExport method

public class Model : AuditedAggregateRoot<Guid>, IMultiTenant // Using Guid type as the Id key
{
    public Guid? TenantId { get; protected set; }

    [NotNull]
    public virtual string SchemaName { get; protected set; } // Value object can be created for primitive types. There is no such requirement in the web API. Inputs are validated in the HTTP layer.

    [NotNull]
    public virtual string ServerName { get; protected set; } // Value object can be created for primitive types. There is no such requirement in the web API. Inputs are validated in the HTTP layer.

    public virtual DatabaseType DatabaseType { get; protected set; }

    public virtual string Password { get; protected set; }

    [NotNull]
    public virtual string Version { get; protected set; }

    // Don't expose mutable collections in an aggregate
    public virtual IReadOnlyCollection<Export> Exports
    {
        get
        {
            return _exports?.ToList(); // Paged operation may return without sub collection. If null then do not turn into list 
        }
    }

    private readonly ICollection<Export> _exports;

    public virtual void RemoveAllExports()
    {
        // NOTE => Clear ,or new, or removeall not working when dealing with ef core because of it only clear the list , and parent doesn't know about the relational children deletion.
        _exports.Clear();
    }
    }

Yes, you should already update the aggregate root normally after children entities changes. So I've tried and that's the problem which is not working even if updating aggregate root. I don't think it's because of private field because we are using backing fields actually , and in a normal ef core project it's working as expected for example :

Aggregate

Entity:

And the operation below is working :

Because of this simple ef core project is working without problem , we think that if this issue about abp efcore implementation? I can show our configuration anytime , adding and saving changes working perfectly but in deletion , removing step with that backing fields , could it be an issue ?

Don't understand the last message , you'll be checking the issue right ?

Thanks , I've already tried this approach but this time no hangfire jobs are registered. So for now I'll be waiting for the other issue solution I think. As a result of this topic , I understand that we shouldn't override a module if there isn't any problem, right ? Thank you.

Ok thank you, we'll be calling SetAsync() method before.

Actually I don't want to change options I want to change for example OnApplicationInitialization method behaviour because of an internal module error . I've also opened the issue related with this topic , you can check the specific case from below link :

https://support.abp.io/QA/Questions/2578/AbpBackgroundWorkersHangfireModule-exception-without-using-hangfire-configuration

But I'll try your module approach. Note => If the issue in the link above will be fixing , we don't even need to change the module.

Hello , Normally I shouldn't reach the entity repository like that from DDD manner , It's not a best practice. Because I should delete them from aggregate repository, so normally we don't have any repositories for entity itself. Anyway I've tried this approach , too. But it didn't work.

Ok, thanks for the information.

Is this the abp design or it comes from Ef core ORM usage ? Because normally when you directly listen table changes from sql we can catch the changes. I understand that abp's pre-built events do not support this cascade deletion , right ?

Hello , Is there any spesific reason to choose netstandard as target framework ? If we change all layers to .net5 , will we run into an unexpected error ?

Thank you.

Showing 11 to 20 of 40 entries
Made with ❤️ on ABP v9.1.0-rc.1. Updated on January 17, 2025, 14:13