Compartilhar via


Atualização incremental avançada e dados em tempo real com o endpoint XMLA

Modelos semânticos em uma capacidade Premium com o ponto de extremidade XMLA habilitado para operações de leitura/gravação permitem atualizações avançadas, gerenciamento de partições e implantações apenas de metadados através de ferramentas, scripts e suporte a API. Além disso, as operações de atualização por meio do ponto de extremidade XMLA não são limitadas a 48 atualizações por dia e o limite de tempo de atualização agendado não é imposto.

Partitions

As partições de tabela de modelo semântico não são visíveis e não podem ser gerenciadas usando o Power BI Desktop ou o serviço do Power BI. Para modelos em um workspace atribuído a uma capacidade Premium, as partições podem ser gerenciadas através do endpoint XMLA. Você pode usar ferramentas como o SSMS (SQL Server Management Studio) ou o Editor de Tabela de software livre para gerenciar partições por meio de scripts com TMSL (Tabular Model Scripting Language) e programaticamente com o TOM (Modelo de Objeto Tabular).

Quando você publica um modelo pela primeira vez no serviço do Power BI, cada tabela no novo modelo tem uma partição. Para tabelas sem política de atualização incremental, essa partição contém todas as linhas de dados para essa tabela, a menos que os filtros sejam aplicados. Para tabelas com uma política de atualização incremental, essa partição inicial só existe porque o Power BI ainda não aplicou a política. Você configura a partição inicial no Power BI Desktop quando define o filtro de intervalo de data/hora para sua tabela com base nos parâmetros RangeStart e RangeEnd, e em quaisquer outros filtros aplicados no Editor do Power Query. Essa partição inicial contém apenas as linhas de dados que atendem aos critérios de filtro.

Quando você executa a primeira operação de atualização, tabelas sem nenhuma política de atualização incremental atualizam todas as linhas contidas na partição única padrão dessa tabela. Para tabelas com uma política de atualização incremental, as partições de atualização e histórico são criadas automaticamente e as linhas são carregadas nelas de acordo com a data/hora de cada linha. Se a política de atualização incremental incluir a obtenção de dados em tempo real, o Power BI também adicionará uma partição DirectQuery à tabela.

Importante

Quando você usa a atualização incremental com dados em tempo real (modo híbrido), as tabelas relacionadas à tabela híbrida devem usar o modo de armazenamento duplo para evitar penalidades de desempenho. Além disso, o cache visual pode atrasar as atualizações dinâmicas até que os visuais reúnam os dados. Para obter mais informações, consulte Solucionar problemas de atualização incremental e dados em tempo real.

Essa primeira operação de atualização pode levar algum tempo dependendo da quantidade de dados que precisa ser carregada da fonte de dados. A complexidade do modelo também pode ser um fator significativo porque as operações de atualização devem fazer mais processamento e recálculo. Essa operação pode ser inicializada. Para obter mais informações, consulte Prevenir tempos de espera na atualização completa inicial.

As partições são criadas e nomeadas por granularidade de período: anos, trimestres, meses e dias. As partições mais recentes, as partições de atualização, contêm linhas no período de atualização que você especificar na política. As partições históricas contêm linhas por período completo até o período de atualização. Se o tempo real estiver habilitado, uma partição DirectQuery selecionará as alterações de dados que ocorreram após a data de término do período de atualização. A granularidade das partições de atualização e das históricas depende dos períodos de atualização e histórico (repositório) que você escolher ao definir a política.

Por exemplo, se a data de hoje for 2 de fevereiro de 2021 e nossa tabela FactInternetSales na fonte de dados contiver linhas até hoje, e se nossa política especificar a inclusão de mudanças em tempo real, atualizar as linhas no último período de atualização de um dia e armazenar as linhas no período histórico dos últimos três anos. Em seguida, com a primeira operação de atualização, uma partição DirectQuery é criada para alterações no futuro. Uma nova partição de importação é criada para as linhas de hoje e uma partição histórica é criada para as linhas de ontem, correspondente a um período de um dia completo em 1º de fevereiro de 2021. Uma partição histórica é criada para o período de mês inteiro anterior (janeiro de 2021). Uma partição histórica é criada para o período de ano inteiro anterior (2020). E as partições históricas para os períodos de ano inteiro de 2019 e 2018 são criadas. Nenhuma partição de trimestre inteiro é criada porque, em 2 de fevereiro, o primeiro trimestre completo de 2021 ainda não foi concluído.

