In my database model I have a navigation property which cannot be null. However, since the variable may not have been loaded yet, it can be null during runtime.
public class Car {
private Brand? _brand = null
public Brand Brand {
set => _brand = value;
get => _brand ?? throw new InvalidOperationException("Uninitialized property: " + nameof(Brand ));
}
public string GetBrandLocation(){
return this.Brand.Location;
}
}
According to Microsoft documentation, this problem can be solved by throwing an exception in the getter. But all Exceptions that are thrown in the Getters are not catched by a global Exception handler. The execution of the code immediately stopps, after the exception has been thrown.
According to the Microsoft recommondations, throwing exceptions in Getters should be avoided. Also this Stackoverflow thread, advises against using Exceptions in Getters.
Another method would be, that I make the property also nullable. But if I do so, I have to check in each single function, if the property is null, which seems not to be very DRY.
public class Car {
private Brand? _brand = null
public Brand? Brand {
set => _brand = value;
get => _brand;
}
public string GetBrandLocation(){
if(this.Brand == null){
throw new InvalidOperationException("Uninitialized property: " + nameof(Brand ));
}
return this.Brand.Location;
}
}
Because we have a big codebase, this can get really messy. Especially with chained calls like:
return Car.Brand.Adress.Street
Do I miss something? What is considered best practice in this use case?
2
Answers
If you normally try to access that objects property that is null, the compiler will throw an
System.NullReferenceException
that you can handle these errors globally. But if you want to throw your own exception and message, you must explicitly check when accessing the property, and in my opinion, the simplest and clean way is to use the Extension Methods as follows.and use as follows :
Correct. EF Navigation properties will always need to be nullable, even if the underlying FK constraint (and
NOT NULL
column constraints) require a valid reference to always be present in the DB, because of the simple fact that loading related data is never required.In that situation, the entity class properties corresponding to the FK columns will (of course) be non-nullable types, but any reference navigation properties must always be
?
(i.e. nullable reference types). (Note: this does not apply to collection navigation properties, which are another story entirely).I agree. I’m disappointed that EF’s documentation is even suggesting that. This isn’t anything new though: ever since they started seriously suggesting people do
= null!
to initialize DbContext properties (which, honestly, is just really, really dumb)Yes, but only if you use the `class Car“ EF type itself as a DTO for passing data around in your application.
…but if you instead design a new, separate immutable DTO
class
, with non-nullable properties, with a constructor that verifies these class-invariants, then that works great.You can also use
implicit
conversions between the DTO and the entity-type to reduce some frictions, such as passing a DTO into a method expecting an entity, or to even use the DTOs with Linq Queries andDbSet
and more.So if this is your EF Entity class:
…and you want to represent a
Car
with a loadedMake
propertty, then just add this to your project:Now, even if that
Car
entity has itsMake
navigation-property re-set tonull
or changed then that’s okay because it won’t affect consumers that useLoadedCarWithMake
becauseLoadedCarWithMake.Make
will never benull
.You’d also want to add a loader method for this too, e.g.:
If all this extra repetitive code looks tedious, don’t worry: you shouldn’t normally need to write this by-hand: it’s straightforward to use tools like T4 – or Roslyn Code Generation – to automatically create these "
Loaded...
" types for you – I just wish the EF team would include that in-box for the benefit of everyone.You can improve this further by defining
IReadOnly...
interfaces for each entity-type (so you’d haveIReadOnlyCar
andIReadOnlyMake
, which do not contain any navigation-properties, onlyget
-only scalar/value properties), thenLoadedCarWithMake
would also then get to implementIReadOnlyCar
.