【问题标题】:How can I insert an existing Google Worksheet into a Google Spreadsheet?如何将现有的 Google Worksheet 插入 Google 电子表格?
【发布时间】:2015-10-17 19:03:08
【问题描述】:

我正在编写一个集成了一些 Google 电子表格的 C# 应用程序。我的情况是工作表中有一些数据需要移动到不同的电子表格中。此工作表包含大量数据,因此我想避免遍历其内容。

API 指南向an example 提供了如何在电子表格中创建新工作表的方法。我对其进行了修改以将现有工作表添加到电子表格中:

using System;
using Google.GData.Client;
using Google.GData.Spreadsheets;

namespace MySpreadsheetIntegration
{
    class Program
    {
        static void Main(string[] args)
        {
            SpreadsheetsService service = new SpreadsheetsService("MySpreadsheetIntegration-v1");

            SpreadsheetEntry destinationSpreadsheet = fetchGoogleSpreadSheetEntry(service, "some_title");
            SpreadsheetEntry originSpreadsheet = fetchGoogleSpreadSheetEntry(service, "some_other_title");

            // Create a local representation of the new worksheet.
            WorksheetEntry originWorksheet = fetchGoogleWorksheet( originSpreadsheet, "some_worksheet_title" );

            // Send the local representation of the worksheet to the API for
            // creation.  The URL to use here is the worksheet feed URL of our
            // spreadsheet.
            WorksheetFeed wsFeed = destinationSpreadsheet.Worksheets;
            service.Insert(wsFeed, originWorksheet);
        }
    }
}

为清楚起见,上面的代码尝试将“some_other_title”电子表格中的“some_worksheet_title”工作表放入“some_title”电子表格中。以下是上述代码中引用的函数。

public static WorksheetEntry fetchGoogleWorksheet( SpreadsheetEntry spreadsheet, string worksheet_title )
{
    WorksheetFeed wsFeed = spreadsheet.Worksheets;
    WorksheetEntry worksheet = null;

    foreach (WorksheetEntry entry in wsFeed.Entries)
    {
        worksheet = entry;
        if (entry.Title.Text == worksheet_title)
        {
            Console.WriteLine(DateTime.Now.ToString("HH:mm") + ": Worksheet found on Google Drive.");
            break;
        }
    }

    if (worksheet.Title.Text != worksheet_title)
    {
        return null;
    }

    return worksheet;
}

public static SpreadsheetEntry fetchGoogleSpreadSheetEntry( SpreadsheetsService service, string spreadsheet_title )
{
    Console.WriteLine(DateTime.Now.ToString("HH:mm") + ": Looking for spreadsheet on Google Drive.");
    SpreadsheetQuery query = new SpreadsheetQuery();
    SpreadsheetFeed feed;

    feed = service.Query(query);

    SpreadsheetEntry spreadsheet = null;
    // Iterate through all of the spreadsheets returned
    foreach (SpreadsheetEntry entry in feed.Entries)
    {
        // Print the title of this spreadsheet to the screen
        spreadsheet = entry;
        if (entry.Title.Text == spreadsheet_title)
        {
            Console.WriteLine(DateTime.Now.ToString("HH:mm") + ": Spreadsheet found on Google Drive.");
            Console.WriteLine(DateTime.Now.ToString("HH:mm") + ": Looking for worksheet in spreadsheet.");
            break;
        }
    }

    if (spreadsheet.Title.Text != spreadsheet_title)
    {
        return null;
    }
    return spreadsheet;

    }   

我希望能够获取我想要添加到电子表格的工作表,然后将其添加到电子表格中。这没用。上面的代码在目标电子表格中创建了一个(正确标题的)工作表,但不传输工作表的任何内容。

有什么办法让它正确传输内容吗?

【问题讨论】:

  • 我能想到的就是批量更新。 (这确实意味着应对非常细胞)。或者谷歌应用程序脚本有一个命令来做到这一点。您可以通过 HTML 服务调用 Google Apps 脚本。
  • @eddyparkinson 我最初使用的是批量更新,但结果证明不可靠。我现在正在使用 Apps 脚本。我稍后会发布它作为答案

标签: c# google-spreadsheet-api


【解决方案1】:

在尝试了几种不同的方法之后,最可靠的方法竟然是Google Apps Scripting。一般而言,我的解决方案涉及一个由我的 C# 应用程序通过 execution API 调用的 Google Apps 脚本。下面是一些代码示例,展示了所有这些如何协同工作。