O diagrama mostra a granularidade de nomenclatura de partição descrita no texto.

Com cada operação de atualização, somente as partições do período de atualização são atualizadas. O filtro de data da partição DirectQuery é atualizado para incluir somente as alterações que ocorrem após o período de atualização atual. Uma nova partição de atualização é criada para novas linhas com uma nova data/hora dentro do período de atualização atualizado. As linhas que já possuem data e hora dentro de partições existentes no período de atualização são atualizadas com novas informações. Linhas com data/hora anteriores ao período de atualização não são mais atualizadas.

À medida que os períodos inteiros se fecham, as partições são mescladas. Por exemplo, se um período de atualização de um dia e um período de armazenamento histórico de três anos for especificado na política, no primeiro dia do mês, as partições diárias do mês anterior serão mescladas em uma partição de mês. No primeiro dia de um novo trimestre, todas as três partições do mês anterior são mescladas em uma partição de trimestre. No primeiro dia de um novo ano, todas as quatro partições do trimestre anterior são mescladas em uma partição de ano.

Um modelo sempre retém partições ao longo de todo o período de armazenamento histórico, além de partições de período completo até o período atual de atualização. No exemplo, três anos completos de dados históricos são mantidos em partições para 2018, 2019, 2020 e também partições para o período do mês 2021Q101, o período de 2021Q10201 e a partição do período de atualização do dia atual. Como o exemplo mantém dados históricos por três anos, a partição de 2018 é mantida até a primeira atualização em 1º de janeiro de 2022.

Com a atualização incremental do Power BI e os dados em tempo real, o serviço gerencia as partições para você com base na política. Embora o serviço possa gerenciar o gerenciamento de partições para você, usando ferramentas através do endpoint XMLA, você pode atualizar seletivamente as partições individualmente, sequencialmente ou em paralelo.

Padrões comuns de atualização de partição

Ao trabalhar com operações de ponto de extremidade XMLA, considere esses padrões comuns para a gestão de operações de atualização.

  • Atualizações pequenas frequentes: execute várias operações de atualização pequenas e direcionadas durante o horário comercial usando comandos de partição XMLA ou a API REST aprimorada para manter os dados recentes atualizados sem processar toda a tabela.
  • Preenchimentos históricos seletivos: Realizar reconstruções de partições históricas maiores ou correções de dados pontuais durante o horário de folga usando TMSL e recompilando períodos históricos específicos sem afetar o comportamento automático da política.
  • Cargas iniciais em etapas: para grandes períodos históricos, divida a atualização inicial em lotes menores processando partições incrementalmente para evitar tempos limite e gerenciar o consumo de recursos.

Esses padrões permitem que você balancee a atualização de dados em tempo real com o desempenho do sistema e restrições de recursos.

Atualizar o gerenciamento com o SQL Server Management Studio

O SSMS (SQL Server Management Studio) pode ser usado para exibir e gerenciar partições criadas pelo aplicativo de políticas de atualização incremental. Usando o SSMS, você pode, por exemplo, atualizar uma partição histórica específica que não está no período de atualização incremental para executar uma atualização datada anterior sem precisar atualizar todos os dados históricos. O SSMS também pode ser usado ao inicializar para carregar dados históricos para modelos grandes adicionando/atualizando incrementalmente partições históricas em lotes.

Captura de tela da janela Partições no SSMS.

Substituir o comportamento de atualização gradativa

Com o SSMS, você também tem mais controle sobre como invocar atualizações usando a linguagem de script de modelo tabular e o modelo de objeto tabular. Por exemplo, no SSMS, no Pesquisador de Objetos, clique com o botão direito do mouse em uma tabela e selecione a opção de menu Tabela de Processo e, em seguida, selecione o botão Script para gerar um comando de atualização TMSL.

Captura de tela do botão Script na caixa de diálogo Tabela de Processo.

