Cloud Spanner: أنشِئ لوحة صدارة للألعاب باستخدام C#

1. نظرة عامة

Google Cloud Spanner هي خدمة قاعدة بيانات ارتباطية وقابلة للتطوير أفقيًا وموزعة عالميًا، وتوفِّر معاملات ACID ودلالات SQL بدون التخلّي عن الأداء والتوفّر العالي.

سوف تتعلم في هذا التمرين المعملي كيفية إعداد مثيل Cloud Spanner. ستتعرف على خطوات إنشاء قاعدة بيانات ومخطط يمكن استخدامهما لقائمة الصدارة في الألعاب. ستبدأ بإنشاء جدول "اللاعبين" لتخزين معلومات اللاعب وجدول النتائج لتخزين نتائج اللاعبين.

بعد ذلك، ستقوم بتعبئة الجداول بنماذج بيانات. ثم ستختتم التمرين المعملي بتشغيل بعض أهم عشرة استعلامات وأخيرًا حذف المثيل لتحرير الموارد.

المعلومات التي ستطّلع عليها

  • كيفية إعداد مثيل Cloud Spanner
  • كيفية إنشاء قاعدة بيانات وجداول
  • كيفية استخدام عمود الطابع الزمني للتنفيذ
  • كيفية تحميل البيانات إلى جدول قاعدة بيانات Cloud Spanner مع الطوابع الزمنية
  • كيفية طلب بحث في قاعدة بيانات Cloud Spanner
  • كيفية حذف مثيل Cloud Spanner

المتطلبات التي ستحتاج إليها

كيف ستستخدم هذا البرنامج التعليمي؟

القراءة فقط اقرأها وأكمِل التمارين

ما هو تقييمك لتجربتك في استخدام Google Cloud Platform؟

حديث متوسط بارع

2. الإعداد والمتطلبات

إعداد بيئة ذاتية

إذا لم يكن لديك حساب Google (Gmail أو Google Apps)، يجب عليك إنشاء حساب. سجِّل الدخول إلى وحدة تحكُّم Google Cloud Platform ( console.cloud.google.com) وأنشئ مشروعًا جديدًا.

إذا كان لديك مشروع بالفعل، فانقر فوق القائمة المنسدلة لاختيار المشروع في أعلى يسار وحدة التحكم:

6c9406d9b014760.png

وانقر فوق "مشروع جديد" في مربع الحوار الناتج لإنشاء مشروع جديد:

f708315ae07353d0.png

إذا لم يكن لديك مشروع، من المفترض أن يظهر لك مربّع حوار مثل هذا لإنشاء مشروعك الأول:

870a3cbd6541ee86.png

يتيح لك مربع الحوار اللاحق لإنشاء المشروع إدخال تفاصيل مشروعك الجديد:

6a92c57d3250a4b3.png

يُرجى تذكُّر رقم تعريف المشروع، وهو اسم فريد في جميع مشاريع Google Cloud (سبق أن تم استخدام الاسم أعلاه ولن يكون مناسبًا لك). ستتم الإشارة إليها لاحقًا في هذا الدرس التطبيقي حول الترميز باسم PROJECT_ID.

بعد ذلك، عليك تفعيل الفوترة في Developers Console لاستخدام موارد Google Cloud وتفعيل Cloud Spanner API إذا لم يسبق لك إجراء ذلك.

15d0ef27a8fbab27.png

لن يكلفك تنفيذ هذا الدرس التطبيقي أكثر من بضعة دولارات، ولكن قد تزيد التكاليف إذا قررت استخدام المزيد من الموارد أو إذا تركتها قيد التشغيل (يُرجى الاطّلاع على قسم "التنظيف" في نهاية هذا المستند). يمكن الاطّلاع على أسعار خدمة Google Cloud Spanner هنا.

إنّ مستخدمي Google Cloud Platform الجدد مؤهّلون للاستفادة من فترة تجريبية مجانية بقيمة 300 دولار أمريكي، ما يجعل هذا الدرس التطبيقي حول الترميز بدون أي تكلفة.

إعداد Google Cloud Shell

يمكن إدارة Google Cloud وSpanner عن بُعد من الكمبيوتر المحمول، ولكن في هذا الدرس التطبيقي حول الترميز، سنستخدم Google Cloud Shell، وهي بيئة سطر أوامر يتم تشغيلها في السحابة الإلكترونية.

هذا الجهاز الافتراضي المستند إلى نظام دبيان محمل بكل أدوات التطوير التي ستحتاج إليها. وتوفّر هذه الشبكة دليلاً رئيسيًا دائمًا بسعة 5 غيغابايت ويتم تشغيله في Google Cloud، ما يحسّن بشكل كبير من أداء الشبكة والمصادقة. وهذا يعني أنّ كل ما ستحتاجه في هذا الدرس التطبيقي حول الترميز هو متصفّح (نعم، يعمل على جهاز Chromebook).

  1. لتفعيل Cloud Shell من Cloud Console، ما عليك سوى النقر على تفعيل Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (من المفترَض أن تستغرق عملية الإعداد والاتصال بالبيئة بضع دقائق فقط).

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSrDc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvIEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

لقطة شاشة يوم 14-06-2017 في الساعة 10.13.43 مساءً.png

بعد الربط بتطبيق Cloud Shell، من المفترض أن يظهر لك أنّه قد تمت مصادقتك وأنّ المشروع سبق أن تم ضبطه على PROJECT_ID.

gcloud auth list

مخرجات الأمر

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

مخرجات الأمر

[core]
project = <PROJECT_ID>

إذا لم يتم ضبط المشروع لسبب ما، ما عليك سوى إصدار الأمر التالي:

gcloud config set project <PROJECT_ID>

