avianrand Ответов: 6

Концепция EOF/BOF для sqldatareader?


Я знаю, что EOF и BOF не существуют для SqlDataReader, но мне интересно, есть ли стратегия, которая могла бы дублировать эту идею.

Я знаю, как перебирать с ним строки.

rdr = cmd.ExecuteReader
If rdr.HasRows Then
    While rdr.Read

    End While
End If


Но я хотел бы сделать это вместо этого, потому что очень неудобно использовать приведенный выше код, когда вам нужно что-то сделать в конце каждой группы. Я имею в виду, если у вас есть такой набор данных, как этот:

Клиент CarsOwned
Бекки БМВ
Бекки Форд
Бекки Хонда
Фред Форд
Фред Хонда
Фред Киа
Джордж гроссмейстер
Джордж Мерседес
Джон Ауди
Джон Хонда
Джон Киа
Джон Мерседес
Джон Вольво
Джон Фольксваген
Салли БМВ
Салли Крайслер
Салли Форд
Салли ГМ

Когда вы проходите через все вышесказанное и вам нужно что-то сделать в конце каждого клиента, это боль.

Это намного проще сделать:

'pseudo code: move to the first row
While 1=1    
    'pseudo code: if we're at the end of the dataset, break out of the while here    

    '... other code here

    'pseudo code: move to the next row
    'if on the next customer or at the very end, do some things here
End While


Я часто делаю это в T-SQL и в Delphi. Это конечно было бы неплохо сделать с vb.net

Спасибо,

Птичий

Что я уже пробовал:

Я пытался сделать эту работу и искал решения. Пока не нашел никаких идей.

0x01AA

Довольно некрасиво, но я думаю, что это сделает свою работу (я надеюсь, что формат/отступы выживут в комментарии. Извините, никаких отступов не осталось)
// Псевдокод
string currPerson= строка.Пустой;
bool eof= !rdr.HasRows;
хотя (!ВФ)
{
ВФ= !чт.Читать();
боол команды newgrp= ложь;
если (!eof && !rdr.Пункт("Имя").Метод toString().Равно(currPerson))
{
newGrp= currPerson != строка.Пустой;
currPerson= rdr.Пункт("Имя").Метод toString();
}

if (eof || newGrp)
{
// Сделайте что-нибудь на newGrp или в самом конце
}
}

6 Ответов

Рейтинг:
8

avianrand

Ну, история с автомобилями была просто примером. На самом деле моя проблема гораздо сложнее и не стоит того, чтобы ее здесь замазывать. Моя проблема была очевидна. Идея словаря хороша, но я просто иду с моим оригинальным решением, которое состоит в том, чтобы дважды вызвать подпрограмму. Один раз в верхней части петли и еще раз после того, как это будет сделано. Это прекрасно работает. Ничего особенного. Мне просто не нравится этот метод. То, как я справляюсь с подобными вещами в T-SQL, чище. Очень жаль, что MS не сочла нужным добавить EOF в SqlDataAdapter. Довольно глупо, ИМО. Спасибо за вашу помощь, подтверждающую, что моя первоначальная идея-это путь сюда.


Рейтинг:
2

Mike V Baker

OK so right after I said I couldn't think of a way to avoid it, I thought of one. The appropriateness of the solution depends on what you're trying to do, of course. But you can avoid repeating the item for the change from one to another by using a dictionary and processing each one identically. It removes the need to track a 'current' item and handle the transition and therefore the need to manage the tail end when there's no transition. It also, as a side-effect, removes the need for the items to be sorted by name which will make the select run faster. There is the added storage cost in the dictionary but depending on your needs... anyway, here you go.

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Dim peopleCarsDict As New Dictionary(Of String, List(Of String))

    Try
        Using conn As New SqlConnection("Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=Tester;Persist Security Info=True;Integrated Security=true")
            conn.Open()

            Using cmd As New SqlCommand("SELECT * FROM PeopleCars", conn)
                Using rdr = cmd.ExecuteReader()
                    If rdr.HasRows Then
                        While rdr.Read
                            AddToDict(peopleCarsDict, rdr.Item("FirstName").ToString(), rdr.Item("CarType").ToString())
                        End While
                    End If
                End Using
            End Using

        End Using
    Catch ex As Exception
        ' report error to user
    End Try

    ' now anything can be done with the dictionary
    For Each kvp As KeyValuePair(Of String, List(Of String)) In peopleCarsDict
        Diagnostics.Debug.WriteLine(kvp.Key + " has " + kvp.Value.Count + " cars")
    Next

End Sub