Esses parâmetros podem ser usados com o comando de atualização TMSL para substituir o comportamento de atualização incremental padrão:

  • applyRefreshPolicy. Se uma tabela tiver uma política de atualização incremental definida, applyRefreshPolicy determinará se a política é aplicada ou não. Se a política não for aplicada, uma operação completa do processo deixará as definições de partição inalteradas e todas as partições na tabela serão totalmente atualizadas. O valor padrão é verdadeiro.

  • effectiveDate. Se uma política de atualização incremental estiver sendo aplicada, ela precisará saber a data atual para determinar intervalos de janelas móveis para a atualização incremental e os períodos históricos. O effectiveDate parâmetro permite substituir a data atual. Esse parâmetro é útil para testes, demonstrações e cenários de negócios em que os dados são atualizados incrementalmente até uma data no passado ou no futuro, por exemplo, orçamentos no futuro. O valor padrão é a data atual.

{ 
  "refresh": {
    "type": "full",

    "applyRefreshPolicy": true,
    "effectiveDate": "12/31/2013",

    "objects": [
      {
        "database": "IR_AdventureWorks", 
        "table": "FactInternetSales" 
      }
    ]
  }
}

Para saber mais sobre como substituir o comportamento de atualização incremental padrão com o TMSL, consulte o comando Atualizar.

Gerenciamento de políticas com o Editor tabular

Além do SSMS, você pode usar o Editor tabular para criar e modificar políticas de atualização incremental diretamente em modelos semânticos por meio do ponto de extremidade XMLA. Esse método permite que você ajuste as configurações de política, como períodos de atualização, períodos históricos e expressões de origem, sem a necessidade de republicar o modelo do Power BI Desktop. O Editor de Tabelas também pode ser usado para aplicar políticas de atualização a tabelas existentes e gerenciar RangeStart e RangeEnd parâmetros de expressões. Para obter mais informações, consulte Incremental refresh na documentação do Editor Tabular.

Atualizar orquestração e automação

Além de usar SSMS, TMSL e TOM para gerenciar atualizações por meio do ponto de extremidade XMLA. Você também pode orquestrar operações semânticas de atualização de modelo usando a API REST do Power BI. A API de atualização aprimorada fornece mais recursos, incluindo atualização no nível da tabela e no nível da partição, lógica de repetição, cancelamento e gerenciamento de tempo limite personalizado. Essa abordagem é útil para integrar operações de atualização em fluxos de trabalho automatizados e pipelines de CI/CD. Para obter diretrizes detalhadas, consulte Atualização aprimorada com a API REST do Power BI.

Garantindo o desempenho ideal

Com cada operação de atualização, o serviço do Power BI pode enviar consultas de inicialização para a fonte de dados para cada partição de atualização incremental. Você pode melhorar o desempenho de atualização incremental reduzindo o número de consultas de inicialização garantindo a seguinte configuração:

  • A tabela para a qual você configura a atualização incremental deve obter dados de uma única fonte de dados. Se a tabela receber dados de mais de uma fonte de dados, o número de consultas enviadas pelo serviço para cada operação de atualização será multiplicado pelo número de fontes de dados, reduzindo potencialmente o desempenho da atualização. Verifique se a consulta para a tabela de atualização incremental é para uma única fonte de dados.
  • Para soluções com atualização incremental de partições de importação e dados em tempo real com Consulta Direta, todas as partições devem consultar dados de uma única fonte de dados.
  • Se os requisitos de segurança permitirem, defina a configuração de nível de privacidade da fonte de dados como Organizacional ou Pública. Por padrão, o nível de privacidade é Privado. No entanto, esse nível pode impedir que os dados sejam trocados com outras fontes de nuvem. Para definir o nível de privacidade, selecione o menu Mais opções e, em seguida, escolha Configurações>Credenciais> de fonte de dadosEditar credenciais>Configuração de nível de privacidade para essa fonte de dados. Se o nível de privacidade estiver definido no modelo do Power BI Desktop antes da publicação no serviço, ele não será transferido para o serviço quando você publicar. Você ainda deve defini-lo nas configurações de modelo semântico no serviço. Para saber mais, confira os níveis de privacidade.
  • Se estiver usando um Gateway de Dados Local, verifique se você está usando a versão 3000.77.3 ou superior.

Impedir tempos limite na atualização completa inicial

Depois de publicar no serviço do Power BI, a operação inicial de atualização completa do modelo cria partições para a tabela de atualização incremental, carrega e processa dados históricos durante todo o período definido na política de atualização incremental. Para alguns modelos que carregam e processam grandes quantidades de dados, o tempo que a operação de atualização inicial leva pode exceder o limite de tempo de atualização imposto pelo serviço ou um limite de tempo de consulta imposto pela fonte de dados.

