Problem with Error Handling

Hi,

I ran into a problem with error handling in data input forms/dialogs. Maybe you have an idea how this can be solved:

I have two tables, "Customer" and "Contact", where Customer has FK field "ContactId" linked to a Contact record.

I have created a page with customers and an auto-generated "Add" button (+ dialog) to add new customers. Since Radzen does not know that a new Contact also needs to be created for a new customer, I did the following:

1.) In the onLoad of the "Add" dialog, I created and assign a new property called "contact" with a new contact object and in the code where the new Customer object is created, I assign the Contact property of the new Customer to the newly created Contact.

2.) In the "New Customer" form, I removed the dropdown box for selecting the contact (which does not make sense any more!). Instead, I added the new fields for the contact record (like Customer.Contact.Adress, etc.).

This works well so that when I click on "Add" customer, the form appears where I can enter all the fields for the customer AND the newly created & linked contact record for this customer. When I hit "Save", the new customer record and the new contact record are created and both are linked by the FK as expected.

Now, here is my problem: There are some fields in the contact record, that are mandatory. If I forget to enter them, the INSERT operation fails, because the DB does not allow these fields to be null. As a result the Balzor app gets inoperable and I cannot close the dialog or click Cancel any more.

In the submit event for the form, it says "Invoke CreateCustomer" and then "Close Dialog" and there is also an Error branch where it says "Notify".

My problem is, that the insert error does not cause the notification to appear. Instead the app gets inoperable. What needs to be done to corectly handle this INSERT error caused by the failed INSERT into "Contact" due to a required value?

Any help would be appreciated!

Best regards,

Joe

Hi @JustJoe,

There should be a try/catch block generated in the Submit handler. It is supposed to catch any exceptions. What happens when you debug the application with Visual Studio?

Hi @korchev ,

thanks for your reply. Yes, there is a try...catch block:

        protected async System.Threading.Tasks.Task Form0Submit(XXXXXXXX.Models.XXXXX.Account args)
        {
            try
            {
                var XXXXXCreateAccountResult = await XXXXX.CreateAccount(account);
                DialogService.Close(account);
            }
            catch (System.Exception XXXXXCreateAccountException)
            {
                NotificationService.Notify(new NotificationMessage(){ Severity = NotificationSeverity.Error,Summary = $"Error",Detail = $"Unable to create new Account!" });
            }
        }

Actually, also the .Notify() method is called (so this looks all fine) but when the Form0Submit method is left after the Notify() no notification message appears on the screen and a NullReferenceException is raised. I cannot track this down where the Exception happens, it simply appears right after the above code is run.

Any idea what could be wrong here?

Can you show us the stacktrace of the NullReference exception? Those usually occur when some page property is used before initialization.

Hello,

that is the problem, there is no stack trace:

Error Number:515,State:2,Class:16
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable1 commandBatches, IRelationalConnection connection) at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList1 entries)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList1 entriesToSave) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(StateManager stateManager, Boolean acceptAllChangesOnSuccess) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<>c.<SaveChanges>b__104_0(DbContext _, ValueTuple2 t)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func3 operation, Func3 verifySucceeded)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
Ausnahme ausgelöst: "Microsoft.EntityFrameworkCore.DbUpdateException" in Microsoft.EntityFrameworkCore.dll
Ausnahme ausgelöst: "Microsoft.EntityFrameworkCore.DbUpdateException" in XXXXX.dll
Ausnahme ausgelöst: "Microsoft.EntityFrameworkCore.DbUpdateException" in System.Private.CoreLib.dll

---until here, that is the failed insert exception that also gets caught. ---

Der Thread 0x6900 hat mit Code 0 (0x0) geendet.
Der Thread 0x4334 hat mit Code 0 (0x0) geendet.
Der Thread 0x2d78 hat mit Code 0 (0x0) geendet.
Der Thread 0x1150 hat mit Code 0 (0x0) geendet.
Der Thread 0x5874 hat mit Code 0 (0x0) geendet.
Der Thread 0x15ac hat mit Code 0 (0x0) geendet.
Der Thread 0x5a58 hat mit Code 0 (0x0) geendet.
Der Thread 0x3aac hat mit Code 0 (0x0) geendet.
Der Thread 0x4848 hat mit Code 0 (0x0) geendet.
Ausnahme ausgelöst: "System.NullReferenceException" in XXXXX.dll
Object reference not set to an instance of an object.