هل تبحث عن PROJECT_ID؟ تحقَّق من المعرّف الذي استخدمته في خطوات الإعداد أو ابحث عنه في لوحة بيانات Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

تضبط Cloud Shell أيضًا بعض متغيرات البيئة تلقائيًا، وهو ما قد يكون مفيدًا عند تشغيل الأوامر المستقبلية.

echo $GOOGLE_CLOUD_PROJECT

مخرجات الأمر

<PROJECT_ID>
  1. أخيرًا، قم بتعيين تهيئة المنطقة الافتراضية والمشروع.
gcloud config set compute/zone us-central1-f

يمكنك اختيار مجموعة متنوعة من المناطق المختلفة. لمزيد من المعلومات، راجع المناطق المناطق:

ملخّص

في هذه الخطوة، يتم إعداد البيئة.

التالي

بعد ذلك، سيتم إعداد مثيل Cloud Spanner.

3- إعداد مثيل Cloud Spanner

في هذه الخطوة، نُعِدّ مثيل Cloud Spanner لهذا الدرس التطبيقي حول الترميز. ابحث عن إدخال Spanner 1a6580bd3d3e6783.pngفي قائمة همبرغر أعلى اليمين 3129589f7bc9e5ce.png أو ابحث عن Spanner عن طريق الضغط على "/" واكتب "Spanner"

36e52f8df8e13b99.png

بعد ذلك، انقر على 95269e75bc8c3e4d.png واملأ النموذج عن طريق إدخال اسم المثيل cloudspanner-leaderboard للمثيل، واختيار الإعدادات (اختيار مثيل إقليمي)، وضبط عدد العُقد. سنحتاج إلى عقدة واحدة فقط في هذا الدرس التطبيقي حول الترميز. بالنسبة إلى مثيلات الإنتاج وللتأهل لاتفاقية مستوى الخدمة في Cloud Spanner، ستحتاج إلى تشغيل 3 عُقد أو أكثر في مثيل Cloud Spanner.

أخيرًا وليس آخرًا، انقر على "إنشاء" وستكون لديك مثيل Cloud Spanner تحت تصرفك خلال ثوانٍ.

dceb68e9ed3801e8.png

في الخطوة التالية، سنستخدم مكتبة عميل C# لإنشاء قاعدة بيانات ومخطط في المثيل الجديد.

4. إنشاء قاعدة بيانات ومخطط

في هذه الخطوة، سننشئ نموذجًا لقاعدة البيانات والمخطط.

لنستخدم مكتبة عميل C# لإنشاء جدولين؛ جدول "اللاعبين" للحصول على معلومات عن اللاعب وجدول النتائج لتخزين نتائج اللاعبين ولإجراء ذلك، سننتقل إلى خطوات إنشاء تطبيق وحدة تحكُّم C# في Cloud Shell.

استنسِخ أولاً نموذج الرمز البرمجي لهذا الدرس التطبيقي من GitHub من خلال كتابة الأمر التالي في Cloud Shell:

git clone https://github.com/GoogleCloudPlatform/dotnet-docs-samples.git

ثم غير الدليل إلى "التطبيقات" الدليل الذي ستنشئ فيه التطبيق.

cd dotnet-docs-samples/applications/

تتوفّر كلّ الرموز المطلوبة لهذا الدرس التطبيقي حول الترميز في دليل dotnet-docs-samples/applications/leaderboard الحالي كتطبيق C# قابل للتشغيل واسمه Leaderboard، لاستخدامه كمرجع أثناء تقدّمك في الدرس التطبيقي حول الترميز. سننشئ دليلاً جديدًا وننشئ نسخة من تطبيق "ليدربورد" على مراحل.

إنشاء دليل جديد باسم "درس تطبيقي حول الترميز" للتطبيق وتغيير الدليل إليه باستخدام الأمر التالي:

mkdir codelab && cd $_

إنشاء تطبيق وحدة تحكم .NET C# جديد باسم "Leaderboard" باستخدام الأمر التالي:

dotnet new console -n Leaderboard

ينشئ هذا الأمر تطبيق وحدة تحكم بسيطًا يتكون من ملفين أساسيين، ملف المشروع Leaderboard.csproj وملف البرنامج Program.cs.

لنقم بإجرائها. تغيير الدليل إلى دليل "ليدربورد" الذي تم إنشاؤه حديثًا حيث يتوفّر التطبيق:

cd Leaderboard

ثم أدخل الأمر التالي لتشغيله.

dotnet run

من المفترض أن يظهر لك ناتج التطبيق "Hello World!".

يمكننا الآن تحديث تطبيق وحدة التحكم من خلال تعديل Program.cs لاستخدام مكتبة عميل C# Spanner لإنشاء لوحة صدارة تتألف من مشغِّلي جدولي ونتائج. ويمكنك إجراء ذلك مباشرةً في "محرِّر Cloud Shell" باتّباع الخطوات التالية:

افتح محرِّر Cloud Shell، من خلال النقر على الرمز الموضح أدناه:

73cf70e05f653ca.png

بعد ذلك، افتح ملف Program.cs في محرِّر Cloud Shell واستبدِل الرمز الحالي للملف بالرمز المطلوب لإنشاء قاعدة البيانات leaderboard والجدولَين Players وScores من خلال لصق رمز تطبيق C# التالي في ملف Program.cs:

using System;
using System.Threading.Tasks;
using Google.Cloud.Spanner.Data;
using CommandLine;