Inicializar a operação de atualização inicial permite que o serviço crie objetos de partição para a tabela de atualização incremental, mas não carregue e processe dados históricos em nenhuma das partições. O SSMS é usado para processar seletivamente partições. Dependendo da quantidade de dados a serem carregados para cada partição, você pode processar cada partição sequencialmente ou em lotes pequenos. Esse método reduz o potencial de que uma ou mais dessas partições possam causar um timeout. Os métodos a seguir funcionam para qualquer fonte de dados.

Aplicar Política de Atualização

A ferramenta de software livre Tabular Editor 2 fornece uma maneira fácil de preparar uma primeira operação de atualização. Depois de publicar um modelo com uma política de atualização incremental definida para ele do Power BI Desktop para o serviço, conecte-se ao modelo usando o endpoint XMLA no modo de leitura e gravação. Execute Aplicar Política de Atualização na tabela de atualização incremental. Com apenas a política aplicada, as partições são criadas, mas nenhum dado é carregado neles. Em seguida, conecte-se com o SSMS para atualizar as partições sequencialmente ou em lotes para carregar e processar os dados. Para obter mais informações, consulte a atualização incremental na documentação do Tabular Editor.

A captura de tela mostra o Editor tabular com Aplicar Política de Atualização selecionada.

Filtro do Power Query para partições vazias

Antes de publicar o modelo no serviço, no Editor do Power Query, adicione outro filtro à coluna ProductKey que remove qualquer valor diferente de 0, efetivamente filtrando todos os dados da tabela FactInternetSales.

A captura de tela mostra o Editor do Power Query com código que filtra a chave do produto.

Depois de selecionar Fechar &Aplicar no Editor do Power Query, definir a política de atualização incremental e salvar o modelo, o modelo será publicado no serviço. No serviço, a operação de atualização inicial é executada no modelo. As partições para a tabela FactInternetSales são criadas de acordo com a política, mas nenhum dado é carregado e processado porque todos os dados são filtrados.

Depois que a operação de atualização inicial for concluída, novamente no Editor do Power Query, o outro filtro na ProductKey coluna será removido. Depois de selecionar Fechar &Aplicar no Editor do Power Query e salvar o modelo, o modelo não será publicado novamente. Se o modelo for publicado novamente, ele substituirá as configurações de política de atualização incremental e forçará uma atualização completa no modelo quando uma operação de atualização subsequente for executada a partir do serviço. Em vez disso, execute uma implantação somente de metadados usando o Kit de Ferramentas do ALM (Gerenciamento de Ciclo de Vida do Aplicativo) que remove o filtro na ProductKey coluna do modelo. O SSMS pode ser usado para processar seletivamente partições. Quando todas as partições são totalmente processadas a partir do SSMS, o que deve incluir um recálculo do processo em todas as partições, as operações subsequentes de atualização no modelo do serviço atualizam apenas as partições de atualização incremental.

Dica

Confira vídeos, blogs e muito mais fornecidos pela comunidade de especialistas em BI do Power BI.

Para saber mais sobre como processar tabelas e partições do SSMS, consulte o banco de dados, a tabela ou as partições do Processo (Analysis Services). Para saber mais sobre modelos de processamento, tabelas e partições usando TMSL, consulte o comando Atualizar (TMSL).

Consultas personalizadas para detectar alterações de dados

TMSL e TOM podem ser usados para sobrepor o comportamento das alterações de dados detectadas. Esse método pode ser usado para evitar a persistência da coluna de última atualização no cache na memória. Ele também pode habilitar cenários em que uma tabela de configuração ou instrução é preparada por meio de processos de ETL (extração, transformação e carregamento). Permitindo que você sinalize apenas as partições que precisam ser atualizadas. Esse método pode criar um processo de atualização incremental mais eficiente, em que somente os períodos necessários são atualizados, independentemente de há quanto tempo as atualizações de dados ocorreram.

O pollingExpression destina-se a ser uma expressão M leve ou o nome de outra consulta M. Ele deve retornar um valor escalar e é executado para cada partição. Se o valor retornado for diferente do que foi a última vez que ocorreu uma atualização incremental, a partição será sinalizada para processamento completo.

