Data is Null. When "safety" is in the way.
Tags: csharp, dotnetcore, programming
Published: 2023-06-05 17:39:35

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](https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references#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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lrj97byja771gs2ou621.jpg)
`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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/15t7obhge6drbhg9tbxl.jpg)
To glue this together in .NET you can use [Entity Framework Core](https://learn.microsoft.com/en-us/ef/core/) (EFCore) with _Database First_ approach creating `DbContext` and following DBO:
![Database Object used by Entity Framework Core](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uhw02pbvre8yckyb450c.png)
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/guctrdqms8xc3awuggf3.png) 
Finally we write the test, expecting everything to be fine... Just getting data for one of profiles.
![Example test fetching the data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kkr9enek0iqhwqundyuz.png)
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uw6u36q6g09ax6h85yhe.png)
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pt13ljy3xs8s7qk722b4.png)
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x8e1f0x26msaww361lm9.png) 
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.