Frage

Hintergrund:Ich habe eine Reihe von Zeichenfolgen, die ich bekomme aus einer Datenbank, und ich will zu Ihnen zurückkehren.Traditionell, es wäre so etwas wie dieses:

public List<string> GetStuff(string connectionString)
{
    List<string> categoryList = new List<string>();
    using (SqlConnection sqlConnection = new SqlConnection(connectionString))
    {
        string commandText = "GetStuff";
        using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection))
        {
            sqlCommand.CommandType = CommandType.StoredProcedure;

            sqlConnection.Open();
            SqlDataReader sqlDataReader = sqlCommand.ExecuteReader();
            while (sqlDataReader.Read())
            {
                categoryList.Add(sqlDataReader["myImportantColumn"].ToString());
            }
        }
    }
    return categoryList;
}

Aber dann habe ich die Abbildung der Verbraucher gehen zu wollen, zu iterieren über die Elemente und kümmert sich nicht um viel anderes, und ich möchte nicht die box selbst, die in einer Liste, per se, also wenn ich wieder eine IEnumerable-alles ist gut/flexibel.Also dachte ich mir ich könnte ein "yield return" - Typ design, dies zu behandeln...so etwas wie dieses:

public IEnumerable<string> GetStuff(string connectionString)
{
    using (SqlConnection sqlConnection = new SqlConnection(connectionString))
    {
        string commandText = "GetStuff";
        using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection))
        {
            sqlCommand.CommandType = CommandType.StoredProcedure;

            sqlConnection.Open();
            SqlDataReader sqlDataReader = sqlCommand.ExecuteReader();
            while (sqlDataReader.Read())
            {
                yield return sqlDataReader["myImportantColumn"].ToString();
            }
        }
    }
}

Aber jetzt bin ich beim Lesen ein bisschen mehr über die Ausbeute (auf Websites wie diese...msdn schien nicht zu erwähnen), es ist offenbar ein lazy evaluator, das hält den Zustand der populator herum, in der Erwartung, jemanden zu Fragen, für den nächsten Wert, und nur dann laufen, bis es liefert den nächsten Wert.

Dies scheint in Ordnung, in den meisten Fällen, aber mit ein DB-Aufruf, das klingt ein wenig riskant.Als ein etwas erfundenes Beispiel, wenn jemand fragt, für die ein IEnumerable-ich bin Auffüllen von DB aufrufen, wird durch Hälfte, und dann stecken in einer Schleife...so weit wie ich sehen kann, meine DB-Verbindung gehen, offen zu bleiben für immer.

Klingt wie ärger in einigen Fällen, wenn der iterator nicht fertig...bin ich etwas fehlt?

War es hilfreich?

Lösung

Es ist ein Balanceakt: tun Sie sofort alle Daten in den Speicher zwingen wollen, so dass Sie die Verbindung freisetzen können, oder haben Sie von Streaming-Daten profitieren wollen, auf Kosten der Bindung der Verbindung für alle, die Zeit bis ?

So wie ich es betrachtet, sollte diese Entscheidung möglicherweise bis zu dem Anrufer, die mehr über weiß, was sie tun wollen. Wenn Sie den Code unter Verwendung eines Iteratorblock schreiben, kann der Anrufer sehr drehte sich leicht, dass Streaming-Form in eine vollständig gepufferte Form:

List<string> stuff = new List<string>(GetStuff(connectionString));

Wenn auf der anderen Seite, Sie tun dem Puffer selbst, gibt es keine Möglichkeit kann der Anrufer zu einem Streaming-Modell zurück.

Also habe ich wahrscheinlich das Streaming-Modell verwenden würde und sagen: explizit in der Dokumentation, was sie tut, und beraten den Anrufer entsprechend zu entscheiden. Man könnte sogar eine Hilfsmethode zur Verfügung stellen möchten im Grunde die gestreamte Version zu nennen und sie in eine Liste umwandeln.

Natürlich, wenn Sie nicht über Ihre Anrufer vertrauen, um die entsprechende Entscheidung zu treffen, und Sie haben guten Grund zu glauben, dass sie nie den Datenstrom wirklich wollen (zB es wird nie viel sowieso zurück), dann gehen für die Liste Ansatz. So oder so, zu dokumentieren - es könnte sehr gut beeinflussen, wie der Rückgabewert verwendet wird

