اذهب إلى المحتوى

السؤال

نشر

 

انا اقوم في زر جديد عندما يضغط عليه مستخدم

بجلب اخر رقم من جدول هل  دالة مناسبة علما بانه مفتاح اساسي وترقيم تلقائي عمود id 

public static int CODE_GENE(string TABLE_NAME, string COLUMN_NAME)
{
    // استخدم استعلام MAX بدل جلب كل الصفوف
    int CODE = 0;
    using (SqlCommand cmd = new SqlCommand(
        $"SELECT ISNULL(MAX({COLUMN_NAME}),0) FROM {TABLE_NAME}", sqlCon))
    {
        if (sqlCon.State != System.Data.ConnectionState.Open)
            sqlCon.Open();

        CODE = Convert.ToInt32(cmd.ExecuteScalar());
    }
    return CODE;
}

علمًا بأنه لدي منطق حفظ فاتورة في حالة يعمل علي نظام أكثر من شخص بحيث يتم اصدار فاتورة أكثر من شخص في وقت واحد 

اعتماد رصيد صنف علي حركة صنف وليس تحديث مخزن بعد كل عملية بيع او تعديل فاتورة او فاتورة ترجيع 

if (comboBox1.SelectedIndex == -1)
{
    MessageBox.Show("الرجاء إدخال اسم زبون", "تنبيه", MessageBoxButtons.OK, MessageBoxIcon.Warning);
    return;
}
if (dataGridView4.Rows.Count == 0)
{
    MessageBox.Show("الرجاء إدخال أصناف في الفاتورة", "تنبيه", MessageBoxButtons.OK, MessageBoxIcon.Warning);
    return;
}

decimal totalInvoice = 0;
foreach (DataGridViewRow row in dataGridView4.Rows)
{
    if (row.Cells[3].Value != null)
        totalInvoice += Convert.ToDecimal(row.Cells[3].Value);
}

textBox2.Text = totalInvoice.ToString("N2");
string totalAr = Class1.NumberToWords(Convert.ToDouble(textBox2.Text), "دينار ", "درهم");

