1
votes

Retour du type SqlDataReader même lorsque try catch échoue - tous les chemins ne renvoient pas une valeur

J'essaie de lire à partir d'une base de données avec un try / catch et je veux évidemment renvoyer des données. Je reçois un message indiquant que tous les chemins de code ne renvoient pas une valeur . Je comprends que je dois retourner quelque chose, mais comment dois-je gérer ce scénario? Puis-je renvoyer un objet SqlDataReader vide?

Les procédures stockées en cours d'exécution renverront différents types d'objets en fonction de ce qui est exécuté, sinon je retournerais le type spécifique.

    private SqlDataReader RunSql(string connectionString, string procName)
    {
        using(SqlConnection = conn = new SqlConnection(connectionString))
        { 
            using(SqlCommand SqlCmd = new SqlCommant(procName, conn))
            { 
                SqlCmd.CommandType = CommandType.StoredProcedure;
                conn.Open();

                try 
                {
                    using(SqlDataReader reader = SqlCmd.ExecuteReader())
                    {
                        if(reader.HasRows)
                        {  
                             return reader;
                        }
                    }
                }
                catch(Exception e)
                {
                    //?
                }
                finally
                {
                    conn.Close();

                }
            } 
        }
        //?
    }


2 commentaires

Oui, car votre méthode ne renvoie rien si la condition échoue. Vous pouvez donc renvoyer null sur votre ligne commentée.


wow, je me sens stupide maintenant. Je n'ai même pas pensé essayer ça, mais avec le recul, cela semble évident. Merci pour ton aide!


3 Réponses :