namespace GoogleCloudSamples.Leaderboard
{
    [Verb("create", HelpText = "Create a sample Cloud Spanner database "
        + "along with sample 'Players' and 'Scores' tables in your project.")]
    class CreateOptions
    {
        [Value(0, HelpText = "The project ID of the project to use "
            + "when creating Cloud Spanner resources.", Required = true)]
        public string projectId { get; set; }
        [Value(1, HelpText = "The ID of the instance where the sample database "
            + "will be created.", Required = true)]
        public string instanceId { get; set; }
        [Value(2, HelpText = "The ID of the sample database to create.",
            Required = true)]
        public string databaseId { get; set; }
    }

    public class Program
    {
        enum ExitCode : int
        {
            Success = 0,
            InvalidParameter = 1,
        }

        public static object Create(string projectId,
            string instanceId, string databaseId)
        {
            var response =
                CreateAsync(projectId, instanceId, databaseId);
            Console.WriteLine("Waiting for operation to complete...");
            response.Wait();
            Console.WriteLine($"Operation status: {response.Status}");
            Console.WriteLine($"Created sample database {databaseId} on "
                + $"instance {instanceId}");
            return ExitCode.Success;
        }

        public static async Task CreateAsync(
            string projectId, string instanceId, string databaseId)
        {
            // Initialize request connection string for database creation.
            string connectionString =
                $"Data Source=projects/{projectId}/instances/{instanceId}";
            using (var connection = new SpannerConnection(connectionString))
            {
                string createStatement = $"CREATE DATABASE `{databaseId}`";
                string[] createTableStatements = new string[] {
                  // Define create table statement for Players table.
                  @"CREATE TABLE Players(
                    PlayerId INT64 NOT NULL,
                    PlayerName STRING(2048) NOT NULL
                  ) PRIMARY KEY(PlayerId)",
                  // Define create table statement for Scores table.
                  @"CREATE TABLE Scores(
                    PlayerId INT64 NOT NULL,
                    Score INT64 NOT NULL,
                    Timestamp TIMESTAMP NOT NULL OPTIONS(allow_commit_timestamp=true)
                  ) PRIMARY KEY(PlayerId, Timestamp),
                      INTERLEAVE IN PARENT Players ON DELETE NO ACTION" };
                // Make the request.
                var cmd = connection.CreateDdlCommand(
                    createStatement, createTableStatements);
                try
                {
                    await cmd.ExecuteNonQueryAsync();
                }
                catch (SpannerException e) when
                    (e.ErrorCode == ErrorCode.AlreadyExists)
                {
                    // OK.
                }
            }
        }

        public static int Main(string[] args)
        {
            var verbMap = new VerbMap<object>();
            verbMap
                .Add((CreateOptions opts) => Create(
                    opts.projectId, opts.instanceId, opts.databaseId))
                .NotParsedFunc = (err) => 1;
            return (int)verbMap.Run(args);
        }
    }
}

لتقديم صورة أوضح عن رمز البرنامج، إليك رسم تخطيطي للبرنامج مع مكوناته الرئيسية بعنوان:

b70b1b988ea3ac8a.png

يمكنك استخدام ملف Program.cs في دليل dotnet-docs-samples/applications/leaderboard/step4 للاطّلاع على مثال حول الشكل الذي يجب أن يظهر به ملف Program.cs بعد إضافة الرمز لتفعيل الأمر create.

بعد ذلك، يمكنك استخدام محرِّر Cloud Shell لفتح ملف Leaderboard.csproj الخاص بالبرنامج وتعديله ليبدو كالرمز التالي. تأكَّد من حفظ جميع التغييرات باستخدام الزر "ملف". قائمة "محرِّر Cloud Shell"

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Google.Cloud.Spanner.Data" Version="3.3.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\..\commandlineutil\Lib\CommandLineUtil.csproj" />
  </ItemGroup>

</Project>

أضاف هذا التغيير إشارة إلى حزمة C# Spanner Nuget Google.Cloud.Spanner.Data التي نحتاج إليها للتفاعل مع واجهة برمجة تطبيقات Cloud Spanner. يضيف هذا التغيير أيضًا إشارة إلى مشروع CommandLineUtil الذي هو جزء من مستودع جيت هب لعينات مستند نقطي، ويوفر طريقة مفيدة لخريطة الفعل إضافة إلى البرنامج المفتوح المصدر CommandLineParser وهي عبارة عن مكتبة مفيدة للتعامل مع إدخال سطر الأوامر لتطبيقات وحدة التحكم.

يمكنك استخدام ملف Leaderboard.csproj في دليل dotnet-docs-samples/applications/leaderboard/step4 للاطّلاع على مثال حول الشكل الذي يجب أن يظهر به ملف Leaderboard.csproj بعد إضافة الرمز لتفعيل الأمر create.

أنت الآن جاهز لتشغيل عينتك المعدّلة. اكتب ما يلي لعرض الاستجابة التلقائية لتطبيقك المحدَّث:

dotnet run

من المفترض أن تظهر لك نتيجة على النحو التالي:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  No verb selected.

  create     Create a sample Cloud Spanner database along with sample 'Players' and 'Scores' tables in your project.

  help       Display more information on a specific command.

  version    Display version information.

يتضح لنا من هذا الرد أن هذا هو تطبيق Leaderboard الذي يمكن تشغيله باستخدام أحد الأوامر الثلاثة المحتملة: create وhelp وversion.

لنجرب الأمر create لإنشاء قاعدة بيانات وجداول بيانات Spanner. شغِّل الأمر بدون وسيطات لعرض وسيطات الأمر المتوقعة.

dotnet run create

يُفترض أن يظهر لك رد مثل ما يلي:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  A required value not bound to option name is missing.

  --help          Display this help screen.

  --version       Display version information.

  value pos. 0    Required. The project ID of the project to use when creating Cloud Spanner resources.

  value pos. 1    Required. The ID of the instance where the sample database will be created.

  value pos. 2    Required. The ID of the sample database to create.

