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:
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
- usesIFM.DBA.MSSQL
directly to save the data -
IFM.WebApi
- Reads the data usingIFM.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
To glue this together in .NET you can use Entity Framework Core (EFCore) with Database First approach creating DbContext
and following DBO:
Assuming we have some data in the table, we create a method which filters out reports for specific profile using EFCore.
Finally we write the test, expecting everything to be fine... Just getting data for one of profiles.
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.
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.
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
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)