DEV Community

Cover image for Data is Null. When "safety" is in the way.
SOCAR
SOCAR

Posted on

Data is Null. When "safety" is in the way.

Back in .NET 2.0, which was my starting point of using .NET stack, null was important aspect, around which we were building our software architecture, meticulously crafting code that was NullReferenceException safe.

Nowadays, new safety features of .NET ecosystem introduced Nullable contexts which are here to “save” us from need of additional null checking. Sometimes however they can introduce unexpected behavior causing exceptions in places you would not expect.

The Project

In below example we track and store followers count of profiles in some generic social network.

Visual Studio solution is split up in several projects that reuse code between them. It's outline looks like this:
Visual Studio solution example
IFM.BackgroundWorker is gathering data by scraping the website and using IFM.DBA.MSSQL saves it to Microsoft SQL Server. IFM.Shared holds DBO (DataBase Object) used by Entity Framework Context consumed by 3 project:

  • IFM.DBA.MSSQL - access layer to the database
  • IFM.BackgroundWorker - uses IFM.DBA.MSSQL directly to save the data
  • IFM.WebApi - Reads the data using IFM.DBA.MSSQL for downstream clients

The Code

Now lets consider string notes; A string can be null and for decades we used String.IsNullOrEmpty() to check that. The same goes for databases such as Microsoft SQL Server (MSSQL) which allows you to have a NULL value in column of NVARCHAR type
Microsoft SQL Server table schema
To glue this together in .NET you can use Entity Framework Core (EFCore) with Database First approach creating DbContext and following DBO:
Database Object used by Entity Framework Core
Assuming we have some data in the table, we create a method which filters out reports for specific profile using EFCore.
Method fetching filtered data from the database
Finally we write the test, expecting everything to be fine... Just getting data for one of profiles.
Example test fetching the data
And test… fails (as well any implementation of this code in any of the projects). It will throw exception when attempting to create list in GetAllStatsForProfile method and provide relatively cryptic info about this: System.Data.SqlTypes.SqlNullValueException: Data is Null. This method or property cannot be called on Null values.
ExceptionScreenshot
Reason for this exception is notes column in FollowersData table that allows NULL values. Previously shown FollowersDataDBO class was coded by hand, however using a dotnet ef dbcontext scaffold gives same results – creates class that have notes property not marked as nullable. string type which can be null is not "accepting" null coming from the database

The Problem

At this point we are facing a problem not only because exception is breaking execution of the application but how to address it in regards of architecture. In this example, we control the source code, but what if the project would be closed source?

The Fix

Here's 3 suggestions how to fix the issue:

1. Marking notes property of DBO class to accept null values

Assuming we can modify the source code of the FollowesDataDBO class we can mark string notes property as accepting null values by adding ? after the type definition.
Fix1 String Accepting Null
Interestingly enough the null forgiving operator can't be used here (public string notes {get;set;} = null!) and would not resolve the issue

2. Disabling <Nullable>

Disabling null checking by setting in the project file <Nullable> value to disable do the trick as well. However it have be done in project which holds the DBO file (and not projects that reference that project), which affect all other projects that might have logic relying on that setting. In our case we would have to modify IFM.Shared.csproj

Fix2 Disable nullable
Obviously from global perspective we cant predict what impact on projects that reference this and rely on <Nullable> to be enabled

3. Change database schema

Finally, the "worst" approach to fix the issue, that we could introduce, is changing database schema by setting notes column to NOT NULL (Allow Nulls = false) but as projects might be dependent on such schema, the introduced risk of breaking change in this case is big.

Conclusion

In this example I have shown that new safety features might backfire in specific conditions. Proper design of application as well continuous testing is important to catch such errors on early stage of project development. Such safety features might be also a breaking change while migrating from older .NET stack and cause additional workload while mitigating potential risks and architecture changes.

Top comments (0)