يتضح لنا هنا أنّ الوسيطات المتوقّعة في الأمر create هي "رقم تعريف المشروع" و"رقم تعريف المثيل" و"رقم تعريف قاعدة البيانات".

قم الآن بتشغيل الأمر التالي. تأكَّد من استبدال PROJECT_ID برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.

dotnet run create PROJECT_ID cloudspanner-leaderboard leaderboard

بعد بضع ثوانٍ، من المفترض أن يظهر لك رد كالتالي:

Waiting for operation to complete...
Operation status: RanToCompletion
Created sample database leaderboard on instance cloudspanner-leaderboard

في قسم Cloud Spanner من Cloud Console، من المفترض أن تظهر لك قاعدة البيانات والجداول الجديدة في القائمة الجانبية اليمنى.

ba9008bb84cb90b0.png

في الخطوة التالية سنحدث تطبيقنا لتحميل بعض البيانات إلى قاعدة بياناتك الجديدة.

5- تحميل البيانات

لدينا الآن قاعدة بيانات تسمى leaderboard تحتوي على جدولين؛ Players وScores لنستخدم الآن مكتبة عملاء C# لملء جدول Players باللاعبين وجدول Scores بنتائج عشوائية لكل لاعب.

افتح محرِّر Cloud Shell، من خلال النقر على الرمز الموضح أدناه:

4d17840699d8e7ce.png

بعد ذلك، عدِّل ملف Program.cs في "محرِّر Cloud Shell" لإضافة الأمر insert الذي يمكن استخدامه لإدراج 100 لاعب في جدول Players أو يمكن استخدامه لإدراج 4 نتائج عشوائية في جدول Scores لكل لاعب في جدول Players.

أضِف أولاً كتلة أوامر insert جديدة في Verbmap. في أعلى "البرنامج" أسفل مجموعة أوامر create الحالية:

[Verb("insert", HelpText = "Insert sample 'players' records or 'scores' records "
        + "into the database.")]
    class InsertOptions
    {
        [Value(0, HelpText = "The project ID of the project to use "
            + "when managing Cloud Spanner resources.", Required = true)]
        public string projectId { get; set; }
        [Value(1, HelpText = "The ID of the instance where the sample database resides.",
            Required = true)]
        public string instanceId { get; set; }
        [Value(2, HelpText = "The ID of the database where the sample database resides.",
            Required = true)]
        public string databaseId { get; set; }
        [Value(3, HelpText = "The type of insert to perform, 'players' or 'scores'.",
            Required = true)]
        public string insertType { get; set; }
    }

أضِف بعد ذلك طرق Insert وInsertPlayersAsync وInsertScoresAsync التالية أسفل طريقة CreateAsync الحالية:

        public static object Insert(string projectId,
            string instanceId, string databaseId, string insertType)
        {
            if (insertType.ToLower() == "players")
            {
                var responseTask =
                    InsertPlayersAsync(projectId, instanceId, databaseId);
                Console.WriteLine("Waiting for insert players operation to complete...");
                responseTask.Wait();
                Console.WriteLine($"Operation status: {responseTask.Status}");
            }
            else if (insertType.ToLower() == "scores")
            {
                var responseTask =
                    InsertScoresAsync(projectId, instanceId, databaseId);
                Console.WriteLine("Waiting for insert scores operation to complete...");
                responseTask.Wait();
                Console.WriteLine($"Operation status: {responseTask.Status}");
            }
            else
            {
                Console.WriteLine("Invalid value for 'type of insert'. "
                    + "Specify 'players' or 'scores'.");
                return ExitCode.InvalidParameter;
            }
            Console.WriteLine($"Inserted {insertType} into sample database "
                + $"{databaseId} on instance {instanceId}");
            return ExitCode.Success;
        }

       public static async Task InsertPlayersAsync(string projectId,
            string instanceId, string databaseId)
        {
            string connectionString =
                $"Data Source=projects/{projectId}/instances/{instanceId}"
                + $"/databases/{databaseId}";

            long numberOfPlayers = 0;
            using (var connection = new SpannerConnection(connectionString))
            {
                await connection.OpenAsync();
                await connection.RunWithRetriableTransactionAsync(async (transaction) =>
                {
                    // Execute a SQL statement to get current number of records
                    // in the Players table to use as an incrementing value 
                    // for each PlayerName to be inserted.
                    var cmd = connection.CreateSelectCommand(
                        @"SELECT Count(PlayerId) as PlayerCount FROM Players");
                    numberOfPlayers = await cmd.ExecuteScalarAsync<long>();
                    // Insert 100 player records into the Players table.
                    SpannerBatchCommand cmdBatch = connection.CreateBatchDmlCommand();
                    for (int i = 0; i < 100; i++)
                    {
                        numberOfPlayers++;
                        SpannerCommand cmdInsert = connection.CreateDmlCommand(
                            "INSERT INTO Players "
                            + "(PlayerId, PlayerName) "
                            + "VALUES (@PlayerId, @PlayerName)",
                                new SpannerParameterCollection {
                                    {"PlayerId", SpannerDbType.Int64},
                                    {"PlayerName", SpannerDbType.String}});
                        cmdInsert.Parameters["PlayerId"].Value =
                            Math.Abs(Guid.NewGuid().GetHashCode());
                        cmdInsert.Parameters["PlayerName"].Value =
                            $"Player {numberOfPlayers}";
                        cmdBatch.Add(cmdInsert);
                    }
                    await cmdBatch.ExecuteNonQueryAsync();
                });
            }
            Console.WriteLine("Done inserting player records...");
        }

        public static async Task InsertScoresAsync(
            string projectId, string instanceId, string databaseId)
        {
            string connectionString =
            $"Data Source=projects/{projectId}/instances/{instanceId}"
            + $"/databases/{databaseId}";

            // Insert 4 score records into the Scores table for each player
            // in the Players table.
            using (var connection = new SpannerConnection(connectionString))
            {
                await connection.OpenAsync();
                await connection.RunWithRetriableTransactionAsync(async (transaction) =>
                {
                    Random r = new Random();
                    bool playerRecordsFound = false;
                    SpannerBatchCommand cmdBatch =
                                connection.CreateBatchDmlCommand();
                    var cmdLookup =
                    connection.CreateSelectCommand("SELECT * FROM Players");
                    using (var reader = await cmdLookup.ExecuteReaderAsync())
                    {
                        while (await reader.ReadAsync())
                        {
                            playerRecordsFound = true;
                            for (int i = 0; i < 4; i++)
                            {
                                DateTime randomTimestamp = DateTime.Now
                                        .AddYears(r.Next(-2, 1))
                                        .AddMonths(r.Next(-12, 1))
                                        .AddDays(r.Next(-28, 0))
                                        .AddHours(r.Next(-24, 0))
                                        .AddSeconds(r.Next(-60, 0))
                                        .AddMilliseconds(r.Next(-100000, 0));
                                SpannerCommand cmdInsert =
                                connection.CreateDmlCommand(
                                    "INSERT INTO Scores "
                                    + "(PlayerId, Score, Timestamp) "
                                    + "VALUES (@PlayerId, @Score, @Timestamp)",
                                    new SpannerParameterCollection {
                                        {"PlayerId", SpannerDbType.Int64},
                                        {"Score", SpannerDbType.Int64},
                                        {"Timestamp",
                                            SpannerDbType.Timestamp}});
                                cmdInsert.Parameters["PlayerId"].Value =
                                    reader.GetFieldValue<int>("PlayerId");
                                cmdInsert.Parameters["Score"].Value =
                                    r.Next(1000, 1000001);
                                cmdInsert.Parameters["Timestamp"].Value =
                                    randomTimestamp.ToString("o");
                                cmdBatch.Add(cmdInsert);
                            }
                        }
                        if (!playerRecordsFound)
                        {
                            Console.WriteLine("Parameter 'scores' is invalid "
                            + "since no player records currently exist. First "
                            + "insert players then insert scores.");
                            Environment.Exit((int)ExitCode.InvalidParameter);
                        }
                        else
                        {
                            await cmdBatch.ExecuteNonQueryAsync();
                            Console.WriteLine(
                                "Done inserting score records..."
                            );
                        }
                    }
                });
            }
        }