--- This is the null ref exception that just "happens" - no stack trace.

Can you try removing the custom components that you added to the form (the contact related ones).

Hi,

yes, I did so and now, the Notification appears again and the app keeps running. However, what do I need to do in order to add the components related to the contact to that form. The basic idea is to add a new Customer, but at the same time, also a new Contact (for that customer) and allowing the user to enter all the data in one form/dialog.

You can try setting the Visible property of those contact related components so they appear only when the contact is not null e.g. ${contact != null} (depending on the name of the Contact property). If you need further assistance show me a screenshot of how the contact is initialized and how the components related to it are configured - you can attach them to this thread.

Thanks for your help. I just added one component (for "Company Name") back to the form and for the "Visible" property, I added, as suggestet: ${account.Contact != null}

But this does not help. The point is, that Contact is not null since on the onLoad, I do create a new Contact object and assign it to account.Contact. Therefore, the component is visible and if I hit "Save", this does not change.

Is there a demo app available that shows the how one should go ahead if not only one object is newly created in an Add-dialog, but multiple ones that have a relationship (FK)?

Thanks for your help!

We don't have such an example as this isn't a built-in feature of Radzen.

Ok, so you have any idea how this could be done? I mean the basic procedure is straight forward: A dialog is shown, a new object is created (and in this case another one and the new object gets linked to the other one). Upon save, SaveChanges() is called, and the data is persisted.

But if something goes wrong during SaveChanges() that should be handled.

I don't quite get the reason for the null ref exception, because the object in question is not null - it did not get persisted, but it should still exist in the scope of the dialog.

I don't understand this either. Did you try inspecting the account instance in the catch block to verify all properties are set? The fact that the code with the notification executes proves the catch block handles the SQL exception. It seems something is set to null after that though.

OK, I will invesitage further...

Ok, I found the reason:

In

        public async Task<Models.XXXX.Account> CreateAccount(Models.XXXX.Account account)

you do this:

try
            {
                Context.Accounts.Add(account);
                Context.SaveChanges();
            }
            catch
            {
                Context.Entry(account).State = EntityState.Detached;
                account.AccountType = null;
                //account.Contact = null;
                account.BillingType = null;
                account.Account1 = null;
                account.Catalog = null;
                throw;
            }

account.Contact = null; is the problem. If I comment that out, like above, it works again.

Is there any way I can prevent this. If SaveChnages() goes wrong, why do you modify the object here? The user might have unsaved changes in the dialog which will gets lost then!

Thanks!

This code is needed otherwise EF failed to add an item that failed to insert initially (similar to your case). If you think this isn't needed you can add the service to Radzen's code generation ignore list and modify the code. Otherwise you can store the contact as a page property and restore it back in the catch block e.g. Execute Code account.Contact = contact

Thanks. Reassigning the object in the error handler was also my idea. However (I'm still relatively new to Radzen), what is the exact sytnax for a "Execute C#" action?

I tried account.Contact = contact and also ${account.Contact = contact} and ${account.Contact} = ${contact} but I always get "The left side of the assignment must be a variable, property or indexer".

account is the page var that holds my new Customer object and contact is the page var that contains the new Contact object. Both get created in onLoad.

What is the generated C# code for this Execute C# action? It looks correct. You can also try without the ${} blocks.

I just entered:

account.Contact = contact

But I get:
error CS0131: The left side of an assignment must be a variable, property, or indexer.
error CS1662: 'lambda-expression' cannot be converted to the desired delegate type because some of the return types in the block cannot be implicitly converted to the delegate return type.

The file names stated do not exist (due to their temporary nature), so I cannot have a look at it and see what code is actually generated...

Radzen generates files which do exist. Right click your page in Radzen and select the razor.designer.cs file. Also try adding the missing ;.,

protected async System.Threading.Tasks.Task Form0Submit(XXX.Models.XXXX.Account args)
        {
            try
            {
                var XXXXCreateAccountResult = await XXXX.CreateAccount(account);
                DialogService.Close(account);
            }
            catch (System.Exception XXXXCreateAccountException)
            {
            account.Contact = contact;

                NotificationService.Notify(new NotificationMessage(){ Severity = NotificationSeverity.Error,Summary = $"Error",Detail = $"Unable to create new Account!" });
            }
        }

That looks ok, but seems to be a problem. Maybe account has a different type that does not have the fields visible I need to address...