1

When retreiving data with an api and saving it in a DTO, some values are nullable: null on initial class initialization but VS also warns you for this. For example, an employee:

public class EmployeeModel
{
    public Guid Id { get; set; }
    public int EmployeeNumber { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
}

In practice, this might never happen because of database constraints obligating the user to enter all properties. But on creating the class and using it in the code, the FirstName might be null. How to deal with this? Two questions.

1) How to check if a class has no null values? Imagine the class has 10 string propeties and 2 int properties. To check if one of them is a null, I need 12 if statements. Which of course could be done in a separate method, see next question, but is still alot of code. And on adding a property, it must be manually added. And checking it outside a method is not enough without the NotNull as used in ArgumentNullException.

2) In case of null values, should ArgumentNullExceptions be used or a custom PropertyNullException? A property is not an argument, but a custom PropertyNullException is identical to ArgumentNullException part from the name. Simplified version:

public class PropertyNullException : Exception
{
    public string PropertyName { get; }

    public PropertyNullException(string propertyName)
        : base($"Property {propertyName}  is null.")
    {
        PropertyName = propertyName;
    }

    public PropertyNullException(string propertyName, Exception inner)
        : base($"Property {propertyName} is null.", inner)
    {
        PropertyName = propertyName;
    }

    public static void ThrowIfNull([NotNull] object? property, string propertyName)
    {
        if (property is null)
        {
            throw new PropertyNullException(propertyName);
        }
    }
}

Another option would be to, after a class creates the DTO's from the api, check all the properties for null/Guid.Empty/0 values? Or, of course, I'm doing something else wrong.

2
  • 2
    Why do you need to check the DTO? The data transfer object should have all kinds of madness in it because it has been deserialized from a string sent over a network. Commented Aug 15, 2023 at 15:41
  • 2
    Can you edit your question to include more information about the use case and how the DTO will be used? Commented Aug 15, 2023 at 15:41

4 Answers 4

2

Assuming these DTOs are generated automatically, then you are stuck with whatever that tool gives you. This might be a good use case for class reflection. You could get meta data for all public properties, retrieve the current value of that property, and when you detect a null value, throw an exception.

Doing this has some benefits:

  • Repeated if statements are replaced by a loop.

  • No additional coding is necessary when adding or removing properties.

  • This can be applied to any object, so no code repetition is necessary between classes.

  • You could make this an extension method, so calling this utility method feels more object-oriented.

Class reflection has some drawbacks, though:

  • It is very abstract and can be difficult to maintain if you are not familiar with this technique. I've found that many programmers consider this to be "advanced". They tend to prefer larger amounts of concrete code over smaller amounts of code which is more abstract.

  • Reflected code runs slower. You get a penalty in runtime performance, however don't let this turn you away. Many times the difference in performance is negligible for the use case. Measure first.

    • Bear in mind that most ORMs and data mappers you already use are utilizing class reflection. Do you notice they run slow because of class reflection? Probably not.
  • You might not find an appropriate built-in exception type to handle null values. Then again, this might not be a drawback, because you can easily create your own dedicated exception.

An illustration of this idea:

public static class DtoExtensions
{
    public static void EnsureNothingIsNull(this object instance)
    {
        var type = instance.GetType();

        // get property meta data
        // for each property, check for null
        // throw whatever exception you decide makes sense when null
    }
}

Calling this is as easy as:

var a = new FooDto();
var b = new BarDto();

a.EnsureNothingIsNull();
b.EnsureNothingIsNull();

This might be worthwhile if you have many DTOs that require this logic, which could offset the additional complexity introduced by class reflection. Prefer built-in exception types like Doc Brown mentioned. You don't need to get fancy. Exceptions are most useful to developers, so imagine you debugging this exception 6 months from now after you've forgotten the code you wrote. If a built-in type doesn't give you what you need, create your own exception type.

1
  • At a first glance, it looks like a good idea. Hoewever, I think issues arise when you try to handle cases where you explicitly want to allow null for a few properties. This could be solved by tagging nullable properties with attributes, but to get there, you would have to modify the DTO generator - and when you are able to do the latter, using reflection becomes pointless. One could directly change the generator to create non-nullable types for the related properties.
    – Doc Brown
    Commented Aug 16, 2023 at 11:13
