Esta pergunta surge ocasionalmente, mas não vi uma resposta satisfatória.
Um padrão típico é (a linha é um DataRow ):
if (row["value"] != DBNull.Value)
{
someObject.Member = row["value"];
}
Minha primeira pergunta é qual é mais eficiente (eu inverti a condição):
row["value"] == DBNull.Value; // Or
row["value"] is DBNull; // Or
row["value"].GetType() == typeof(DBNull) // Or... any suggestions?
este indica que .GetType () deve ser mais rápido, mas talvez o compilador conheça alguns truques que eu não conheço?
Segunda pergunta, vale a pena armazenar em cache o valor da linha ["value"] ou o compilador otimiza o indexador de qualquer maneira?
Por exemplo:
object valueHolder;
if (DBNull.Value == (valueHolder = row["value"])) {}
Notas:
- a linha ["valor"] existe.
- Eu não sei o índice da coluna (daí a pesquisa do nome da coluna).
- Estou perguntando especificamente sobre a verificação de DBNull e depois a atribuição (não sobre otimização prematura etc.).
Comparei alguns cenários (tempo em segundos, 10.000.000 de tentativas):
row["value"] == DBNull.Value: 00:00:01.5478995
row["value"] is DBNull: 00:00:01.6306578
row["value"].GetType() == typeof(DBNull): 00:00:02.0138757
Object.ReferenceEquals tem o mesmo desempenho que "=="
O resultado mais interessante? Se você não corresponder o nome da coluna por maiúsculas e minúsculas (por exemplo, "Valor" em vez de "valor", levará aproximadamente dez vezes mais (para uma sequência de caracteres):
row["Value"] == DBNull.Value: 00:00:12.2792374
A moral da história parece ser que, se você não puder procurar uma coluna por seu índice, verifique se o nome da coluna que você alimenta no indexador corresponde exatamente ao nome da DataColumn.
O armazenamento em cache do valor também parece ser quase duas vezes mais rápido:
No Caching: 00:00:03.0996622
With Caching: 00:00:01.5659920
Portanto, o método mais eficiente parece ser:
object temp;
string variable;
if (DBNull.Value != (temp = row["value"]))
{
variable = temp.ToString();
}
IDataRecord
extensões.Respostas:
Eu devo estar esquecendo alguma coisa. Não está verificando
DBNull
exatamente o que oDataRow.IsNull
método faz?Eu tenho usado os dois métodos de extensão a seguir:
Uso:
Se você não deseja
Nullable<T>
retornar valoresGetValue<T>
, poderá retornar facilmentedefault(T)
ou alguma outra opção.Em uma nota não relacionada, aqui está uma alternativa do VB.NET à sugestão do Stevo3000:
fonte
row.IsNull(columnName)
você já está lendo uma vez e novamente. Não estou dizendo que vai fazer a diferença, mas teoricamente pode ser menos eficiente ..System.Data.DataSetExtensions.DataRowExtensions.Field<T>(this System.Data.DataRow, string)
fazendo essencialmente a mesma coisa que o primeiro método?Você deve usar o método:
Considerando que está embutido no Framework, eu esperaria que este fosse o mais eficiente.
Eu sugeriria algo como:
E sim, o compilador deve armazená-lo em cache para você.
fonte
O compilador não otimizará o indexador (ou seja, se você usar a linha ["value"] duas vezes), então sim, será um pouco mais rápido:
e depois use o valor duas vezes; usando .GetType () corre o risco de problemas se for nulo ...
DBNull.Value
é realmente um singleton, então, para adicionar uma quarta opção - talvez você possa usar o ReferenceEquals -, mas, na realidade, acho que você está se preocupando demais aqui ... Não acho que a velocidade seja diferente entre "is", "== "etc será a causa de qualquer problema de desempenho que você estiver vendo. Perfile seu código inteiro e concentre-se em algo que importa ... não será isso.fonte
Eu usaria o seguinte código em C # (o VB.NET não é tão simples).
O código atribui o valor se não for nulo / DBNull, caso contrário, atribui o padrão que pode ser definido como o valor LHS, permitindo que o compilador ignore a atribuição.
fonte
oSomeObject.IntMember = If(TryCast(oRow("Value), Integer?), iDefault)
.TryCast
não fornece a mesma funcionalidade conveniente que oas
operador de C # paraNullable(Of T)
tipos. A maneira mais próxima de imitar isso é escrever sua própria função, como sugeri agora na minha resposta.Sinto que apenas poucas abordagens aqui não arriscam a perspectiva do OP mais preocupante (Marc Gravell, Stevo3000, Richard Szalay, Neil, Darren Koppand) e a maioria é desnecessariamente complexa. Consciente de que isso é uma micro-otimização inútil, deixe-me dizer que você deve basicamente empregar estes:
1) Não leia o valor do DataReader / DataRow duas vezes; portanto, armazene-o em cache antes de verificações e lançamentos / conversões nulos ou, melhor ainda, passe diretamente seu
record[X]
objeto para um método de extensão personalizado com a assinatura apropriada.2) Para obedecer ao acima, não use a
IsDBNull
função incorporada no seu DataReader / DataRow, pois isso chama orecord[X]
internamente; portanto, você fará isso duas vezes.3) A comparação de tipos sempre será mais lenta que a comparação de valores como regra geral. Apenas faça
record[X] == DBNull.Value
melhor.4) A transmissão direta será mais rápida do que chamar
Convert
classe pela conversão, embora eu tenha medo de que a segunda vacile menos.5) Por fim, acessar o registro pelo índice em vez do nome da coluna será mais rápido novamente.
Eu sinto que seguir as abordagens de Szalay, Neil e Darren Koppand será melhor. Eu particularmente gosto da abordagem do método de extensão de Darren Koppand, que inclui
IDataRecord
(embora eu queira restringir ainda maisIDataReader
) o nome do índice / coluna.Tome cuidado para chamá-lo:
e não
caso você precise diferenciar entre
0
eDBNull
. Por exemplo, se você tiver valores nulos nos campos de enumeração, odefault(MyEnum)
risco será que o primeiro valor de enumeração seja retornado. Então é melhor ligarrecord.GetColumnValue<MyEnum?>("Field")
.Desde que você está lendo a partir de um
DataRow
, eu iria criar método de extensão para ambosDataRow
eIDataReader
por DRY código comum.Então agora chame assim:
Eu acredito que é assim que deveria ter sido na estrutura (em vez dos métodos
record.GetInt32
,record.GetString
etc) em primeiro lugar - sem exceções em tempo de execução e nos dá a flexibilidade de lidar com valores nulos.Pela minha experiência, tive menos sorte com um método genérico para ler no banco de dados. Eu sempre tive de lidar com personalizado vários tipos, então eu tive que escrever meu próprio
GetInt
,GetEnum
,GetGuid
, etc métodos a longo prazo. E se você quisesse aparar espaços em branco ao ler a string do db por padrão ou tratarDBNull
como uma string vazia? Ou se o seu decimal deve ser truncado de todos os zeros à direita. Eu tive mais problemas com oGuid
tipo em que diferentes drivers de conector se comportavam de maneira diferente quando os bancos de dados subjacentes podem armazená-los como string ou binários. Eu tenho uma sobrecarga como esta:Com a abordagem do Stevo3000, considero a chamada um pouco feia e entediante, e será mais difícil criar uma função genérica com ela.
fonte
Há um caso problemático em que o objeto pode ser uma string. O código do método de extensão abaixo lida com todos os casos. Aqui está como você o usaria:
fonte
Pessoalmente, sou a favor dessa sintaxe, que usa o método IsDbNull explícito exposto por
IDataRecord
e armazena em cache o índice da coluna para evitar uma pesquisa de string duplicada.Expandido para facilitar a leitura, é algo como:
Reescrito para caber em uma única linha para compactação no código DAL - observe que neste exemplo estamos atribuindo
int bar = -1
serow["Bar"]
for nulo.A atribuição em linha pode ser confusa se você não souber que está lá, mas mantém toda a operação em uma linha, o que eu acho que melhora a legibilidade quando você está preenchendo propriedades de várias colunas em um bloco de código.
fonte
Não que eu tenha feito isso, mas você pode contornar a chamada do indexador duplo e ainda manter seu código limpo usando um método estático / extensão.
Ou seja.
Então:
Também tem o benefício de manter a lógica de verificação nula em um único local. A desvantagem é, obviamente, que é uma chamada de método extra.
Apenas um pensamento.
fonte
Eu tento evitar essa verificação o máximo possível.
Obviamente, não precisa ser feito para colunas que não podem conter
null
.Se você estiver armazenando em um tipo de valor Nullable (
int?
, etc.), poderá apenas converter usandoas int?
.Se você não precisar diferenciar entre
string.Empty
enull
, basta ligar.ToString()
, pois o DBNull retornarástring.Empty
.fonte
Eu sempre uso:
Achei curto e abrangente.
fonte
É assim que eu lido com a leitura do DataRows
Exemplo de uso:
Adereços para Monsters Got My .Net para código ChageTypeTo.
fonte
Eu fiz algo semelhante com métodos de extensão. Aqui está o meu código:
Para usá-lo, você faria algo como
fonte
se em um DataRow a linha ["fieldname"] isDbNull a substitua por 0, caso contrário, obtenha o valor decimal:
fonte
use assim
fonte
Eu tenho o IsDBNull em um programa que lê muitos dados de um banco de dados. Com o IsDBNull, ele carrega dados em cerca de 20 segundos. Sem IsDBNull, cerca de 1 segundo.
Então eu acho que é melhor usar:
fonte