以下是用于将内容从一个工作表移动到另一个工作表的 Google Apps 脚本:

function copyWorksheet( destinationSpreadsheetId, destinationWorksheetTitle, originSpreadsheetId, originWorksheetTitle ) {

  // Spreadsheet where new data will go:
  var dss = SpreadsheetApp.openById(destinationSpreadsheetId);

  // Spreadsheet where new data is coming from:
  var oss = SpreadsheetApp.openById(originSpreadsheetId);

  // Worksheet containing new data:
  var dataOriginWorksheet = oss.getSheetByName(originWorksheetTitle);

  // Worksheet whose data will be 'overwritten':
  var expiredWorksheet = dss.getSheetByName(destinationWorksheetTitle);

  // If a spreadsheet only has one worksheet, deleting that worksheet causes an error.
  // Thus we need to know whether the expired worksheet is the only worksheet in it's parent spreadsheet.
  var expiredWorksheetIsAlone = dss.getNumSheets() == 1 && expiredWorksheet != null;

  // Delete the expired worksheet if there are other worksheets:
  if (expiredWorksheet != null && !expiredWorksheetIsAlone)
    dss.deleteSheet(expiredWorksheet);

  // Otherwise, rename it to something guaranteed not to clash with the new sheet's title:
  if(expiredWorksheetIsAlone)
    expiredWorksheet.setName(dataOriginWorksheet.getName() + destinationWorksheetTitle);

  // Copy the new data into it's rightful place, and give it it's rightful name.
  dataOriginWorksheet.copyTo(dss).setName(destinationWorksheetTitle);

  // Since there are now definitely 2 worksheets, it's safe to delete the expired one.
  if(expiredWorksheetIsAlone)
    dss.deleteSheet(expiredWorksheet);

  // Make sure our changes are applied ASAP:
  SpreadsheetApp.flush();

  return "finished";
}

这是我最终使用的代码的严格精简版本,这就是为什么有两个电子表格 ID 字段的原因。这意味着这两个工作表是否在同一个电子表格中并不重要。

解决方案的 C# 部分如下所示:

// We need these for the method below
using Google.Apis.Script.v1;
using Google.Apis.Script.v1.Data;

...    

public static bool copyWorksheet(ScriptService scriptService, string destinationSpreadsheetId, string destinationWorksheetTitle, string originSpreadsheetId, string originWorksheetTitle)
  {
    // You can get the script ID by going to the script in the 
    // Google Apps Scripts Editor > Publish > Deploy as API executable... > API ID
    string scriptId = "your-apps-script-id";

    ExecutionRequest request = new ExecutionRequest();
    request.Function = "copyWorksheet";
    IList<object> parameters = new List<object>();

    parameters.Add(destinationSpreadsheetId);
    parameters.Add(destinationWorksheetTitle);
    parameters.Add(originSpreadsheetId);
    parameters.Add(originWorksheetTitle);            

    request.Parameters = parameters;

    ScriptsResource.RunRequest runReq = scriptService.Scripts.Run(request, scriptId);

    try
    {
      Operation op = runReq.Execute();

      if (op.Error != null)
      {
        Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " The Apps script encountered an error");
        // The API executed, but the script returned an error.

        IDictionary<string, object> error = op.Error.Details[0];
        Console.WriteLine( "Script error message: {0}", error["errorMessage"]);
        if ( error.ContainsKey("scriptStackTraceElements") )
        {

          // There may not be a stacktrace if the script didn't
          // start executing.
          Console.WriteLine("Script error stacktrace:");
          Newtonsoft.Json.Linq.JArray st = (Newtonsoft.Json.Linq.JArray)error["scriptStackTraceElements"];
          foreach (var trace in st)
            {
              Console.WriteLine(
                "\t{0}: {1}",
                trace["function"],
                trace["lineNumber"]);
            }

          }
        }
        else
        {
          // The result provided by the API needs to be cast into
          // the correct type, based upon what types the Apps
          // Script function returns. Here, the function returns
          // an Apps Script Object with String keys and values.
          // It is most convenient to cast the return value as a JSON
          // JObject (folderSet).

          return true;

        }
      }
      catch (Google.GoogleApiException e)
      {
        // The API encountered a problem before the script
        // started executing.
        Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " Could not call Apps Script");
      }

      return false;
    }

...

以上两段代码,配合使用,完美解决问题。不同数据量之间的执行时间差别不大,传输时没有数据损坏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多