2

I made the experience that in reality, for most model classes and most string properties, it is rarely required to distinguish between null and the empty string.

Hence, first thing I would do here to use string instead of string? whenever possible. In C# version 8 and "nullable reference types" enabled, this can reduce the number of necessary tests for null heavily. Without nullable reference types, there is no difference between string and string?. You may try to work around the issue by some convention, or by other techniques like converting nulls to empty strings once-and-only during the construction process of your classes. The latter may also be an option if your DTOs are generated and you cannot simply choose string over string?.

This will already reduce the issue. It still leaves you with a few attributes for which you want to enforce not just "non-nullness", but "non-emptyness". For example, in your EmployeeModel, you may want to allow an empty FirstName, but not an empty LastName. Such constraints could be checked during a validation process, typically after construction (but YMMV, it often depends on the context where the validation is called best).

To your question which exception to throw from the validation: I would usually try to use the ones provided by the framework as long as they are sufficient. When you can foresee special requirements where you need to distinguish between standard and your own exceptions, then it is time to implement your own. Otherwise I would start with the most simple solution I can think of, which is reusing the existing ones.

3
  • The only caveat to rarely being required to distinguish between a null string and "" is Microsoft SQL Server. Frustratingly, it does distinguish between a null value and an empty string for certain data types. This was a surprise to me when migrating from an Oracle database to SQL server, because our code blissfully sent "" to the database and expecting a null back. That's not what we got, though. We got "" instead. This all depends on how you interpret a null string versus an empty string. Commented Aug 15, 2023 at 17:53
  • @GregBurghardt: my point is, regardless whether a DB technically makes a difference between NULL and the empty string. for most real world use cases and domains I know, the distinction isn't important. Hence, the typical domain class doesn't need many string? attributes, just string atttributes which might be empty. Of course, an ORM which generates DTOs from some table definition, using string? for each nullable column, might force you to deal with null values, but typically only once at construction time.
    – Doc Brown
    Commented Aug 15, 2023 at 20:24
  • @GregBurghardt If you use JSON it could be worse. In a dictionary, you might want to access a string by key. The possibilities are: Key is not present. Key is present and the value is explicitely null. Key is present and the value is an empty string. In theory, they might all mean different things.
    – gnasher729
    Commented Aug 16, 2023 at 13:34
1
public class EmployeeModel
{
    public Guid Id { get; set; } = Guid.NewGuid();
    public int? EmployeeNumber { get; set; } = null; //here I think you _need_ a nullable!
    public string FirstName { get; set; } = ""
    public string LastName { get; set; } = ""
}

Now, unless you set them to null deliberately (make sure your DB has NOT Null columns!), you are good.

Also, make liberal use of null coalescence ?? and null conditional ?. operators

1
  • On one hand, that is what I would recommend. Because having a nullable value that must never be null seems just daft. On the other hand, the problem might not be that the value is null, but that the value has not been set. In Swift you can write FirstName! for an optional string which means "It must not be nil. If it is nil then crash right now.", and that finds error (In Swift there is no way to access nullable data without a null check, including this suicidal null check).
    – gnasher729
    Commented Aug 16, 2023 at 13:42
1

In practice, this might never happen because of database constraints obligating the user to enter all properties

Well there's the problem right there. Presuming the DB is an integral part of a class' design. Never mind "nullable properties that should never be null." This is just a synecdoche for the broader problem of depending on the database to change an object to a valid state.

What class cares what the DB "thinks" anyway? The incoherent class does. Oh right, it's just a DTO - "just transporting to the DB, nothing to look at here!" As if the properties did not originate from an object with explicit requirements. It is an eternal trope of carelessness.

2
  • And that's the EXACT point of this question... Not assuming anything and checking it. Commented Feb 8 at 9:04
  • The real question being asked is "should an incomplete object state be allowed to be saved to the DB?" I think it is OK as long as the re-constituted object is subsequently validated in the model. 'null' is an implementation issue - a technicality - not the business rule. Do not implement DB constraints that violate business rules. And in the holy name of Von Neumann do not use 'xxx' or the like and pretend it's null. It is an express elevator to the 7th level of hell. HEAR ME now and believe me later.
    – radarbob
    Commented Oct 19 at 4:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.