Can you provide me the docs related to this default behaviour of EF Core?
Sure. Here are two documentation that you can refer:
Regards.
Hi @EngincanV,
I have created a 3rd example app, of the User & UserProfile example inside an ABP framework solution: https://cdn.fuzed.app/share/abp/AbpEFCorePlayground.zip
This time the SQL query loop occurs again, within ABP framework, using the same configuration as previous example. So I think this is some framework issue that needs to be fixed.
User entity:
using System; using Volo.Abp.Domain.Entities.Auditing; namespace AbpEFCorePlayground.Users; public class User : FullAuditedAggregateRoot<Guid> { public User( Guid id, string name) { Id = id; Name = name; } private User() { } public string Name { get; set; } public UserProfile? Profile { get; set; } }
UserProfile entity:
using System; using Volo.Abp.Domain.Entities.Auditing; namespace AbpEFCorePlayground.Users; public class UserProfile : FullAuditedEntity<Guid> { public Guid Id { get; set; } public string Bio { get; set; } public Guid UserId { get; set; } public User User { get; set; } }
UsersDataSeedContributor:
using System; using System.Threading.Tasks; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Uow; namespace AbpEFCorePlayground.Users; public class UsersDataSeedContributor : IDataSeedContributor, ITransientDependency { private readonly IUserRepository _userRepository; public UsersDataSeedContributor(IUserRepository userRepository) { _userRepository = userRepository; } [UnitOfWork] public virtual async Task SeedAsync(DataSeedContext context) { User user = new User( id: Guid.Parse("c64b873e-c067-43e0-ae00-07992a880837"), name: "Steff Beckers") { Profile = new UserProfile() { Bio = "Software Developer" } }; await _userRepository.DeleteAsync(x => x.Id == user.Id); await _userRepository.InsertAsync(user); } }
EF entity configuration:
builder.Entity<User>(b => { b.ToTable(AbpEFCorePlaygroundConsts.DbTablePrefix + "Users", AbpEFCorePlaygroundConsts.DbSchema); b.ConfigureByConvention(); //auto configure for the base class props b.HasOne(x => x.Profile) .WithOne(x => x.User) .HasForeignKey<UserProfile>(x => x.UserId) .IsRequired() .OnDelete(DeleteBehavior.Cascade); }); builder.Entity<UserProfile>(b => { b.ToTable(AbpEFCorePlaygroundConsts.DbTablePrefix + "UserProfiles", AbpEFCorePlaygroundConsts.DbSchema); b.ConfigureByConvention(); //auto configure for the base class props }); Configure<AbpEntityOptions>(options => { options.Entity<User>(e => { e.DefaultWithDetailsFunc = query => query.Include(x => x.Profile); }); });
App service:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AbpEFCorePlayground.Users; public class UsersAppService : AbpEFCorePlaygroundAppService, IUsersAppService { private readonly IUserRepository _userRepository; public UsersAppService(IUserRepository userRepository) { this._userRepository = userRepository; } public async Task DeleteUserProfileAsync() { var user = await _userRepository.GetAsync(x => x.Id == Guid.Parse("c64b873e-c067-43e0-ae00-07992a880837")); user.Profile = null; await _userRepository.UpdateAsync(user); } }
Best regards, Steff Beckers
The problem is that you are setting the UserId as not nullable in the UserProfile, and this restricts you to set the user.Profile as null. This is default behaviour of EF Core. There is no problem in the ABP Framework side.You can try the same in a plain asp.net core application.
Also, the cascade-delete does not apply in the update scenarios. For example, if you delete the related user, the relevant profile will also be deleted, because of the cascade-delete option. However, setting a user's profile as null is not related to that. To be able to set it null, you should make it as optional. And if you want to ensure, setting it null also should delete the dependent 1-1 entity you should update your code according to that.
I've tested IsRequired(false) and it solves the query issue, but the Author itself isn't deleted. An author can't exist on its own in this case and now we have a "ghost" Author record, since BookId is now allowed to be NULL. So I don't think we should use IsRequired(false). IsRequired configures whether this is a required relationship (i.e. whether the foreign key property(s) can be assigned null). It doesn't ensure that a Book requires an Author. It only ensures that the FK is required/can't be NULL => an Author must have a BookId.
In your design, it's normal not to delete the author itself because while you are setting the author as null it just ensures it does not have any reference for the related author. So, the cascade delete option of EF Core does not work in that case.
I've created a new and better (relation wise) example using the ASP.NET Core Web API template, where the removal works as expected: https://cdn.fuzed.app/share/abp/EFCorePlayground.zip
Yes, this is what it should look like. Now you are defining the UserId as required and made it as not nullable, in the previous example, it was nullable and in that case, a SQL statement loop occurred.
Hi, can you please share what you have done to override the localization entries? For example, you might be followed this documentation, but the options might be configured between the debug pragma statements (e.g. #if DEBUG
)
Or did you change the localization entries on the angular UI side? (https://abp.io/docs/latest/framework/ui/angular/localization) If you did, then the environment might not be set correctly.
In your db configuration it seems you set the one-to-one relationship as required, however, this is not what you want (because you made the Author
as nullable (Author?
)), so you should make the following change (set IsRequired(false)):
builder.Entity<Book>(b =>
{
b.ToTable(AbpHardDeleteConsts.DbTablePrefix + "Books", AbpHardDeleteConsts.DbSchema);
b.ConfigureByConvention(); //auto configure for the base class props
b.HasOne(x => x.Author).WithOne(x => x.Book)
.HasForeignKey<Author>(x => x.BookId)
- .IsRequired()
+ .IsRequired(false)
.OnDelete(DeleteBehavior.Cascade);
});
If you don't change the configuration like that, you can't set the Author as null, and SQL query will be generated over and over again.
Hi, normally it should not be like that. It seems there is a mis-configuration in the database side. I will check your project and write you back asap.
Hi, thank you very much. I imagine this applies to all entities, but I would like to do it for just one entity.
Hi, when you do this the related placeholder will be applied to all of your index.js files. However, since you will only write your custom code for a single index.js file, the other placeholders will be empty and they will be shown as comment blocks. So, you will be able to write your custom code per file by your choice.
Hi, yes you can use the custom code support to add an entity-action to the data table directly. You can apply the following steps:
1-) Edit Frontend.Mvc.Page.index.js.txt template and add custom-code block placeholder:
//<suite-custom-code-block-10>
//your custom code goes here...
//</suite-custom-code-block-10>
2-) Regenerate the same entity, after you do that the placeholder will be added for the related entity, and you can update the content in the placeholder for the related index.js file:
//<suite-custom-code-block-10>
,{
text: l("ClickHere"),
action: function (data) {
//define what should happen when to the action clicked
}
},
//</suite-custom-code-block-10>
3-) Then in the next code generations, ABP Suite will respect your change, and will not override the specified section.
For further info please refer to the documentation: https://abp.io/docs/latest/suite/customizing-the-generated-code#adding-new-custom-hook-points-changing-their-places-1
<suite-custom-code-block-10>
is just an example placeholder, you can use any number, however, please note that the number should be used only once per page.
Hi, indeed there is a problem as you stated. But this is by design for now. I will create an internal issue for that because I agree that we should also reflect the impersonation on the auth server side.
Thanks for reporting. Regards.
Hi @EngincanV ,
why did you close the thread about abp 8.3?https://abp.io/support/questions/7844/Bugs--Issues-v83x
Thanks
Hi, it was by mistake. Thanks for reminding me. I've reopened it.