تحليل وإصلاح اثنين من الأخطاء في دلفي
عند استخدام دلفي 7 لتطوير قاعدة بيانات ثلاثية المستويات، واجهت مشكلتين صغيرتين من خلال التجارب المتكررة، اكتشفت أخيرًا الخطأين الصغيرين في دلفي 7 وقمت بإصلاحهما (يبدو أن هناك نفس الأخطاء في دلفي 6)، أكتب. يشارك هذا المقال فرحة النجاح مع الجميع. أنا أيضًا جديد على دلفي، لذلك يجب أن يكون هناك الكثير من الأخطاء في المقالة، يرجى تصحيحي.
يتم اقتطاع الأحرف الصينية BUG1 عند تمرير المعلمات:
كيفية إعادة إنتاج الخطأ:
يتم استخدام SQL Server 2000 في الخلفية، ويوجد جدول XsHeTong للاختبار، ويمكنك ضبطه وفقًا لموقفك الفعلي.
قم أولاً بإنشاء خادم بيانات: أنشئ مشروعًا جديدًا، وأنشئ وحدة بيانات عن بعد، ثم ضع ADOConnection وADODataSet وDataSetPRovider عليه، ثم قم بتعيين الإعدادات المقابلة لـ ComamndText لـ ADODataSet فارغًا، وقم بتعيين poAllowCommandText في الخيار الخاص به على True. تجميع وتشغيل.
إنشاء برنامج عميل مرة أخرى: إنشاء مشروع جديد، ووضع DCOMConnection في النموذج، والاتصال بخادم البيانات الذي تم إنشاؤه مسبقًا، ووضع ClientDataSet، وتعيين اتصاله بـ DCOMConnection هنا، وتعيين اسم الموفر الخاص به إلى الخادم أعلاه اسم DataSetProvider. أخيرًا، ضع DataSource وDBGrid وقم بإجراء الإعدادات المقابلة لعرض النتائج، ثم ضع زرًا للاختبار.
اكتب رمزًا مشابهًا لما يلي في Button's OnClick (هنا استخدمت جدول XsHeTong وحقليه HTH (char 15)، GCMC (varchar 100)، ويمكنك ضبطه وفقًا لحالة الاختبار الفعلية لديك):
مع ClientDataSet1 القيام به
يبدأ
يغلق؛
CommandText := 'أدخل في قيم XsHeTong(HTH, GCMC)(:HTH,:GCMC)';
Params[0].AsString := '12345';
Params[1].AsString := 'الأحرف الصينية التي سيتم اقتطاعها';
ينفذ؛
يغلق؛
CommandText := 'Select * from XsHeTong';
يفتح؛
نهاية؛
قم بتشغيل البرنامج، وانقر فوق الزر، ولاحظ أنه تم إدراج السجل، لسوء الحظ، النتيجة غير صحيحة. "الأحرف الصينية التي سيتم اقتطاعها" تصبح "سيتم اقتطاعها"، ولكن تم إدراج "12345" بدون الأحرف الصينية بشكل صحيح. .
تحليل الأخطاء وإصلاحها:
للمقارنة، حاولت استخدام ADOConnection وADOCommand وADOTable مباشرة لاختبار بنية C/S وكانت النتيجة صحيحة ولن يتم قطع الأحرف الصينية. يوضح هذا أن هذا الخطأ يظهر فقط في البنية ثلاثية الطبقات.
استخدم SQL Server Profiler للتحقق من البيانات التي تم إرسالها للتشغيل على SQL Server والعثور على الاختلافات التالية بين البنية ذات المستويين والبنية ثلاثية الطبقات:
العمارة ذات المستويين:
exec sp_executesql N'Insert into XsHeTong(HTH, GCMC) value(@P1,@P2)', N'@P1 varchar(15),@P2 varchar(100)', '12345', 'الأحرف الصينية التي سيتم اقتطاعها '
العمارة ثلاثية الطبقات:
exec sp_executesql N'إدراج في قيم XsHeTong(HTH, GCMC)(@P1,@P2)', N'@P1 varchar(5),@P2 varchar(7)', '12345', 'سيتم اقتطاعها
من الواضح أنه في البنية المكونة من طبقتين، يتم تمرير طول المعلمة وفقًا لبنية المكتبة الفعلية. في البنية ثلاثية الطبقات، يتم تمرير طول المعلمة وفقًا لطول سلسلة المعلمة الفعلية يبدو أن طول السلسلة قد تم حسابه بشكل خاطئ. يتم التعامل مع الحرف الصيني على أنه حرفين.
ليس هناك خيار سوى التتبع والتصحيح من أجل تصحيح أخطاء مكتبة VCL الخاصة بـ Delphi، تحتاج إلى تحديد "Use Debug DCUs" في "Compiler Options" لخيارات المشروع.
قم أولاً بتتبع برنامج العميل، ثم ClientDataSet1.Execute، ثم انتقل عبر سلسلة من الوظائف مثل TCustomClientDataSet.Exectue، وTCustomeClientDataSet.PackageParams، وTCustomClientDataSet.DoExecute، وما إلى ذلك، حتى AppServer.AS_Execute(ProviderName, CommandText, Params, OwnerData); يرسل الطلب إلى الخادم لا توجد أي شذوذ ويبدو أن المشكلة تكمن في جانب الخادم.
بعد تتبع الخادم والمحاولات المتكررة، ركزت على وظيفة TCustomADODataSet.PSSetCommandText، وبعد التتبع المتكرر والمفصل، أصبح الهدف أكثر دقة: TCustomADODataSet.PSSetParams، TParameter.Assign، TParameter.SetValue، VarDataSize. لقد عثرت أخيرًا على مصدر الخطأ: وظيفة VarDataSize، وهذا هو الكود الخاص بها:
الدالة VarDataSize(const Value: OleVariant): Integer;
يبدأ
إذا VarIsNull(Value) إذن
النتيجة := -1
وإلا إذا كان VarIsArray(Value) إذن
النتيجة: = VarArrayHighBound(Value, 1) + 1
وإلا إذا TVarData(Value).VType = varOleStr إذن
يبدأ
النتيجة := Length(PWideString(@TVarData(Value).VOleStr)^);
إذا كانت النتيجة = 0 ثم
النتيجة := -1؛
نهاية
آخر
النتيجة := SizeOf(OleVariant);
نهاية؛
في هذه الوظيفة يتم حساب طول المعلمة الفعلية، فهي تأخذ عنوان القيمة في القيمة وتستخدمها كمؤشر WideString للعثور على طول السلسلة يتم اقتطاعها" يصبح الطول 7 بدلاً من 14.
بمجرد العثور على المشكلة، ليس من الصعب حلها ببساطة
النتيجة := Length(PWideString(@TVarData(Value).VOleStr)^);
التغيير الى
النتيجة := Length(PAnsiString(@TVarData(Value).VOleStr)^); //لا مشكلة
هذا كل شيء.
لكن هذا سيؤدي إلى مضاعفة الطول عند إيجاد طول السلسلة الإنجليزية، لذلك يمكنك أيضًا تغيير هذا السطر إلى:
النتيجة: = الطول (القيمة)؛
وبهذه الطريقة، يمكن الحصول على الطول الصحيح سواء كانت سلسلة صينية أو إنجليزية أو مختلطة بين الصينية والإنجليزية. هذا سؤال لا يزال يحيرني، لماذا يدور بورلاند في دائرة للعثور على طول قيمة المعلمة من خلال المؤشر؟ إذا كان أي شخص يعرف، يرجى توضيح ذلك لي شكرا جزيلا لك.
قد يكون لدى بعض الأصدقاء أسئلة، لماذا لا تحدث مشكلة اقتطاع السلسلة عندما لا يتم ذلك من خلال بنية ثلاثية المستويات؟ الإجابة ليست معقدة. عند إرسال الأوامر إلى SQL Server مباشرة من خلال ADOCommand، يتم تحديد طول المعلمة وفقًا لبنية الجدول. سيتم أولاً إرسال رسالة إلى SQL Server
SET FMTONLY ON حدد HTH، GCMC من XsHeTong SET FMTONLY OFF
للحصول على هيكل الجدول. ضمن البنية ثلاثية الطبقات، على الرغم من أن TCustomADODataSet يستخدم أيضًا كائن TADOCommand داخليًا لإصدار الأوامر، إلا أنه لا يستخدم هذه القيمة كطول المعلمة بعد الحصول على بنية الجدول، ولكنه يعيد حساب الطول بناءً على المعلمات الفعلية والنتيجة خطأ.
BUG2. مشكلة في حقل البحث الخاص بـ ClientDataSet:
كيفية إعادة إنتاج الخطأ:
قم بإنشاء مشروع جديد وقم بوضع مجموعتي ClientDataSet عليه، وهما cds1 وcds2. يمكن أن تكون مصادر البيانات الخاصة بهم عشوائية، ومن بينها cds1، أضف حقل بحث جديد فيه في قيمة cds1 للعثور على القيمة المقابلة في cds2.
بشكل عام، من الطبيعي تشغيل البرنامج، ولكن بمجرد ظهور القيمة في حقل البحث الخاص بـ cds1 مع علامة اقتباس واحدة "'" (يمكنك تعديل أو إضافة سجل، حاول إدخال علامة اقتباس واحدة)، سيحدث خطأ على الفور : ثابت السلسلة غير المنتهية.
تحليل الأخطاء وإصلاحها:
سبب هذا الخطأ أكثر وضوحًا من السبب السابق، ويجب أن يكون سببه الفشل في معالجة الآثار الجانبية لعلامات الاقتباس المفردة بشكل صحيح.
وبالمثل، دعونا نتتبع الكود المصدري لـ VCL:
قم بتشغيل البرنامج، وعندما يحدث خطأ، افتح نافذة Call Stack (في قائمة View->Debug Windows) للتحقق من استدعاءات الوظائف السابقة ولا توجد مشكلة إلى Lookup للتحقق من السبب. الأول استدعاء الوظيفة المتعلق بالبحث هو TField.CalcLookupValue. قمنا بتعيين نقطة توقف في هذه الوظيفة، وأعد تشغيل البرنامج، وقمنا بإجراء تصحيح الأخطاء بخطوة واحدة بعد الانقطاع.
TCustomClientDataSet.Lookup->TCustomClientDataSet.LocateRecord
بعد عدة استدعاءات للوظائف أعلاه، قمنا بتعيين الهدف بسرعة في عملية LocateRecord. في هذه العملية، نقوم بإنشاء شروط التصفية المقابلة بناءً على إعدادات حقل البحث، ثم نضيف شروط التصفية المقابلة إلى مجموعة البيانات المستهدفة وجدت، والخطأ يكمن في توليد شروط التصفية. على سبيل المثال، نحتاج إلى الانتقال إلى cds2 استنادًا إلى قيمة حقل Cust (من المفترض أن يكون 001) في cds1 والعثور على قيمة حقل CustName المقابلة استنادًا إلى قيمة حقل CustID. يجب أن يكون الشرط الذي تم إنشاؤه هو [CustID] = '001'، ولكن إذا كانت قيمة Cust هي aa'bb، فإن الشرط الذي تم إنشاؤه سيصبح [CustID] = 'aa'bb'، وهو ما يؤدي بوضوح إلى ثابت سلسلة غير مكتمل.
عادةً، عندما نحل مشكلة ظهور علامات الاقتباس المفردة ضمن علامات الاقتباس المفردة، نحتاج فقط إلى كتابة علامتي اقتباس في علامتي الاقتباس. وينطبق الشيء نفسه هنا طالما أن الشرط الذي تم إنشاؤه يصبح [CustID] = 'aa''bb'، هناك لن يكون هناك خطأ. لذلك يمكنك تعديل الكود المصدري مثل هذا:
ابحث عن الكود التالي في إجراء LocateRecord:
ftString، ftFixedChar، ftWideString، ftGUID
إذا (i = Fields.Count - 1) و (loPartialKey في الخيارات) ثم
ValStr := Format('''%s*''',[VarToStr(Value)]) آخر
ValStr := Format('''%s''',[VarToStr(Value)]);
التغيير إلى:
ftString، ftFixedChar، ftWideString، ftGUID:
إذا (i = Fields.Count - 1) و (loPartialKey في الخيارات) ثم
ValStr := Format('''%s*''',[ StringReplace(VarToStr(Value),'''','''''',[rfReplaceAll])])
آخر
ValStr := Format('''%s''',[ StringReplace(VarToStr(Value),'''','''''',[rfReplaceAll])]);
أي أنه عند إنشاء سلسلة شرط المرشح، قم بتغيير جميع علامات الاقتباس المفردة في قيمة عامل التصفية للشرط من واحد إلى اثنين.
من أجل التأكد من صحة هذا التعديل، قمت بفحص عملية LocateRecord المقابلة في TCustomADODataSet (عند استخدام حقل البحث في TADODataSet، لن تكون هناك أخطاء بسبب علامات الاقتباس المفردة، فقط عند استخدام TCustomClientDataSet)، فإن طريقة المعالجة الخاصة بها هي نفسها يختلف TCustomClientDataSet قليلاً، فهو ينشئ شروط التصفية من خلال الدالة GetFilterStr، ولكنه في GetFilterStr، يعالج مشكلة علامات الاقتباس المفردة بشكل صحيح. لذا، بالنظر إلى الأمر بهذه الطريقة، فإن مشكلة عدم التعامل بشكل صحيح مع علامات الاقتباس المفردة في LocateRecord لـ TCustomClientDataSet هي في الواقع إغفال بسيط من قبل Borland.