Hi,
In our application we have the following structure: Main Application -> Our Custom Module -> ABP Module(s). Inside this custom module, we want to be able to link our data with a User and/or Organisation. So through ABP Suite we have setup the needed navigation properties:
This took some effort, and if we didn't have the Commercial source package, I am not sure how to have accomplished this properly from inside a module, but that is besides the issue I am raising. Looking at the provided sample code generated within the AcmeDbContext
class, we can see that [ReplaceDbContext(typeof(IIdentityProDbContext))]
is used to replace that implementation with that of our own AcmeDbContext class.
Assuming this is how it is done, we did the same in our ModuleDbContext class and added the [ReplaceDbContext(typeof(IIdentityProDbContext))]
attribute. We added the required DbSet
properties and made sure that builder.ConfigureIdentityPro();
is called. We then added [ReplaceDbContext(typeof(IAcmeDbContext))]
attribute to the main application AcmeDbContext so we can also expose our new entities there for later use (a call to builder.ConfigureAcme();
was also added as we replicated that setup).
We have a Company entity that we wish to connect to a User and Organization and using the Suite configuration above it generates the following class:
public partial class Company : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid OrganizationId { get; set; } // OrganizationUnit is referenced here as expected
public Guid UserId { get; set; } // IdentityUser is referenced here as expected
public Guid? TenantId { get; set; }
/* Snip! */
}
Subsequently, the WithNavigationProperties class is generated as expected (putting it here for reference and completeness sake):
public class CompanyWithNavigationProperties
{
public Company Company { get; set; }
public OrganizationUnit OrganizationUnit { get; set; }
public IdentityUser IdentityUser { get; set; }
}
And the necessary methods within the EfCoreCompanyRepository class is also available:
protected virtual async Task<IQueryable<CompanyWithNavigationProperties>> GetQueryForNavigationPropertiesAsync()
{
return from company in (await GetDbSetAsync())
join organizationUnit in (await GetDbContextAsync()).OrganizationUnits
on company.OrganizationId equals organizationUnit.Id into organizationUnits
from organizationUnit in organizationUnits.DefaultIfEmpty()
join identityUser in (await GetDbContextAsync()).Users
on company.UserId equals identityUser.Id into users
from identityUser in users.DefaultIfEmpty()
select new CompanyWithNavigationProperties
{
Company = company,
OrganizationUnit = organizationUnit,
IdentityUser = identityUser
};
}
We build and start the application and navigate to the 'Companies' menu item; here we are treated with the following:
- Error 500 dialog in our Application
- Error
Value cannot be null. (Parameter 'inner')
message inside the EF Core log
After a slight refactoring of this particular method (as I wanted to get more familiar with the generated code and why it looked like that) it looks like the following:
protected virtual async Task<IQueryable<CompanyWithNavigationProperties>> GetQueryForNavigationPropertiesAsync()
{
// Retrieve the current DbContext once (does it make a difference?)
var dbContext = await GetDbContextAsync();
// Add explicit references to the DbSet properties so we can easily inspect them during Debug
var companies = dbContext.Companies; // This is (await GetDbSetAsync()) in generated code
var organizations = dbContext.OrganizationUnits; // This DbSet<T> is null!
var identityUsers = dbContext.Users; // This DbSet<T> is null!
return from company in companies
// The generated join is an 'Outer Join' even though our navigations are 'Required', but irrelevant to the issue
join organizationUnit in organizations
on company.OrganizationId equals organizationUnit.Id into organizationUnits
from organizationUnit in organizationUnits.DefaultIfEmpty()
// The generated join is an 'Outer Join' even though our navigations are 'Required', but irrelevant to the issue
join identityUser in identityUsers
on company.UserId equals identityUser.Id into users
from identityUser in users.DefaultIfEmpty()
select new CompanyWithNavigationProperties
{
Company = company,
OrganizationUnit = organizationUnit,
IdentityUser = identityUser
};
}
Now I could see that the DbSet properties that we took from the IIdentityProDbContext
interface are seemingly not properly referring, or mapped to the table on this particular context, even though by my current, and still very much growing understanding of ABP, was configured correctly using [ReplaceDbContext(typeof(IIdentityProDbContext))]
?
TL;DR, after using ReplaceDbContext
the implemented DbSet properties are null
inside a module that the main application is using.
What am I missing here? Why is Users and Organization null? Did I miss some key configuration part to make this work? Did I totally misunderstand the intention here? All the wiring of this logic is happening inside the Module and not the Application. We will make and use different pages and logic there that does not belong to this module. I have also checked the various sources of the Commercial modules, but it seems nothing is making a reference to either a User or Organization.
To summarize:
- Module has its own DbContext and entities, but Migration is handled in the Application
- Entities have either a required or optional link to an Organization and/or a User
- ReplaceDbContext was used inside the Module (IdentityPro) and also the Application (Module, IdentityPro) but the 'inherited' DbSet properties are
null
- DependsOn was used inside the Module (IdentityPro) and also the Application (IdentityPro and Module)
- The AppService, Repository and Pages for the entities (Company) are inside the Module
- Additional properties have been added to IdentityUser and OrganizationUnit (that are migrated, displayed and working properly)
10 Answer(s)
-
0
hi
Please share more code. eg DbContext and Your EF Core Module or share a simple project that can reproduce the problem,
Thanks
-
0
Not sure how to create a "simple" project that still reflects the setup and intent that we want to use within our Application. But since the project contains very little IP, I have no issue sharing the solution in its entirety.
Could this be transferred to you in a secure manner?
-
0
A complete project will make troubleshooting difficult, you can create a simple project according to your steps.
-
0
I will try to reproduce this in a "barren" project, but it might very well not happen, which is exactly why I am asking troubleshooting for our actual project. There are no code samples available anywhere about creating a custom module that needs to use the User and Organization.
I'll report back if I can extract all the effort I did, not happy with this answer. Our actual project is not that much "bigger" than the starting template with 1 custom module containing half a dozen entities.
-
0
Waiting your good news.
-
0
I have a simple project, that is roughly the same size in ZIP as our actual project, but whatever.
You can find the source here: Deleted
The application is setup to use LocalDb 2019 (came with VS2022 installer). After giving the default admin user permissions to see the module, click on the Companies menu item and you will get the error. Saving a new Company gives the same error.
Getting just this simple "clean" application to compile and start was frustration, as the generated files did not compile and some ConsoleTest project refused to build, so I removed that from the solution.
(The ctor generated for Company resulted in an illegal order of parameters in combination with nullable types, so I had to fix that manually)
-
0
hi
I will check it, Please share the commercial project via email, Thanks.
-
0
maliming,
I messed up and totally forgot to edit the
NuGet.config
to remove a certain detail from the feed.Apologies for this oversight, would it be possible/required to invalidate that key and obtain a new one?
Sorry.
-
0
You can send email to info@abp.io to request to change a API Key.
-
0