سؤال

الخلفية:لدي مجموعة من السلاسل التي أحصل عليها من قاعدة البيانات و أريد العودة لهم.تقليديا, سيكون شيئا مثل هذا:

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;
}

ولكن بعد ذلك اكتشفت أن المستهلك سوف ترغب في تكرار خلال العناصر و لا يهمني أي شيء آخر, و أود أن لا صندوق نفسي في قائمة في حد ذاتها حتى لو كنت إعادة IEnumerable كل ما هو جيد/مرن.لذلك كنت أفكر في استخدام "العائد العودة" نوع التصميم على التعامل مع هذا شيئا من هذا القبيل:

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();
            }
        }
    }
}

ولكن الآن أنا أقرأ أكثر قليلا عن العائد (على مثل هذه المواقع...msdn لا يبدو أن أذكر هذا), انها على ما يبدو كسول المقيم ، التي تحافظ على الدولة من populator حولها ، تحسبا من شخص يسأل عن القيمة التالية ثم تشغيل فقط حتى تقوم بإرجاع القيمة التالية.

هذا يبدو على ما يرام في معظم الحالات, ولكن مع DB الدعوة ، هذا يبدو قليلا مشبوه.باعتباره نوعا ما المفتعلة سبيل المثال ، إذا كان شخص ما يسأل عن IEnumerable من أنني ملء من DB الدعوة ، يحصل خلال النصف ومن ثم يحصل عالقا في حلقة...بقدر ما أستطيع أن أرى بلدي اتصال DB سوف تبقى مفتوحة إلى الأبد.

يبدو وكأنه يسأل عن المتاعب في بعض الحالات إذا كان مكرر لا تنتهي...أنا في عداد المفقودين شيئا ؟

هل كانت مفيدة؟

المحلول

وانها التوازن: هل ترغب في فرض كافة البيانات في الذاكرة على الفور حتى تتمكن من تحرير الاتصال، أو هل ترغب في الاستفادة من تدفق البيانات، على حساب ربط الاتصال للكل هذا الوقت

والطريقة أنا ننظر في الأمر، أن من المحتمل أن يكون هذا القرار حتى المتصل، والذي يعرف أكثر حول ما تريد القيام به. إذا كنت كتابة التعليمات البرمجية باستخدام كتلة مكرر، والطالب يمكن أن <م> جدا تحولت بسهولة أن تدفق شكل الى شكل مخزنة بالكامل:

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

إذا، من ناحية أخرى، يمكنك القيام التخزين المؤقت نفسك، وليس هناك طريقة المتصل يمكن العودة إلى نموذج التدفق.

وهكذا كنت على الارجح استخدام نموذج تدفق ويقول <م> صراحة في الوثائق ما تقوم به، وتقديم المشورة المتصل لاتخاذ قرار مناسب. قد تحتاج حتى لتوفير طريقة المساعد للاتصال في الأساس نسخة البث المباشر وتحويله إلى القائمة.

وبطبيعة الحال، إذا كنت لا تثق المتصلين بك لاتخاذ القرار المناسب، وكان لديك سبب وجيه للاعتقاد بأنهم لن تريد حقا أن تيار البيانات (على سبيل المثال انها لن بالعودة كثيرا على أي حال) ثم انتقل للنهج القائمة. وفي كلتا الحالتين، وتوثيق ذلك - أنها يمكن أن تؤثر جيدا كيف يستخدم قيمة الإرجاع

.

وثمة خيار آخر للتعامل مع كميات كبيرة من البيانات لاستخدام دفعات، وبطبيعة الحال - وهذا التفكير إلى حد ما بعيدا عن السؤال الأصلي، لكنه مقاربة مختلفة للنظر في الحالة التي يكون فيها تدفق تكون عادة جذابة

.

نصائح أخرى

وأنت لست دائما غير آمنة مع IEnumerable. إذا تركت GetEnumerator دعوة الإطار (وهو ما معظم الناس سوف تفعل)، ثم كنت آمنة. أساسا، كنت آمنة مثل carefullness من التعليمات البرمجية باستخدام الأسلوب الخاص بك:

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");
    }
}

وإذا كان يمكنك من affort لمغادرة اتصال قاعدة بيانات مفتوحة أو لا يعتمد على الهندسة المعمارية الخاص بك أيضا. إذا شارك الطالب في معاملة (والاتصال هو جند السيارات)، ثم سيتم الاحتفاظ اتصال مفتوحة من قبل الإطار على أي حال.

وثمة ميزة أخرى من yield هي (عند استخدام مؤشر من جانب الخادم)، وليس لديها التعليمات البرمجية لقراءة جميع البيانات (على سبيل المثال: 1000 المقالات) من قاعدة البيانات، إذا المستهلك يريد الخروج من الحلقة في وقت سابق ( مثال: بعد هذا البند 10TH). هذا يمكن تسريع الاستعلام عن البيانات. وخاصة في بيئة أوراكل، حيث المؤشرات من جانب الخادم هي طريقة شائعة لاسترداد البيانات.

أنت لم تفوت أي شيء.نموذج يوضح كيفية استخدام العائد العودة.إضافة عناصر إلى قائمة إغلاق الاتصال ، والعودة القائمة.طريقة التوقيع لا تزال تعود IEnumerable.

تحرير: وقال جون لديه نقطة (متفاجئة!):هناك حالات نادرة حيث الجري هو في الواقع أفضل شيء نفعله من منظور الأداء.بعد كل شيء, إذا كان 100,000 (1,000,000?10,000,000?) الصفوف نتحدث عنه هنا, كنت لا تريد أن يتم تحميل هذا كل ما في الذاكرة الأولى.

