Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nullable return value on a generic argument

I am trying to save a model to the database using Dapper. I set up the parameters with an input/output parameter that is an int with an existing primary key value used for an update.

public async Task<TKey> SaveAsync<TKey>(IGraph builder, IDataContext context = null)
{
    var parameters = this.GetParametersFromDefinition(builder, DefinitionDirection.In);

    // See if we have a key defined. If not, we assume this is a new insert.
    // Otherwise we pull the key out and assume it's an update.
    PropertyDefinition key = builder.GetKey();

    if (key != null)
    {
        parameters.Add(key.ResolvedName, key.PropertyValue, null, ParameterDirection.InputOutput);
    }
    else
    {
        throw new InvalidOperationException("The data graph did not have a primary key defined for it.");
    }

    await this.ExecuteProcedure(parameters, builder, context);

    object returnedId = parameters.Get<TKey>(key.ResolvedName);
    return returnedId == null ? default(TKey) : (TKey)returnedId;
}

private Task ExecuteProcedure(DynamicParameters parameters, IGraph builder, IDataContext context = null)
{
    ProcedureDefinition mapping = builder.GetProcedureForOperation(ProcedureOperationType.Insert);
    if (string.IsNullOrEmpty(mapping.StoredProcedure))
    {
        throw new InvalidOperationException("No stored procedure mapped to the builder.");
    }

    // Query the database
    return this.SetupConnection(
        context,
        (connection, transaction) => connection.ExecuteAsync(
            mapping.StoredProcedure,
            parameters,
            commandType: CommandType.StoredProcedure,
            transaction: transaction));
}

It is invoked like this:

this.Address.AddressId = await repository.SaveAsync<int>(graph);

When I evaluate the parameters, I see my input/output parameters on it.

Dynamic Parameters

However when I try to execute this line in my save:

TKey returnedId = parameters.Get<TKey>(key.ResolvedName);

I am given the following exception:

Exception:Caught: "Attempting to cast a DBNull to a non nullable type! Note that out/return parameters will not have updated values until the data stream completes (after the 'foreach' for Query(..., buffered: false), or after the GridReader has been disposed for QueryMultiple)" (System.ApplicationException) A System.ApplicationException was caught: "Attempting to cast a DBNull to a non nullable type! Note that out/return parameters will not have updated values until the data stream completes (after the 'foreach' for Query(..., buffered: false), or after the GridReader has been disposed for QueryMultiple)" Time: 7/21/2015 10:19:48 PM Thread:[7200]

I am assuming this is an issue with the generic type not being nullable in this case, as it's an integer. Is this because Dapper always returns a nullable? I've just assigned the OUTPUT on the stored procedure a constant value for now to make sure the output is assigned something.

How can I work around dapper returning a nullable, is the only way around it my passing in int? as the generic type?

Update

I was able to resolve it using this approach. SO won't let me post it as an answer yet. When the time limit is up, I'll post an answer, unless someone else has any better ideas.

object returnedId = parameters.Get<TKey>(key.ResolvedName);
if (returnedId == null)
{
    return default(TKey);
}

return (TKey)returnedId;
like image 531
Johnathon Sullinger Avatar asked Nov 10 '22 08:11

Johnathon Sullinger


1 Answers

If DBNull is a valid value and should mean something other than default(TKey) (ex: default(int) = 0), and if TKey will always be a value type, then constrain TKey as struct like so:

public async Task<TKey?> SaveAsync<TKey>(IGraph builder, IDataContext context = null) where TKey : struct
{
    ...
}

Then get the key like so:

TKey? returnedId = parameters.Get<TKey?>(key.ResolvedName);

The return type of SaveAsync will reflect that the key could be null. If the key should never be null, and if DBNull should be defaulted to default(TKey), then simply use the following:

public async Task<TKey?> SaveAsync<TKey>(IGraph builder, IDataContext context = null) where TKey : struct
{
    ...
    return parameters.Get<TKey?>(key.ResolvedName).GetValueOrDefault();
}
like image 65
Tyree Jackson Avatar answered Nov 14 '22 23:11

Tyree Jackson