.NET Mjoin, LeftJoin, and Multiselect
.NET Mjoin, LeftJoin, and Multiselect
Description of problem:
I have basically what I'd describe as a One to Many to Many. Users -> UserOrg -> UserOrgRoles -> Roles. Users can be in Many Organizations and Many Roles inside those Organizations.
I have the Editor essentially setup like this (cut down for brevity):
var editor = new Editor(db, "Users", "Id")
.Model<User>("Users")
.Model<OrganizationUser>("OrganizationUsers")
.Model<OrganizationUserRole>("OrganizationUserRoles")
.Field(new Field("Users.Id").Set(false))
....
.LeftJoin("OrganizationUsers", "OrganizationUsers.UserId", "=", "Users.Id")
.LeftJoin("OrganizationUserRoles", "OrganizationUserRoles.UserId", "=", "Users.Id")
.MJoin(new MJoin("Roles")
.Link("Users.Id", "OrganizationUserRoles.UserId")
.Link("Roles.Id", "OrganizationUserRoles.RoleId")
.Model<Role>()
.Order("Roles.Name")
.Field(new Field("Id")
.Options(new Options()
.Table("Roles")
.Value("Id")
.Label("Name")
)
)
).Where("OrganizationUsers.OrganizationId", id);
I got everything working (I haven't tried create/edit data) except for the fact that the Datatable shows duplicate rows, basically a row for every Role the Users are in:
This person being in 4 Roles for the Organizaiton
Clicking Edit on the record selects all the Roles appropriately
Do I need to do a 2nd MJoin
? I noticed someone else asked about GroupBy
in the forum and it was said it wasn't supported, but has this changed since then?
Answers
Probably not, because you can't really edit this anyway. But you can create and use a (non-updatable) view that uses "group by". No problem.
Exactly that.
GROUP BY
makes the editing aspect a lot more difficult.It sounds like you are doing something like this.
I'm trying to work out the database structure here - do you need the left join to
OrganizationUserRoles
if that is your link table for the Mjoin? Or indeedOrganizationUsers
? Could you show me the full controller code and ideally if you have an image showing the database structure and references, that would be useful.Allan
The redundance is caused by the left joins
This means that one row is retrieved for every organization the user is assigned to AND every role the user has for the respective organization.
If you want just one row for each user you need to get rid of both left joins and retrieve the details with an Mjoin as suggested in Allan's example (see Permissions column).
If a user can only be assigned to one organization, you can keep the first left join.
In a way a data table with an Mjoin works similar to "group by": The grouping parameters are the fields of the data table, and the group content is what you return from the Mjoin.
^ relooking at that I might be able to skip the OrgUser join and go directly to the OrgUserRole join.
@allan that is the example I was looking at in the .NET package. Its exactly what I am needing but instead of permissions its Roles... except its deeper in the DB structure.
I'm trying to edit User data which Roles are set PER Organization the User is apart of.
I setup the left joins thinking I needed to let Editor know about them so it could set data fields appropriately on edit/create.
Good points on the
Group By
being non-editable.I'll see about removing some left joins.
Here is the full Editor.. I tried removing the Left Joins but get error messages that I can't remove them because it needs to know about them.
Are those the only three fields that you are showing in the form? I presume you are showing others in the DataTable? Which ones?
The problem you are going to run into here is that your junction (link) table has more information in it than just the junction of users and roles. The example I linked to has a
user_permission
table which is simply two columns.The way Mjoin works in Editor is that it will delete the records in the junction table and then add them again. This is fine for simple links, but if you have extra data, such as
OrganizationUserRoles.isActive
, it simply doesn't work since that data would be lost.The way to handle such a case is with a nested editor. You could still use Mjoin to show the multiple rows for a user (and perhaps we should focus on that first), using
->set(false)
to make sure it doesn't attempt to write to the child table. Then have a child editor forOrganizationuserRoles
(i.e. as if it were a main table, doing that it might be best to add its own primary key). A lot of the values will be set by the API (userId and CreatedById for example) but I think that should be possible. I don't have an example of that I can just link to though unfortunately.Allan
Yeah I'm showing mostly User data, but those 3 are the ones being edited.
I have a PreCreate/PreEdit that sets the CreatedBy/ModifiedBy.
I'll look into a nest editor?.. by that do you mean like this? https://editor.datatables.net/examples/datatables/mJoin.html
Using a datatable in the editor?
Speaking of focusing on data output first.... I had a thought of modifying the response.data and de-duping the values to return... but that seems hacky.
Here is my Editor.js
Actually I think you were referring to this:
https://datatables.net/blog/2019/parent-child-editing-in-child-rows
So I can have a user parent table, the child datatable/row can just be a datatable of all the roles, the editor would just pop up to a single multiselect?
UI wise, I think this is the closest to what I want...
https://editor.datatables.net/examples/datatables/parentChild.html
Basically working on this to have an entirely new editor/datatable for the Roles here..
Now I just want to avoid having to select a row and click edit.. I really want to just click the Edit button and it pop a MULTISELECT. Like I don't want to edit individual roles. I just want to add and remove existing Roles from the OrgUser.
That sounds a lot like this? That example uses checkboxes, but it could readily use a
select
set for multiple values, or adatatable
.This would update only
OrganizationUsers
right? You can useField->setValue(...)
for the fields to set the values such as update time, etc. But theisActive
is going to be difficult. There is no concept in the field of a row being present, but not active. Either it is checked, and therefore in the table, or it is not checked and therefore not in the link table.Allan
That is what I was trying originally. Think of my problem like this:
The query returning duplicate rows for each role.. Mjoin was going to blow away extra data in table (which I'm fine with resetting values if needed). That is why we landed on the Parent/Child/nested editor.
So instead we are trying to have:
Parent Editor 1
User -> UserOrg data
Child Editor 2
UserOrg -> UserOrgRoles (-> Roles is the multiselect data)
But Child Editor makes me select a role then click edit where I just want multiselect capabilities.
This is what I'm saying. Basically I want the "Edit Entry" to be the child row. The UX of expanding a child row to select a single row in the child to then half to click Edit to multiselect ALL the roles, feels wonky.
All you need is a single select field for "user's organization" and then a multi select field for "user's roles in the selected organization". Can the user really have multiple roles in one organization? Or is it actually only a single role per organization? If the latter is true you only need two single select fields.
The options for "user's organization" probably need to be read from the server because they are dynamic.
Are the "user's roles in the selected organization" also dynamic? If they are static, e.g. "VP, Director, Client Service Rep, etc." it is really easy: You just define those options locally client side. If these options are dynamic and deviate for each organization you will need to retrieve them from the server and update the options locally.
If the catalogue of roles deviates per organization but is not dynamic, you can also define those options locally client side and then update the options depending on user selection.
So what is your use case here?
Users
Organzations
OrgUser (User can be part of many Orgs)
Within each Organization, they have specific roles.
Roles: Enum table of values. These are global roles across organizations (Business Owner, Product Owner.)
Roles are dynamic in a sense that we can add new ones via the admin at any time and Users drop and add roles yearly.
I had the form displaying (see above) but the issue was the returned data being duplicated per Role in the datatable.. so the suggestion was Parent/Child editor to move the Role editing to a Child editor.
I'm realizing that the Parent/Child editor makes the Role modification process strange.
Open to suggestions since I feel we've come full circle to the original problem again.
Ok, then I assume the roles need to be fetched and updated dynamically:
Just use a single select field for "user's organization" and get the options from the server. Please note: if you are getting the options from the server using an options instance you don't need to specify the options again in the "select" field definition. The "select" field will use the options read from the server - if there are any.
And use a multi select field for "user's role in the selected organization"
Then using "dependent" make an ajax call to fetch the available user's roles for the multi select field and populate the options accordingly using the select field's "update" method.
https://editor.datatables.net/reference/field/select
https://editor.datatables.net/reference/api/dependent()
"dependent" could look something like this:
That is an interesting option. Right now the Org is already filtered by the Where clause in the Editor (where orgid = id). Can Dependent() be fired on a disabled field value (not on change) basically a read only field?
No, I don't think so and I don't understand why you should want that.
You can also replace the above "dependent" statement like this and use different events if you don't like the "change" event.
User organisation - if a user can be part of multiple organisations, then that one needs to be an Mjoin. Presumably then they have have multiple roles per organisation?
That's where I think the nested editing needs to come in. You need the child table to show the list of organisations. Then when you edit rows there, then that's where you need a second Mjoin to show the user roles.
I'm unfortunately very tight for time this week or I'd try to create an example, sorry.
Allan
I think you don't even need nested editing for this use case:
You just select an organization with a simple single select fied.
"Dependent" on that selection you load the options for the multi select that is further down the form. I have quite a few use cases like this but I use "selectize" for this where I can't simply update the options. I "clear" the selectize fields and "add" them back in with the updated options.
Here is an example using "selectize":
I have different options for creation and editing. When editing the options depend on the contract id. I save the options in a global variable because I also need them for other purposes (e.g. column rendering in my data table)
This is just the "create" case but it is very similar for "edit"; the main difference being that the passed "ctr_id" is not 0 but the id of the record edited. For performance reasons I also update the options on "select" of the record to be edited in the data table; I don't wait for "initEdit".
If a user can be part of multiple organisations, that would mean you need to show one row for each user / organisation group. I'd been thinking a single row per user, and then the list of organisations for that user would be part of the form.
Either way could work nicely I think. Indeed, @rf1234's solution might be nicer when considering the
isActive
and other fields.Allan
Many roads lead to Rome
Good luck!
I appreciate the brain storming around this. Just to summarize:
For the context of this editor the Users datatable is already filtered to only show users for that organization (see where clause). This is passed in by the organization id on the URL example:
/organizations/2/users
, so there will be no selection of organization on user edit. We will only be editing the OrgUser -> OrgUserRoles in this case (Parent User data is readonly/disabled on editor). The options for the multiselect comes from the Roles table.I had the editor popping the multiselect but the issue was the Users datatable showed a row for each Role the user was in. I'm okay with wiping and re-adding extra field data in OrgUserRoles.
Users can be in multiple orgs, but in the context of this editor it is only editing this organization's user.
Then it is very simple: Just a multi "select" field
Yes, and that is caused by your left joins above. At least one of them has to go. See my 2nd comment above.
I thought the discussion was how to achieve that, ie by doing a Parent/child editor I won't need the OrgUsersRole join, that'll be in the 2nd editor, etc.
I can't see how remove a join with killing the contents I need to show/edit atm.
I'll re-read this thread and go over the examples again. I may have gotten lost in the weeds.
Sorry, I am lost. I'm afraid, I can't help you. Please accept my apologies.
Let's go back a bit. The
OrganizationUserRole
is not a one-to-one link - it is one-to-many. Therefore it should not be included as aLeftJoin
, but rather only as anMjoin
.To do that drop:
and
Resulting in:
I suspect when you tried to remove the left joins before, you didn't remove the model, and that is what generated the error?
In the DataTable, do you have any columns that refer to
OrganizationUserRole
as if it were a left join? I see the Editor init code above, but not the DataTable.Allan
Here is my datatable:
It shows, the "Role1,Role2, Role3" fine but 3 rows for the user.
You're right I don't show anything for OrganizationUserRoles, but I needed the model for the Link on the Mjoin, if I remove the model, it throws an error for multipart OrganizationUserRoles not found.
Actually.. this works!
I moved the OrganizationUserRole to the Mjoin (didn't realize that was possible for some reason).
Now datatables shows single user. Editor still shows all Roles in multiselect.
Now I just need to set all the values I need in the edit (user's can't be created here). Because mjoin is going to blow away those extra fields.
That's probably going to be the rub here. If I don't do anything it throws that it can't be null on edit, but when I try to set it