Private Sub AddToDict(ByRef dict As Dictionary(Of String, List(Of String)), key As String, val As String)
    Dim valArray As New List(Of String)
    ' if new person then create dict entry
    If Not dict.ContainsKey(key) Then
        dict.Add(key, New List(Of String))
    End If
    ' get the proper array and add the val
    If dict.TryGetValue(key, valArray) Then
        valArray.Add(val)
    Else
        ' something went wrong here, we couldn't get the array!!
        Throw New Exception("Could not get array")
    End If
End Sub


Кстати, словарь также может быть списком(Person), где Person - это пользовательский класс с именем и списком(String) автомобилей.

Майк


Рейтинг:
2

avianrand

Привет, Майк. Да, именно этим я сейчас и занимаюсь. Но я стараюсь избегать повторения кода "конец группы". Это не ужасно. Обычно я пишу небольшую рутину, которую вызываю в обоих местах, но другой способ понятнее и проще кодировать, ИМО. - птичий :-)


Mike V Baker

Таким образом, вы только пытаетесь избежать повторения кода "сделать что-то в конце группы"? Я не знаю, как этого избежать. Конечно, если это больше одной или двух строк, я бы записал их в функцию и вызвал эту функцию, чтобы не было большого дублирования. Кроме этого, что-то вроде этого не заставило бы меня спать по ночам.
Что это было, что вы пытаетесь сделать с этим кодом? Если вы считаете автомобили на человека, то, возможно, в sql было бы лучше..

Выберите FirstName, COUNT(CarType) из PeopleCars GROUP BY FirstName

Естественно, в реальном приложении эта информация не будет находиться в одной таблице. "PeopleCars" здесь, в этой выборке, скорее всего, будет объединением трех таблиц.

Майк

Рейтинг:
1

Mike V Baker

Думаешь, это сделает то, что ты хочешь?

Using conn As New SqlConnection("Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=Tester;Persist Security Info=True;Integrated Security=true")
    conn.Open()

    Using cmd As New SqlCommand("SELECT * FROM PeopleCars ORDER BY FirstName, CarType", conn)
        Using rdr = cmd.ExecuteReader()
            If rdr.HasRows Then
                Dim currPerson As String = ""
                While rdr.Read
                    Diagnostics.Debug.WriteLine(rdr.Item("FirstName").ToString())
                    If Not rdr.Item("FirstName").ToString().Equals(currPerson) Then
                        If currPerson <> "" Then
                            ' do something at the end of a group
                        End If
                        currPerson = rdr.Item("FirstName")
                    End If
                End While
                ' do something with the last group
            End If
        End Using
    End Using

End Using


ХТХ,
Майк


Рейтинг:
1

Richard Deeming

Самым простым вариантом было бы использовать LINQ.

Начните с метода расширения, который вам нужно написать только один раз:

Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Data.Common

Module DataReaderExtensions
    Private Shared Iterator Function AsEnumerableCore(ByVal reader As IDataReader) As IEnumerable(Of IDataRecord)
        Dim enumerator = New DbEnumerator(reader)
        While enumerator.MoveNext()
            Yield CType(enumerator.Current, IDataRecord)
        End While
    End Function

    <Extension()>
    Public Shared Function AsEnumerable(ByVal reader As IDataReader) As IEnumerable(Of IDataRecord)
        If reader Is Nothing Then Throw New ArgumentNullException(NameOf(reader))
        
        Dim dbReader = TryCast(reader, DbDataReader)
        If dbReader IsNot Nothing Then Return dbReader.Cast(Of IDataRecord)()
        Return AsEnumerableCore(reader)
    End Function
End Module

Затем вы можете сгруппировать записи, возвращаемые вашим устройством чтения данных:
Using rdr = cmd.ExecuteReader()
    Dim groupedRecords = rdr.AsEnumerable().GroupBy(Function (r) r["FirstName"].ToString())
    
    For Each recordGroup As IGrouping(Of String, IDataRecord) In groupedRecords
        Dim name As String = recordGroup.Key
        
        ' ... Start of group here ...
        For Each record As IDataRecord In recordGroup
            ' ... Detail here ...
        Next
        ' ... End of group here ...
    Next
End Using


Рейтинг:
1

Patrice T

Цитата:
Я знаю, что EOF и BOF не существуют для SqlDataReader, но мне интересно, есть ли стратегия, которая могла бы дублировать эту идею.

EOF и BOF-это функции и концепции, которые датируются примерно 40 годами, эпохой, когда человек работал непосредственно с файлом базы данных.
Все работало с номером записи в файле базы данных, проблема заключалась в том, что с индексами и фильтрами мы прыгали от записи к записи, и нужно было знать, когда было достигнуто начало файла или конец файла. Таким образом, 2 функции.
В SQL результат запроса уже отфильтрован и упорядочен, поэтому первая строка результата-BOF, а последняя-EOF. Так что вам не нужны функции.