.

Eine weitere Möglichkeit mit großen Datenmengen für den Umgang ist Chargen zu verwenden, natürlich - das ist etwas weg von der ursprünglichen Frage zu denken, aber es ist ein anderer Ansatz in der Lage zu prüfen, wo Streaming normalerweise attraktiv sein würde

.

Andere Tipps

Sie sind nicht immer unsicher mit dem IEnumerable. Wenn Sie den Rahmen Aufruf GetEnumerator verlassen (was die meisten Menschen tun), dann sind Sie sicher. Grundsätzlich sind Sie so sicher wie das sehr professionell des Codes mit Ihrer Methode:

class Program
{
    static void Main(string[] args)
    {
        // safe
        var firstOnly = GetList().First();

        // safe
        foreach (var item in GetList())
        {
            if(item == "2")
                break;
        }

        // safe
        using (var enumerator = GetList().GetEnumerator())
        {
            for (int i = 0; i < 2; i++)
            {
                enumerator.MoveNext();
            }
        }

        // unsafe
        var enumerator2 = GetList().GetEnumerator();

        for (int i = 0; i < 2; i++)
        {
            enumerator2.MoveNext();
        }
    }

    static IEnumerable<string> GetList()
    {
        using (new Test())
        {
            yield return "1";
            yield return "2";
            yield return "3";
        }
    }

}

class Test : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("dispose called");
    }
}

Ob Sie sich leisten können die Datenbank-Verbindung öffnen oder nicht zu verlassen, hängt von Ihrer Architektur. Wenn der Anrufer in einer Transaktion beteiligt ist (und die Verbindung wird automatisch eingetragen), dann wird die Verbindung gehalten wird ohnehin durch den Rahmen offen.

Ein weiterer Vorteil von yield ist (wenn Sie einen serverseitigen Cursor), den Code nicht über alle Daten lesen (Beispiel: 1.000 Stück) aus der Datenbank, wenn Ihr Verbraucher aus der Schleife früher bekommen will ( Beispiel: nach dem 10. Punkt). Dies kann bis Abfragen von Daten beschleunigen. Besonders in einer Oracle-Umgebung, in der serverseitige Cursors sind die gemeinsamen Weg, um Daten abzurufen.

Sie vermissen nichts. Ihr Beispiel zeigt, wie NICHT Ausbeute Rückkehr zu verwenden. Fügen Sie die Elemente in einer Liste, schließen Sie die Verbindung, und die Liste zurück. Ihre Methode Signatur kann noch IEnumerable zurück.

Edit: Das heißt, Jon hat einen Punkt (so überrascht!): Es gibt seltene Fälle, in denen Streaming eigentlich das Beste, was aus Sicht der Leistung zu tun. Nach allem, wenn es 100.000 ist (1.000.000? 10.000.000?) Reihen wir hier sprechen, wollen Sie nicht zuerst, dass alle in den Speicher zu laden.

Als Nebenwirkung - beachten Sie, dass der IEnumerable<T> Ansatz ist im Wesentlichen , was die LINQ-Provider (LINQ to SQL, LINQ-to-Entities) für ein Leben tun. Der Ansatz hat Vorteile, wie Jon sagt. Allerdings gibt es bestimmte Probleme zu - insbesondere (für mich) in Bezug auf (die Kombination aus) Trennung | Abstraktion.

Was ich meine, hier ist, dass:

  • in einem MVC-Szenario (zum Beispiel) Sie möchten, dass Ihre "get Daten" Schritt zu tatsächlich Daten erhalten , so dass man es an der Controller funktioniert testen , nicht die Ansicht (ohne merken .ToList() usw. nennen)
  • Sie können nicht garantieren, dass eine andere DAL Implementierung wird können Daten streamen (zum Beispiel ein POX / WSE / SOAP-Aufruf kann in der Regel nicht Aufzeichnungen streamen); und Sie wollen nicht unbedingt das Verhalten zum Verwechseln anders machen (das heißt Verbindung noch offen während der Iteration mit einer Implementierung und geschlossen für eine andere)

Dies steht in einem Stück mit meinen Gedanken hier: Pragmatische LINQ .

Aber ich betone soll - es gibt definitiv Zeiten, in denen das Streaming sehr wünschenswert ist. Es ist nicht einfach „immer vs nie“ Sache ...