بعد ذلك، لتفعيل الأمر insert، أضِف الرمز التالي إلى "الرئيسي" في برنامجك. :

                .Add((InsertOptions opts) => Insert(
                    opts.projectId, opts.instanceId, opts.databaseId, opts.insertType))

يمكنك استخدام ملف Program.cs في دليل dotnet-docs-samples/applications/leaderboard/step5 للاطّلاع على مثال حول الشكل الذي يجب أن يظهر به ملف Program.cs بعد إضافة الرمز لتفعيل الأمر insert.

بعد ذلك، سنشغِّل البرنامج للتأكُّد من تضمين الأمر insert الجديد في قائمة الأوامر المحتملة في البرنامج. شغِّل الأمر التالي:

dotnet run

من المفترض أن يظهر لك الأمر insert الآن ضمن الإخراج التلقائي للبرنامج:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  No verb selected.

  create     Create a sample Cloud Spanner database along with sample 'Players' and 'Scores' tables in your project.

  insert     Insert sample 'players' records or 'scores' records into the database.

  help       Display more information on a specific command.

  version    Display version information.

لنقم الآن بتشغيل الأمر insert لمعرفة وسيطات الإدخال. أدخِل الأمر التالي.

dotnet run insert

يُفترض أن يعرض هذا الرد التالي:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  A required value not bound to option name is missing.

  --help          Display this help screen.

  --version       Display version information.

  value pos. 0    Required. The project ID of the project to use when managing Cloud Spanner resources.

  value pos. 1    Required. The ID of the instance where the sample database resides.

  value pos. 2    Required. The ID of the database where the sample database resides.

  value pos. 3    Required. The type of insert to perform, 'players' or 'scores'.

يمكنك أن ترى من الرد أنه بالإضافة إلى رقم تعريف المشروع ومعرّف المثيل ومعرّف قاعدة البيانات، هناك وسيطة أخرى value pos. 3 متوقعة وهي "نوع الإدراج" تنفيذها. يمكن أن تحتوي هذه الوسيطة على قيمة "players" أو "النتائج".

لنقم الآن بتشغيل الأمر insert باستخدام نفس قيم الوسيطة التي استخدمناها عند استدعينا الأمر create، مع إضافة "players" باعتباره "نوع الإدخال" الإضافي الوسيطة. تأكَّد من استبدال PROJECT_ID برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.

dotnet run insert PROJECT_ID cloudspanner-leaderboard leaderboard players

بعد بضع ثوانٍ، من المفترض أن يظهر لك رد كالتالي:

Waiting for insert players operation to complete...
Done inserting player records...
Operation status: RanToCompletion
Inserted players into sample database leaderboard on instance cloudspanner-leaderboard

لنستخدم الآن مكتبة عملاء C# لتعبئة جدول Scores بأربع نتائج عشوائية بالإضافة إلى طوابع زمنية لكل لاعب في جدول Players.

تم تعريف عمود Timestamp في جدول Scores على أنّه "طابع زمني للالتزام". عبر عبارة SQL التالية التي تم تنفيذها عند تشغيل الأمر create سابقًا:

CREATE TABLE Scores(
  PlayerId INT64 NOT NULL,
  Score INT64 NOT NULL,
  Timestamp TIMESTAMP NOT NULL OPTIONS(allow_commit_timestamp=true)
) PRIMARY KEY(PlayerId, Timestamp),
    INTERLEAVE IN PARENT Players ON DELETE NO ACTION

لاحِظ السمة OPTIONS(allow_commit_timestamp=true). يجعل هذا الإجراء Timestamp "طابعًا زمنيًا للالتزام". وتمكين ملؤه تلقائيًا بالطابع الزمني الدقيق للمعاملة لعمليات INSERT وUPDATE في صف جدول معين.

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

لنقم الآن بتشغيل الأمر insert باستخدام نفس قيم الوسيطة التي استخدمناها عندما استدعينا الأمر create بإضافة "scores" باعتباره "نوع الإدخال" الإضافي الوسيطة. تأكَّد من استبدال PROJECT_ID برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.

dotnet run insert PROJECT_ID cloudspanner-leaderboard leaderboard scores

بعد بضع ثوانٍ، من المفترض أن يظهر لك رد كالتالي:

Waiting for insert players operation to complete...
Done inserting player records...
Operation status: RanToCompletion
Inserted players into sample database leaderboard on instance cloudspanner-leaderboard

جارٍ تشغيل insert باستخدام "نوع الإدراج" المحددون على أن scores يستدعي الطريقة InsertScoresAsync التي تستخدم مقتطفات الرموز التالية لإدراج طابع زمني تم إنشاؤه عشوائيًا مع تاريخ ووقت يحدث في الماضي:

DateTime randomTimestamp = DateTime.Now
    .AddYears(r.Next(-2, 1))
    .AddMonths(r.Next(-12, 1))
    .AddDays(r.Next(-28, 0))
    .AddHours(r.Next(-24, 0))
    .AddSeconds(r.Next(-60, 0))
    .AddMilliseconds(r.Next(-100000, 0));
...
 cmdInsert.Parameters["Timestamp"].Value = randomTimestamp.ToString("o");

لتعبئة عمود Timestamp تلقائيًا بالطابع الزمني عند ضبط الزر "إدراج" بالضبط تحدث المعاملة، يمكنك بدلاً من ذلك إدراج عنصر C# الثابت SpannerParameter.CommitTimestamp كما في مقتطف الرمز التالي:

cmd.Parameters["Timestamp"].Value = SpannerParameter.CommitTimestamp;

الآن وبعد أن أكملنا تحميل البيانات، لنتحقق من القيم التي كتبناها للتو إلى جداولنا الجديدة. اختَر أولاً قاعدة بيانات leaderboard ثم اختَر الجدول Players. انقر على علامة التبويب Data. من المفترض أن يظهر لك أنّ لديك بيانات في العمودَين PlayerId وPlayerName في الجدول.

7bc2c96293c31c49.png

بعد ذلك، لنتحقق من أن جدول "النتائج" يحتوي أيضًا على بيانات من خلال النقر على جدول Scores واختيار علامة التبويب Data. من المفترض أن يظهر لك أنّ لديك بيانات في الأعمدة PlayerId وTimestamp وScore في الجدول.

d8a4ee4f13244c19.png

أحسنت! لنعدّل برنامجنا لإجراء بعض طلبات البحث التي يمكننا استخدامها لإنشاء قائمة الصدارة للألعاب.

6- تنفيذ طلبات بحث في قائمة الصدارة

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

  1. مَن هم اللاعبين ضمن "أفضل عشرة" من جميع الأوقات؟
  2. مَن هم اللاعبين ضمن "أفضل عشرة" من العام؟
  3. مَن هم اللاعبين ضمن "أفضل عشرة" من الشهر؟
  4. مَن هم اللاعبين ضمن "أفضل عشرة" من الأسبوع؟

لنقم بتحديث برنامجنا لتشغيل استعلامات SQL (لغة الاستعلام البنيوية) التي سوف تجيب على هذه الأسئلة.

سنضيف الأمر query الذي يوفر طريقة لتشغيل الاستعلامات للإجابة عن الأسئلة التي ستنتج المعلومات المطلوبة لقائمة الصدارة.

عدِّل ملف Program.cs في "محرِّر Cloud Shell" لتعديل البرنامج من أجل إضافة الأمر query.

أضِف أولاً كتلة أوامر query جديدة في Verbmap. في أعلى "البرنامج" أسفل مجموعة أوامر insert الحالية:

    [Verb("query", HelpText = "Query players with 'Top Ten' scores within a specific timespan "
        + "from sample Cloud Spanner database table.")]
    class QueryOptions
    {
        [Value(0, HelpText = "The project ID of the project to use "
            + "when managing Cloud Spanner resources.", Required = true)]
        public string projectId { get; set; }
        [Value(1, HelpText = "The ID of the instance where the sample data resides.",
            Required = true)]
        public string instanceId { get; set; }
        [Value(2, HelpText = "The ID of the database where the sample data resides.",
            Required = true)]
        public string databaseId { get; set; }
        [Value(3, Default = 0, HelpText = "The timespan in hours that will be used to filter the "
            + "results based on a record's timestamp. The default will return the "
            + "'Top Ten' scores of all time.")]
        public int timespan { get; set; }
    }

أضِف بعد ذلك الطريقتَين Query وQueryAsync التاليتَين أسفل طريقة InsertScoresAsync الحالية:

public static object Query(string projectId,
            string instanceId, string databaseId, int timespan)
        {
            var response = QueryAsync(
                projectId, instanceId, databaseId, timespan);
            response.Wait();
            return ExitCode.Success;
        }        