using (SqlConnection con = new SqlConnection(Class1.sqlCon.ConnectionString))
{
    con.Open();
    SqlTransaction trans = con.BeginTransaction();

    try
    {
        string sqlInsertInvoice = @"INSERT INTO Invoices (invoice_date, customer_name, total_amount, total_ar) 
                                    VALUES (@date, @cus, @total, @total_ar);
                                    SELECT SCOPE_IDENTITY();";

        SqlCommand cmdInvoice = new SqlCommand(sqlInsertInvoice, con, trans);
        cmdInvoice.Parameters.AddWithValue("@date", dateTimePicker1.Value);
        cmdInvoice.Parameters.AddWithValue("@cus", comboBox1.Text);
        cmdInvoice.Parameters.AddWithValue("@total", totalInvoice);
        cmdInvoice.Parameters.AddWithValue("@total_ar", totalAr);

        object result = cmdInvoice.ExecuteScalar();
        string newInvoiceID = result.ToString();

        foreach (DataGridViewRow row in dataGridView4.Rows)
        {
            if (row.IsNewRow) continue;

            int itemId = Convert.ToInt32(row.Cells[5].Value);
            decimal qty = Convert.ToDecimal(row.Cells[1].Value);
            decimal price = Convert.ToDecimal(row.Cells[2].Value);
            decimal rowTotal = Convert.ToDecimal(row.Cells[3].Value);
            string itemName = row.Cells[0].Value.ToString();
            string storeNum = row.Cells[4].Value.ToString();

            string sqlDetails = @"INSERT INTO InvoiceDetails (invoice_number, item_name, item_id, inv_date, quantity, unit_price, total_price, store_number)
                                  VALUES (@invNo, @name, @id, @date, @qty, @price, @tot, @store)";

            SqlCommand cmdDetails = new SqlCommand(sqlDetails, con, trans);
            cmdDetails.Parameters.AddWithValue("@invNo", newInvoiceID);
            cmdDetails.Parameters.AddWithValue("@name", itemName);
            cmdDetails.Parameters.AddWithValue("@id", itemId);
            cmdDetails.Parameters.AddWithValue("@date", dateTimePicker1.Value);
            cmdDetails.Parameters.AddWithValue("@qty", qty);
            cmdDetails.Parameters.AddWithValue("@price", price);
            cmdDetails.Parameters.AddWithValue("@tot", rowTotal);
            cmdDetails.Parameters.AddWithValue("@store", storeNum);
            cmdDetails.ExecuteNonQuery();

            string sqlUpdateStock = @"UPDATE inventory SET quantity = quantity - @qty 
                                      WHERE item_id = @id AND quantity >= @qty";

            SqlCommand cmdStock = new SqlCommand(sqlUpdateStock, con, trans);
            cmdStock.Parameters.AddWithValue("@qty", qty);
            cmdStock.Parameters.AddWithValue("@id", itemId);

            int rowsAffected = cmdStock.ExecuteNonQuery();
            if (rowsAffected == 0)
            {
                throw new Exception("الكمية غير متوفرة حالياً للصنف: " + itemName);
            }

            string dayName = dateTimePicker1.Value.ToString("dddd", new CultureInfo("ar-LY"));
            string sqlMove = @"INSERT INTO ItemDailyMovement (item_id, movement_date, day_name, quantity_sold, invoice_number, item_name, name_cus, type, invotry_num)
                               VALUES (@id, @date, @day, @qty, @inv, @iname, @cus, 'مبيعات', @store)";

            SqlCommand cmdMove = new SqlCommand(sqlMove, con, trans);
            cmdMove.Parameters.AddWithValue("@id", itemId);
            cmdMove.Parameters.AddWithValue("@date", dateTimePicker1.Value);
            cmdMove.Parameters.AddWithValue("@day", dayName);
            cmdMove.Parameters.AddWithValue("@qty", qty);
            cmdMove.Parameters.AddWithValue("@inv", newInvoiceID);
            cmdMove.Parameters.AddWithValue("@iname", itemName);
            cmdMove.Parameters.AddWithValue("@cus", comboBox1.Text);
            cmdMove.Parameters.AddWithValue("@store", storeNum);
            cmdMove.ExecuteNonQuery();
        }

        Class1.OPER_CUSTOMERS(newInvoiceID, DateTime.Today, "مبيعات", totalInvoice, 0, comboBox1.Text, trans, label10.Text);

        trans.Commit();
        MessageBox.Show("تم حفظ الفاتورة رقم " + newInvoiceID + " بنجاح", "تأكيد", MessageBoxButtons.OK, MessageBoxIcon.Information);

        fill_dgv3();
        fill_dgv2_inv();
        dataGridView4.Rows.Clear();
        comboBox1.SelectedIndex = -1;
        textBox2.Clear();
        label9.Text = "";
    }
    catch (Exception ex)
    {
        trans.Rollback();
        MessageBox.Show("فشل العملية: " + ex.Message, "خطأ", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

 

Recommended Posts

  • 0
نشر

دالة CODE_GENE غير مناسبة في بيئة متعددة المستخدمين، فستؤدي لمشكلة أمنية، حيث المستخدم الأول يقرأ MAX(ID) = 100

والمستخدم الثاني يقرأ MAX(ID) = 100 أي نفس الرقم، ثم المستخدم الأول يحفظ فاتورة برقم 101، والمستخدم B يحفظ فاتورة برقم 101 أي يوجد تكرار.

كذلك أنت في كود الحفظ تستخدم SCOPE_IDENTITY() بالفعل وذلك هو الصحيح.

بالتالي لعرض رقم الفاتورة القادم فقط للعرض أي تقديري، فالرقم الفعلي للفاتورة يجب أن يأتي من SCOPE_IDENTITY() بعد الحفظ، قم بما يلي:

public static int CODE_GENE_SAFE(string TABLE_NAME, string COLUMN_NAME)
{
    int CODE = 0;
    using (SqlConnection con = new SqlConnection(sqlCon.ConnectionString))
    {
        con.Open();
        using (SqlCommand cmd = new SqlCommand(
            $"SELECT ISNULL(MAX([{COLUMN_NAME}]), 0) + 1 FROM [{TABLE_NAME}]", con))
        {
            CODE = Convert.ToInt32(cmd.ExecuteScalar());
        }
    }
    return CODE;
}

 

  • 0
نشر
بتاريخ On 18‏/2‏/2026 at 18:38 قال Mustafa Suleiman:

دالة CODE_GENE غير مناسبة في بيئة متعددة المستخدمين، فستؤدي لمشكلة أمنية، حيث المستخدم الأول يقرأ MAX(ID) = 100

والمستخدم الثاني يقرأ MAX(ID) = 100 أي نفس الرقم، ثم المستخدم الأول يحفظ فاتورة برقم 101، والمستخدم B يحفظ فاتورة برقم 101 أي يوجد تكرار.

كذلك أنت في كود الحفظ تستخدم SCOPE_IDENTITY() بالفعل وذلك هو الصحيح.

بالتالي لعرض رقم الفاتورة القادم فقط للعرض أي تقديري، فالرقم الفعلي للفاتورة يجب أن يأتي من SCOPE_IDENTITY() بعد الحفظ، قم بما يلي:

public static int CODE_GENE_SAFE(string TABLE_NAME, string COLUMN_NAME)
{
    int CODE = 0;
    using (SqlConnection con = new SqlConnection(sqlCon.ConnectionString))
    {
        con.Open();
        using (SqlCommand cmd = new SqlCommand(
            $"SELECT ISNULL(MAX([{COLUMN_NAME}]), 0) + 1 FROM [{TABLE_NAME}]", con))
        {
            CODE = Convert.ToInt32(cmd.ExecuteScalar());
        }
    }
    return CODE;
}

 

سالت دكاء اصطناعي علي كودك اخي مصطفي هكدا كان رده 

سؤالك ممتاز ويدل على فهم عميق لمشاكل البيانات المتزامنة (Concurrency)، لكن الجواب المباشر هو: لا، هذه الطريقة لا تزال غير آمنة في البيئات متعددة المستخدمين (Multi-user System).

إنشاء اتصال جديد new SqlConnection يحمي فقط من التداخل في نفس فتحة الاتصال (Connection level)، لكنه لا يمنع أن يقوم مستخدمان بفتح فاتورة في نفس الميكروثانية ويحصلان على نفس الرقم.

لماذا يحدث التضارب (Race Condition)؟

المستخدم (أ) يضغط "فاتورة جديدة": الكود يستعلم عن MAX والنتيجة الحالية 100 -> يتولد عنده رقم 101.

المستخدم (ب) يضغط "فاتورة جديدة" في نفس اللحظة (قبل أن يحفظ المستخدم "أ" فاتورته): الكود يستعلم عن MAX والنتيجة لا تزال 100 -> يتولد عنده أيضاً رقم 101.

عندما يحاول أحدهما الحفظ، إما أن ترفض قاعدة البيانات الحفظ بسب تكرار المفتاح الرئيسي (Primary Key Violation)، أو يُحفظ السجلان بنفس الرقم!

ما هي الحلول الصحيحة لبيئة متعددة المستخدمين؟

1. الحل الأفضل والأكثر أماناً: استخدام IDENTITY أو SEQUENCE في SQL Server

دع قاعدة البيانات هي من تتولى توليد الرقم التلقائي لحظة الحفظ الفعلي وليس لحظة فتح الشاشة.

اعطني حل صحيح 

انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أجب على هذا السؤال...

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.

  • إعلانات

  • تابعنا على



×
×
  • أضف...