-2
votes
        { 
            SqlCmd.CommandType = CommandType.StoredProcedure;
            conn.Open();

            try 
            {
                using(SqlDataReader reader = SqlCmd.ExecuteReader())
                {
                    if(reader.HasRows)
                    {  
                         return reader;
                    }
                }

0 commentaires

-1
votes

Comme la méthode ne renvoie rien si la condition échoue, c'est la raison pour laquelle vous obtenez cette erreur de compilation. Vous pouvez essayer l'extrait ci-dessous. renvoyez la valeur null sur votre ligne commentée //?

private SqlDataReader RunSql(string connectionString, string procName)
        {

            using (var conn = new SqlConnection(connectionString))
            {
                using (SqlCommand SqlCmd = new SqlCommand(procName, conn))
                {
                    SqlCmd.CommandType = CommandType.StoredProcedure;
                    conn.Open();

                    try
                    {
                        using (SqlDataReader reader = SqlCmd.ExecuteReader())
                        {
                            if (reader.HasRows)
                            {
                                return reader;
                            }

                        }
                    }
                    catch (Exception e)
                    {

                    }
                    finally
                    {
                       conn.Close();

                    }
                }
            }
            return null;
        }

J'espère que cela résoudra votre erreur de compilation.

p >


0 commentaires

0
votes

La réponse acceptée fonctionnera mais, comme écrit, elle "avale" l'exception que vous avez interceptée; cela empêche même l'appelant de savoir que quelque chose a mal tourné. Une bien meilleure approche consiste à renvoyer l'exception afin que l'appelant soit toujours informé que quelque chose de mal s'est produit et qu'il puisse ensuite traiter le problème comme il le souhaite.

Un autre problème est que les objets de lecture et de connexion seront supprimés. avant de les consommer dans le code en aval; grâce aux instructions using et finally . Une solution à ce problème est de confier à l'appelant la responsabilité de gérer l'objet de connexion.

// define a container for information relevant to each row
public sealed class SqlResultSetRow : IEnumerable<(string fieldName, Type fieldType, object fieldValue)>
{
    private readonly (string fieldName, Type fieldType, object fieldValue)[] m_fields;

    public int ResultSetIndex { get; }

    public SqlResultSetRow((string, Type, object)[] fields, int resultSetIndex) {
        m_fields = fields;

        ResultSetIndex = resultSetIndex;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    public IEnumerator<(string fieldName, Type fieldType, object fieldValue)> GetEnumerator() {
        var rows = m_fields;

        foreach (var row in rows) {
            yield return row;
        }
    }
    public object GetFieldName(int fieldOffset) => m_fields[fieldOffset].fieldName;
    public object GetFieldValue(int fieldOffset) => m_fields[fieldOffset].fieldValue;
}

// define a class to hold our generic method(s)
public static class SqlClientExtensions
{
    // implement a refactored version of the original method
    public static async IAsyncEnumerable<T> ProcessRows<T>(this SqlCommand command, Func<SqlResultSetRow, CancellationToken, ValueTask<T>> rowCallback, CommandBehavior commandBehavior = CommandBehavior.SequentialAccess, [EnumeratorCancellation] CancellationToken cancellationToken = default) {
        using (var dataReader = await command.ExecuteReaderAsync(commandBehavior, cancellationToken)) {
            var resultSetIndex = 0;

            do {
                var fieldCount = dataReader.FieldCount;
                var fieldNames = new string[fieldCount];
                var fieldTypes = new Type[fieldCount];

                for (var i = 0; (i < fieldCount); ++i) {
                    fieldNames[i] = dataReader.GetName(i);
                    fieldTypes[i] = dataReader.GetFieldType(i);
                }

                while (await dataReader.ReadAsync(cancellationToken)) {
                    var fields = new (string, Type, object)[fieldCount];

                    for (var i = 0; (i < fieldCount); ++i) {
                        fields[i] = (fieldNames[i], fieldTypes[i], dataReader.GetValue(i));
                    }

                    yield return await rowCallback(new SqlResultSetRow(fields, resultSetIndex), cancellationToken);
                }
            } while (await dataReader.NextResultAsync(cancellationToken));
        }
    }
}

class Program
{
    // a minimal implementation of a rowCallBack function
    public static async ValueTask<ExpandoObject> OnProcessRow(SqlResultSetRow resultSetRow, CancellationToken cancellationToken) {
        var rowValue = (new ExpandoObject() as IDictionary<string, object>);

        foreach (var field in resultSetRow) {
            rowValue[field.fieldName] = field.fieldValue;
        }

        return (rowValue as ExpandoObject);
    }

    // put everything together
    static async Task Main(string[] args) {
        try {
            var programTimeout = TimeSpan.FromMinutes(3);
            var cancellationTokenSource = new CancellationTokenSource(programTimeout);

            using (var connection = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=master;Integrated Security=True;"))
            using (var command = connection.CreateCommand()) {
                command.CommandText = "select 1 as [a];";
                command.CommandType = CommandType.Text;

                await connection.OpenAsync(cancellationToken: cancellationTokenSource.Token);

                await foreach (dynamic row in command.ProcessRows(rowCallback: OnProcessRow, cancellationToken: cancellationTokenSource.Token)) {
                    Console.WriteLine($"a: {row.a}");
                }
            }
        }
        catch (Exception e) {
            // do something with exception here

            throw;
        }
    }
}

Voici une version alternative qui utilise IAsyncEnumerable ; notez que nous sommes désormais autorisés à disposer des objets selon la même méthode. De plus, la logique qui traitait le cas du lecteur vide n'est plus nécessaire car notre énumérable sera simplement vide s'il n'y avait rien à lire.

private static SqlDataReader RunSql(SqlConnection connection, string procedureName) {
    using (var command = connection.CreateCommand()) {
        command.CommandText = procedureName;
        command.CommandType = CommandType.StoredProcedure;

        try {
            if (connection.State != ConnectionState.Open) {
                connection.Open();
            }

            var reader = command.ExecuteReader();

            if (reader.HasRows) {
                return reader;
            }
            else {
                return null;
            }
        }
        catch (Exception e) {
            // log the exception or whatever you wanna do with it

            throw; // rethrow the exception
        }
    }
}


7 commentaires

peut-être voulez-vous simplement enregistrer l'exception et permettre à l'application de continuer ...


L'inquiétude d'OP n'était pas dans le bloc d'exception mais en dehors du bloc d'utilisation qui n'a pas été résolu sur votre code également.


@MdFaridUddinKiron Vous n'avez pas tort, j'ai écrit le code entièrement dans l'éditeur SO et j'ai manqué la section où le flux de contrôle a échappé au bloc try . A également remarqué que l'OP avait le lecteur enveloppé dans un using , cela entraînera la suppression de l'objet dès que la méthode sera retournée; vient de résoudre les deux problèmes.


Mais votre code n'a toujours pas l'air élégant car si la condition échoue, vous retournez également null Je l'ai fait avant, mais ensuite je me suis figé sur le code d'OP. J'espère que tu as compris.


@MdFaridUddinKiron Je suis désolé, vous m'avez perdu quelque part; la seule fois où mon exemple renvoie null , c'est si le lecteur est vide. Personnellement, je ne pense pas qu'une telle conception soit très utile, mais cela semblait être ce que l'OP recherchait. Je crois que mon deuxième exemple est une approche beaucoup plus élégante qui capture encore l'esprit de ce que le PO essayait de faire.


Tout d'abord, l'approche des questions n'est pas du tout la meilleure pratique, j'ai passé quelques fois pendant que les op étaient coincés à lui apprendre plutôt la meilleure approche. Quoi qu'il en soit, votre deuxième approche est meilleure que la première.


@JonathanAlfaro Bien sûr, vouloir cela est une bonne chose, mais une telle logique devrait-elle être encodée dans une méthode qui se veut clairement générique? Probablement pas...