public static async Task QueryAsync(
            string projectId, string instanceId, string databaseId, int timespan)
        {
            string connectionString =
            $"Data Source=projects/{projectId}/instances/"
            + $"{instanceId}/databases/{databaseId}";
            // Create connection to Cloud Spanner.
            using (var connection = new SpannerConnection(connectionString))
            {
                string sqlCommand;
                if (timespan == 0)
                {
                    // No timespan specified. Query Top Ten scores of all time.
                    sqlCommand =
                        @"SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
                            FROM Players p
                            JOIN Scores s ON p.PlayerId = s.PlayerId
                            ORDER BY s.Score DESC LIMIT 10";
                }
                else
                {
                    // Query Top Ten scores filtered by the timepan specified.
                    sqlCommand =
                        $@"SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
                            FROM Players p
                            JOIN Scores s ON p.PlayerId = s.PlayerId
                            WHERE s.Timestamp >
                            TIMESTAMP_SUB(CURRENT_TIMESTAMP(),
                                INTERVAL {timespan.ToString()} HOUR)
                            ORDER BY s.Score DESC LIMIT 10";
                }
                var cmd = connection.CreateSelectCommand(sqlCommand);
                using (var reader = await cmd.ExecuteReaderAsync())
                {
                    while (await reader.ReadAsync())
                    {
                        Console.WriteLine("PlayerId : "
                          + reader.GetFieldValue<string>("PlayerId")
                          + " PlayerName : "
                          + reader.GetFieldValue<string>("PlayerName")
                          + " Score : "
                          + string.Format("{0:n0}",
                            Int64.Parse(reader.GetFieldValue<string>("Score")))
                          + " Timestamp : "
                          + reader.GetFieldValue<string>("Timestamp").Substring(0, 10));
                    }
                }
            }
        }

بعد ذلك، لتفعيل الأمر query، أضِف الرمز التالي إلى "الرئيسي" في برنامجك. :

                .Add((QueryOptions opts) => Query(
                    opts.projectId, opts.instanceId, opts.databaseId, opts.timespan))

يمكنك استخدام ملف Program.cs في دليل dotnet-docs-samples/applications/leaderboard/step6 للاطّلاع على مثال حول الشكل الذي يجب أن يظهر به ملف Program.cs بعد إضافة الرمز لتفعيل الأمر query.

بعد ذلك، سنشغِّل البرنامج للتأكُّد من تضمين الأمر query الجديد في قائمة الأوامر المحتملة في البرنامج. شغِّل الأمر التالي:

dotnet run

من المفترض أن يظهر لك الأمر query الآن ضمن الإخراج التلقائي للبرنامج كخيار أمر جديد:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  No verb selected.

  create     Create a sample Cloud Spanner database along with sample 'Players' and 'Scores' tables in your project.

  insert     Insert sample 'players' records or 'scores' records into the database.

  query      Query players with 'Top Ten' scores within a specific timespan from sample Cloud Spanner database table.

  help       Display more information on a specific command.

  version    Display version information.

لنقم الآن بتشغيل الأمر query لمعرفة وسيطات الإدخال. أدخِل الأمر التالي:

dotnet run query

سيعرض هذا الرد التالي:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  A required value not bound to option name is missing.

  --help          Display this help screen.

  --version       Display version information.

  value pos. 0    Required. The project ID of the project to use when managing Cloud Spanner resources.

  value pos. 1    Required. The ID of the instance where the sample data resides.

  value pos. 2    Required. The ID of the database where the sample data resides.

  value pos. 3    (Default: 0) The timespan in hours that will be used to filter the results based on a record's timestamp. The default will return the 'Top Ten' scores of all time.

يمكنك أن ترى من الرد أنه بالإضافة إلى رقم تعريف المشروع ورقم تعريف المثيل ورقم تعريف قاعدة البيانات، هناك وسيطة أخرى من المتوقع أن تستخدم value pos. 3. تسمح لنا بتحديد فترة زمنية بعدد الساعات لاستخدامها لفلترة السجلات استنادًا إلى قيمتها في العمود Timestamp للجدول Scores. تتضمّن هذه الوسيطة القيمة التلقائية 0، ما يعني أنّه لن تتم فلترة أي سجلّات حسب الطوابع الزمنية. إذًا، يمكننا استخدام الأمر query بدون "timespan" للحصول على قائمة "أهم عشرة" لاعبين في جميع الأوقات.

لنشغِّل الأمر query بدون تحديد "timespan" باستخدام قيم الوسيطات نفسها التي استخدمناها عند تشغيل الأمر create. تأكَّد من استبدال PROJECT_ID برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard

من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" لاعبين من جميع العصور مثل ما يلي:

PlayerId : 1843159180 PlayerName : Player 87 Score : 998,955 Timestamp : 2016-03-23
PlayerId : 61891198 PlayerName : Player 19 Score : 998,720 Timestamp : 2016-03-26
PlayerId : 340906298 PlayerName : Player 48 Score : 993,302 Timestamp : 2015-08-27
PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 857460496 PlayerName : Player 68 Score : 988,010 Timestamp : 2015-05-25
PlayerId : 1826646419 PlayerName : Player 91 Score : 984,022 Timestamp : 2016-11-26
PlayerId : 1002199735 PlayerName : Player 35 Score : 982,933 Timestamp : 2015-09-26
PlayerId : 2002563755 PlayerName : Player 23 Score : 979,041 Timestamp : 2016-10-25
PlayerId : 1377548191 PlayerName : Player 2 Score : 978,632 Timestamp : 2016-05-02
PlayerId : 1358098565 PlayerName : Player 65 Score : 973,257 Timestamp : 2016-10-30