O exemplo a seguir abrange todos os 120 meses do período histórico para alterações retroativas. Especificar 120 meses em vez de 10 anos significa que a compactação de dados pode não ser tão eficiente. No entanto, evita ter que atualizar um ano histórico inteiro, o que seria mais caro quando um mês bastaria para uma alteração retroativa.

"refreshPolicy": {
    "policyType": "basic",
    "rollingWindowGranularity": "month",
    "rollingWindowPeriods": 120,
    "incrementalGranularity": "month",
    "incrementalPeriods": 120,
    "pollingExpression": "<M expression or name of custom polling query>",
    "sourceExpression": [
    "let ..."
    ]
}

Dica

Confira vídeos, blogs e muito mais fornecidos pela comunidade de especialistas em BI do Power BI.

Implantação somente de metadados

Quando você publica uma nova versão de um arquivo .pbix do Power BI Desktop em um workspace. Você verá o prompt a seguir para substituir o modelo existente, se já existir um modelo com o mesmo nome.

A captura de tela mostra a caixa de diálogo Substituir modelo.

Em alguns casos, talvez você não queira substituir o modelo, especialmente com a atualização incremental. O modelo no Power BI Desktop pode ser consideravelmente menor do que o do serviço do Power BI. Se o modelo no serviço do Power BI tiver uma política de atualização incremental aplicada, você poderá perder vários anos de dados históricos se o modelo for substituído. Atualizar todos os dados históricos pode levar horas e resultar em tempo de inatividade do sistema para os usuários.

Em vez disso, é melhor executar uma implantação somente de metadados, que permite a implantação de novos objetos sem perder os dados históricos. Por exemplo, se você adicionar apenas algumas medidas, poderá implantar apenas as novas medidas sem precisar atualizar os dados, economizando tempo.

Nos workspaces atribuídos a uma capacidade Premium configurada para leitura/gravação do ponto de extremidade XMLA, as ferramentas compatíveis habilitam a implantação apenas de metadados. Por exemplo, o ALM Toolkit é uma ferramenta de diferenciação de esquema para modelos do Power BI e pode ser usado para executar apenas a implantação de metadados.

Baixe e instale a versão mais recente do ALM Toolkit do repositório Git do Analysis Services. As diretrizes passo a passo sobre como usar o ALM Toolkit não estão incluídas na documentação da Microsoft. Os links da documentação do ALM Toolkit e informações sobre suporte estão disponíveis na aba de Ajuda. Para executar uma implantação somente de metadados, execute uma comparação e selecione a instância do Power BI Desktop em execução como a origem e o modelo existente no serviço do Power BI como destino. Considere as diferenças exibidas e ignore a atualização da tabela com partições de atualização incremental ou use a caixa de diálogo Opções para reter partições para atualizações de tabela. Valide a seleção para garantir a integridade do modelo de destino e, em seguida, atualize.

A captura de tela mostra a janela do Kit de Ferramentas do ALM.

Adicionando uma política de atualização incremental e dados em tempo real programaticamente

Você também pode usar o TMSL e o TOM para incluir uma política de atualização incremental a um modelo existente através do endpoint XMLA.

Observação

Para evitar problemas de compatibilidade, use a versão mais recente das bibliotecas de clientes do Analysis Services. Por exemplo, para trabalhar com políticas híbridas, a versão deve ser 19.27.1.8 ou superior.

O processo inclui as seguintes etapas:

  1. Verifique se o modelo de destino tem o nível mínimo de compatibilidade necessário. No SSMS, clique com o botão direito do mouse no [nome do modelo]>Nível de Compatibilidade de>. Para aumentar o nível de compatibilidade, use um script TMSL createOrReplace ou verifique o código de exemplo TOM a seguir para obter um exemplo.

    a. Import policy - 1550
    b. Hybrid policy - 1565
    
  2. Adicione os parâmetros RangeStart e RangeEnd às expressões de modelo. Se necessário, adicione também uma função para converter valores de data/hora em chaves de data.

  3. Defina um RefreshPolicy objeto com o arquivamento desejado (janela deslizante) e períodos de atualização incremental, bem como uma expressão de origem que filtra a tabela de destino com base nos parâmetros RangeStart e RangeEnd. Defina o modo de política de atualização como Importar ou Híbrido , dependendo dos requisitos de dados em tempo real. O Híbrido faz com que o Power BI adicione uma partição DirectQuery à tabela para buscar as alterações mais recentes da fonte de dados que ocorreram após a hora da última atualização.

  4. Adicione a política de atualização à tabela e execute uma atualização completa para que o Power BI particione a tabela de acordo com seus requisitos.