بوصفها جانبا - علما بأن IEnumerable<T> النهج أساسا ما ينق مقدمي (LINQ إلى SQL, LINQ إلى كيانات) لا من أجل لقمة العيش.نهج مزاياه ، كما يقول جون.ومع ذلك ، هناك مشاكل محددة جدا - على وجه الخصوص (بالنسبة لي) حيث (مزيج) الفصل | التجريد.

ما أعنيه هنا هو أن:

  • في MVC السيناريو (على سبيل المثال) تريد "الحصول على البيانات" خطوة إلى في الواقع الحصول على البيانات, بحيث يمكنك اختبار ذلك يعمل في تحكم, لا عرض (دون الحاجة إلى تذكر الاتصال .ToList() الخ)
  • لا يمكنك ضمان أن آخر دال التنفيذ سيكون على إلى تيار من البيانات (على سبيل المثال ، جدري/WSE/الصابون الدعوة لا يمكن عادة تيار السجلات);و كنت لا تريد بالضرورة إلى جعل سلوك مشوش مختلفة (أياتصال لا تزال مفتوحة خلال التكرار مع تطبيق واحد ، وأغلقت آخر)

هذه العلاقات قليلا مع أفكاري هنا: عملي LINQ.

ولكن يجب أن الإجهاد - بالتأكيد هناك أوقات عندما يتدفقون هو مرغوب فيه للغاية.أنها ليست بسيطة "دائما مقابل لا شيء"...

وقليلا أكثر إيجازا وسيلة لإجبار تقييم مكرر:

using System.Linq;

//...

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

لا، كنت على الطريق الصحيح ... والعائد قفل القارئ ... يمكنك اختبار ذلك القيام استدعاء قاعدة بيانات أخرى أثناء استدعاء IEnumerable

والطريقة الوحيدة هذا من شأنه أن يسبب المشاكل هو إذا كانت انتهاكات المتصل بروتوكول IEnumerable<T>. الطريقة الصحيحة لاستخدامه لاستدعاء Dispose عليه عندما لم يعد هناك حاجة.

وتنفيذ الناتجة عن yield return يأخذ الدعوة Dispose كإشارة لتنفيذ أي كتل finally المفتوحة، التي في المثال الخاص بك سوف ندعو Dispose على الكائنات التي قمت بإنشائها في البيانات using.

وهناك عدد من ميزات اللغة (في foreach وجه الخصوص) التي تجعل من السهل جدا للاستخدام IEnumerable<T> بشكل صحيح.

هل يمكن دائما استخدام موضوع منفصل للتخفيف من البيانات (ربما إلى قائمة انتظار) في حين تقوم أيضا yeild لإرجاع البيانات. عندما تطلب بيانات المستخدم (عاد عن طريق yeild)، تتم إزالة عنصر من قائمة الانتظار. ويجري أيضا إضافة البيانات بشكل مستمر إلى قائمة الانتظار من خلال موضوع منفصل. وبهذه الطريقة، إذا طلب المستخدم البيانات بسرعة كافية، قائمة الانتظار أبدا الكامل للغاية، وكنت لا داعي للقلق حول مشاكل الذاكرة. إذا لم يفعلوا ذلك، ثم قائمة الانتظار سوف تملأ، والتي قد لا يكون سيئا للغاية. إذا كان هناك نوعا من القيد كنت ترغب في فرض على الذاكرة، هل يمكن فرض الحد الأقصى لحجم الطابور (وعند هذه النقطة في موضوع آخر أن تنتظر العناصر المراد إزالتها قبل أن يضيف المزيد إلى قائمة الانتظار). وبطبيعة الحال، سوف تحتاج للتأكد من التعامل مع الموارد (أي قائمة الانتظار) بشكل صحيح بين المواضيع اثنين.

وكبديل لذلك، يمكن أن إجبار المستخدم لتمرير في منطقية لبيان ما إذا كان ينبغي أو لا ينبغي مخزنة البيانات. إذا كان هذا صحيحا، يتم تخزينها مؤقتا البيانات وإغلاق الاتصال في أقرب وقت ممكن. إذا كاذبة، لا يتم تخزينها مؤقتا البيانات واتصال قاعدة البيانات يبقى مفتوحا طالما يحتاج المستخدم أن يكون. وجود معلمة منطقية يفرض على المستخدم لجعل الاختيار، والتي تضمن يعرفونه عن هذه المسألة.

ولقد اصطدم هذا الجدار عدة مرات. استعلامات قاعدة البيانات SQL ليست بسهولة للبث مثل الملفات. بدلا من ذلك، الاستعلام فقط بقدر ما كنت تعتقد أنك سوف تحتاج إليها وإعادته كما أيا كان الحاوية التي تريد (IList<>، DataTable، وما إلى ذلك). سوف IEnumerable لن يساعدك هنا.

ما يمكنك القيام به هو استخدام SqlDataAdapter بدلا من ذلك وملء DataTable و. شيء من هذا القبيل:

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();
    }
}

وبهذه الطريقة، وكنت الاستعلام عن كل شيء في طلقة واحدة، وإغلاق الاتصال على الفور، ولكن كنت لا تزال بالتكرار بتكاسل النتيجة. وعلاوة على ذلك، فإن المتصل هذا الأسلوب لا يمكن أن يلقي النتيجة إلى قائمة وتفعل شيئا لا ينبغي أن تفعل.

لا أميل استخدام العائد هنا. عينتك على ما يرام.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top