لنُشغِّل الآن الأمر query مع الوسيطات اللازمة للاستعلام عن "أهم عشرة". أفضل لاعب في العام من خلال تحديد "فترة زمنية" يساوي عدد الساعات في السنة وهو 8760. تأكَّد من استبدال PROJECT_ID برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 8760

من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" أفضل لاعبي العام كما يلي:

PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 228469898 PlayerName : Player 82 Score : 967,177 Timestamp : 2018-01-26
PlayerId : 1131343000 PlayerName : Player 26 Score : 944,725 Timestamp : 2017-05-26
PlayerId : 396780730 PlayerName : Player 41 Score : 929,455 Timestamp : 2017-09-26
PlayerId : 61891198 PlayerName : Player 19 Score : 921,251 Timestamp : 2018-05-01
PlayerId : 634269851 PlayerName : Player 54 Score : 909,379 Timestamp : 2017-07-24
PlayerId : 821111159 PlayerName : Player 55 Score : 908,402 Timestamp : 2017-05-25
PlayerId : 228469898 PlayerName : Player 82 Score : 889,040 Timestamp : 2017-12-26
PlayerId : 1408782275 PlayerName : Player 27 Score : 874,124 Timestamp : 2017-09-24
PlayerId : 1002199735 PlayerName : Player 35 Score : 864,758 Timestamp : 2018-04-24

لنقم الآن بتشغيل الأمر query للاستعلام عن "أعلى عشرة" لاعب الشهر من خلال تحديد "فترة زمنية" يساوي عدد الساعات في الشهر وهو 730. تأكَّد من استبدال PROJECT_ID برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 730

من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" اللاعبين الأفضل في الشهر كما يلي:

PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 61891198 PlayerName : Player 19 Score : 921,251 Timestamp : 2018-05-01
PlayerId : 1002199735 PlayerName : Player 35 Score : 864,758 Timestamp : 2018-04-24
PlayerId : 1228490432 PlayerName : Player 11 Score : 682,033 Timestamp : 2018-04-26
PlayerId : 648239230 PlayerName : Player 92 Score : 653,895 Timestamp : 2018-05-02
PlayerId : 70762849 PlayerName : Player 77 Score : 598,074 Timestamp : 2018-04-22
PlayerId : 1671215342 PlayerName : Player 62 Score : 506,770 Timestamp : 2018-04-28
PlayerId : 1208850523 PlayerName : Player 21 Score : 216,008 Timestamp : 2018-04-30
PlayerId : 1587692674 PlayerName : Player 63 Score : 188,157 Timestamp : 2018-04-25
PlayerId : 992391797 PlayerName : Player 37 Score : 167,175 Timestamp : 2018-04-30

لنقم الآن بتشغيل الأمر query للاستعلام عن "أعلى عشرة" لاعبو الأسبوع من خلال تحديد "فترة زمنية" يساوي عدد الساعات في الأسبوع وهو 168. تأكَّد من استبدال PROJECT_ID برقم تعريف المشروع الذي أنشأته في بداية هذا الدرس التطبيقي حول الترميز.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 168

من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" لاعبي الأسبوع مثل ما يلي:

PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 61891198 PlayerName : Player 19 Score : 921,251 Timestamp : 2018-05-01
PlayerId : 228469898 PlayerName : Player 82 Score : 853,602 Timestamp : 2018-04-28
PlayerId : 1131343000 PlayerName : Player 26 Score : 695,318 Timestamp : 2018-04-30
PlayerId : 1228490432 PlayerName : Player 11 Score : 682,033 Timestamp : 2018-04-26
PlayerId : 1408782275 PlayerName : Player 27 Score : 671,827 Timestamp : 2018-04-27
PlayerId : 648239230 PlayerName : Player 92 Score : 653,895 Timestamp : 2018-05-02
PlayerId : 816861444 PlayerName : Player 83 Score : 622,277 Timestamp : 2018-04-27
PlayerId : 162043954 PlayerName : Player 75 Score : 572,634 Timestamp : 2018-05-02
PlayerId : 1671215342 PlayerName : Player 62 Score : 506,770 Timestamp : 2018-04-28

ممتاز!

والآن بعد إضافة السجلات، سيعمل Spanner على تغيير حجم قاعدة البيانات إلى الحجم الذي تحتاج إليه.

بغض النظر عن حجم قاعدة بياناتك، يمكن أن يستمر تغيير قائمة الصدارة في لعبتك بدقة باستخدام Spanner وتقنية Truetime الخاصة بها.

7. تنظيف

بعد كل متعة اللعب مع Spanner، علينا تنظيف ساحة اللعب وتوفير الموارد والمال الثمينة. لحسن الحظّ، هذه خطوة سهلة. ما عليك سوى الانتقال إلى Play Console وحذف المثيل الذي أنشأناه في خطوة الدرس التطبيقي حول الترميز المسماة "إعداد مثيل Cloud Spanner".

8. تهانينا!

المواضيع التي تناولناها:

  • مثيلات Google Cloud Spanner وقواعد البيانات ومخطط الجدول لقائمة الصدارة
  • كيفية إنشاء تطبيق وحدة تحكم .NET Core C#
  • كيفية إنشاء قاعدة بيانات وجدول Spanner باستخدام مكتبة عملاء C#
  • كيفية تحميل البيانات إلى قاعدة بيانات Spanner باستخدام مكتبة عميل C#
  • كيفية الاستعلام عن "أعلى عشرة" نتائج من بياناتك باستخدام الطوابع الزمنية لتنفيذ Spanner ومكتبة عميل C#

الخطوات التالية:

يُرجى إرسال ملاحظاتك إلينا

  • يُرجى تخصيص بعض الوقت لإكمال الاستطلاع القصير جدًا.