Etwas mehr prägnante Art und Weise Auswertung von Iterator zu erzwingen:

using System.Linq;

//...

var stuff = GetStuff(connectionString).ToList();

Nein, Sie auf dem richtigen Weg sind ... die Ausbeute wird den Leser sperren ... Sie können testen, sie eine andere Datenbank-Aufruf zu tun, während die IEnumerable Aufruf

Der einzige Weg, dies zu Problemen führen würde, ist, wenn die Anrufer Mißbräuche das Protokoll von IEnumerable<T>. Der richtige Weg, es zu benutzen ist Dispose auf es zu nennen, wenn es nicht mehr benötigt wird.

Die von yield return erzeugte Implementierung führt den Dispose Aufruf als Signal alle geöffneten finally Blöcke auszuführen, die in Ihrem Beispiel Dispose auf die Objekte aufrufen werden Sie in den using Aussagen erstellt haben.

Es gibt eine Reihe von Sprachfunktionen (insbesondere foreach), die es sehr einfach machen richtig IEnumerable<T> zu verwenden.

Sie könnte immer verwenden Sie einen separaten thread, die Daten zu Puffern (vielleicht eine Warteschlange), während auch eine yeild um die Daten zurückzugeben.Wenn der Benutzer anfordert, Daten (die zurückgegeben werden, über eine yeild), ein Element aus der queue entfernt.Daten werden auch kontinuierlich Hinzugefügt, um die Warteschlange über den separaten thread.Auf diese Weise, wenn der Benutzer anfordert, die Daten schnell genug, wird die Warteschlange ist nie sehr voll ist und Sie nicht haben, um sorgen über Speicher Probleme.Wenn nicht, dann wird die Warteschlange füllen, das kann nicht so schlimm sein.Wenn es irgendeine Art von Einschränkung, die Sie möchten, zu verhängen, um den Speicher, Sie durchsetzen konnte eine maximale queue-Größe (die anderen Threads warten würde, für die Elemente entfernt werden, bevor Sie mehr in die Warteschlange).Natürlich, werden Sie wollen, stellen Sie sicher, dass Sie mit den Ressourcen (D. H., das queue) korrekt zwischen den beiden threads.

Als alternative könnten Sie den Benutzer zwingen pass in a boolean, um anzuzeigen, ob die Daten gepuffert werden soll.Wenn true, werden die Daten gepuffert ist und die Verbindung wird geschlossen sobald als möglich mit.Wenn false, werden die Daten nicht gepuffert und die Datenbank-Verbindung bleibt so lange offen, wie der Benutzer Sie benötigt werden.Mit einem booleschen parameter zwingt den Benutzer, um die Wahl zu treffen, die sicherstellt, dass Sie wissen über das Thema.

Ich habe ein paar Mal in diese Wand stößt. SQL-Datenbank-Abfragen sind nicht leicht streambare wie Dateien. Stattdessen Abfrage nur so viel, wie Sie denken, dass Sie es brauchen und zurück, wie alles, was Sie wollen Behälter (IList<>, DataTable, etc.). IEnumerable werden Sie hier nicht helfen.

Was Sie tun können, ist ein SqlDataAdapter verwenden statt und eine Datentabelle füllen. So etwas wie folgt aus:

public IEnumerable<string> GetStuff(string connectionString)
{
    DataTable table = new DataTable();
    using (SqlConnection sqlConnection = new SqlConnection(connectionString))
    {
        string commandText = "GetStuff";
        using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection))
        {
            sqlCommand.CommandType = CommandType.StoredProcedure;
            SqlDataAdapter dataAdapter = new SqlDataAdapter(sqlCommand);
            dataAdapter.Fill(table);
        }

    }
    foreach(DataRow row in table.Rows)
    {
        yield return row["myImportantColumn"].ToString();
    }
}

Auf diese Weise sind Sie alles in einem Schuss Abfrage und die Verbindung sofort zu schließen, doch Sie Iterieren noch gemächlich das Ergebnis. Darüber hinaus kann der Aufrufer dieser Methode nicht das Ergebnis in eine Liste werfen und etwas tun, sollten sie nicht tun.

Dont Verwendung Ausbeute hier. Ihre Probe ist in Ordnung.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top