O exemplo de código a seguir demonstra como executar as etapas anteriores usando TOM. Se você quiser usar esse exemplo como está, deverá ter uma cópia para o banco de dados AdventureWorksDW e importar a tabela FactInternetSales para um modelo. O exemplo de código pressupõe que os RangeStart parâmetros e RangeEnd a DateKey função não existem no modelo. Basta importar a tabela FactInternetSales e publicar o modelo em um workspace no Power BI Premium. Em seguida, atualize o workspaceUrl para que o exemplo de código possa se conectar ao seu modelo. Atualize mais linhas de código conforme necessário.

using System;
using TOM = Microsoft.AnalysisServices.Tabular;
namespace Hybrid_Tables
{
    class Program
    {
        static string workspaceUrl = "<Enter your Workspace URL here>";
        static string databaseName = "AdventureWorks";
        static string tableName = "FactInternetSales";
        static void Main(string[] args)
        {
            using (var server = new TOM.Server())
            {
                // Connect to the dataset.
                server.Connect(workspaceUrl);
                TOM.Database database = server.Databases.FindByName(databaseName);
                if (database == null)
                {
                    throw new ApplicationException("Database cannot be found!");
                }
                if(database.CompatibilityLevel < 1565)
                {
                    database.CompatibilityLevel = 1565;
                    database.Update();
                }
                TOM.Model model = database.Model;
                // Add RangeStart, RangeEnd, and DateKey function.
                model.Expressions.Add(new TOM.NamedExpression {
                    Name = "RangeStart",
                    Kind = TOM.ExpressionKind.M,
                    Expression = "#datetime(2021, 12, 30, 0, 0, 0) meta [IsParameterQuery=true, Type=\"DateTime\", IsParameterQueryRequired=true]"
                });
                model.Expressions.Add(new TOM.NamedExpression
                {
                    Name = "RangeEnd",
                    Kind = TOM.ExpressionKind.M,
                    Expression = "#datetime(2021, 12, 31, 0, 0, 0) meta [IsParameterQuery=true, Type=\"DateTime\", IsParameterQueryRequired=true]"
                });
                model.Expressions.Add(new TOM.NamedExpression
                {
                    Name = "DateKey",
                    Kind = TOM.ExpressionKind.M,
                    Expression =
                        "let\n" +
                        "    Source = (x as datetime) => Date.Year(x)*10000 + Date.Month(x)*100 + Date.Day(x)\n" +
                        "in\n" +
                        "    Source"
                });
                // Apply a RefreshPolicy with Real-Time to the target table.
                TOM.Table salesTable = model.Tables[tableName];
                TOM.RefreshPolicy hybridPolicy = new TOM.BasicRefreshPolicy
                {
                    Mode = TOM.RefreshPolicyMode.Hybrid,
                    IncrementalPeriodsOffset = -1,
                    RollingWindowPeriods = 1,
                    RollingWindowGranularity = TOM.RefreshGranularityType.Year,
                    IncrementalPeriods = 1,
                    IncrementalGranularity = TOM.RefreshGranularityType.Day,
                    SourceExpression =
                        "let\n" +
                        "    Source = Sql.Database(\"demopm.database.windows.net\", \"AdventureWorksDW\"),\n" +
                        "    dbo_FactInternetSales = Source{[Schema=\"dbo\",Item=\"FactInternetSales\"]}[Data],\n" +
                        "    #\"Filtered Rows\" = Table.SelectRows(dbo_FactInternetSales, each [OrderDateKey] >= DateKey(RangeStart) and [OrderDateKey] < DateKey(RangeEnd))\n" +
                        "in\n" +
                        "    #\"Filtered Rows\""
                };
                salesTable.RefreshPolicy = hybridPolicy;
                model.RequestRefresh(TOM.RefreshType.Full);
                model.SaveChanges();
            }
            Console.WriteLine("{0}{1}", Environment.NewLine, "Press [Enter] to exit...");
            Console.ReadLine();
        }
    }
}