在尝试了 SSIS 和 KingswaySoft 后,我选择编写一个控制台应用程序来迁移我的 Dataverse 表。 事实证明,与无代码/低代码方法相比,代码对我来说更熟悉并且更容易。 我遇到的一个问题是保留源记录中的用户/日期戳。 迁移时,这些值会被系统覆盖。
我找到了一种 OOTB 方法来使用
createdon
覆盖 overriddencreatedon
,但这种方法并不全面,并且不适用于所有 4 个领域。
我最终编写了一个 RecordStamps 插件。 我将其连接到每个正在迁移的表的创建/更新操作,特别是预操作阶段。
我创建了一个用于迁移目的的中间表,称为迁移元。 在其中我创建了 Meta Created On、Meta Created By、Meta Modified On、Meta Modified By,所有这些都是为了确保列名称与系统更新的列名称不同。我还将主要名称字段命名为实体名称。
在迁移应用程序中,在迁移任何记录之前,我执行以下逻辑:
//cache meta for use in RecordStamps Plugin.
try {
var mm = new Entity("crab9_migrationmeta", rec.Id);
mm["crab9_entityname"] = rec.LogicalName;
foreach (var key in new string[] { "modifiedby", "modifiedon", "createdby", "createdon" }) {
mm[$"crab9_meta{key}"] = rec.Contains(key) ? rec[key] : null;
}
var found = false;
try {
this.targetDb.Retrieve(mm.LogicalName, mm.Id, new ColumnSet(false));
found = true;
} catch {
}
if (found) {
this.targetDb.Update(mm);
} else {
this.targetDb.Create(mm);
}
} catch (Exception ex) {
throw new Exception("Unable to set meta.", ex);
}
我将源记录元数据值保存到这个表中,以便跟踪每条迁移的记录。 回想一下,GUID 是全局唯一的。保存元数据后,我将记录从源迁移到目标 Dataverse 环境。
RecordStamps 插件拦截创建/更新,使用 Id 和实体名称从迁移元表中检索靶心命中,然后在写入之前替换这些事实。 由于它会给环境增加一些开销,因此仅在迁移期间启用。
using System;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
namespace RecordStamps {
public class RecordStampsPlugin : IPlugin {
public static readonly string[] fields = new string[] { "createdon", "createdby", "modifiedon", "modifiedby" };
public void Execute(IServiceProvider serviceProvider) {
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = serviceFactory.CreateOrganizationService(context.UserId);
var target = (Entity)context.InputParameters["Target"];
if (context.Stage == 20) {
try {
tracingService.Trace("Checking record stamps at {1} for {0}", Tracing.Show(target), context.MessageName);
var mm = service.Retrieve("crab9_migrationmeta", target.Id, new ColumnSet(true));
if (mm != null) {
if (mm["crab9_entityname"] != target.LogicalName) {
throw new Exception("Meta entity name does not match target.");
}
tracingService.Trace("Applying record stamps {0}", Tracing.ShowAll(mm));
foreach (var key in fields) {
var mkey = $"crab9_meta{key}";
if (mm[mkey] != null) {
target[key] = mm[mkey];
tracingService.Trace("Applied {0} => {1}", key, Tracing.Show(target[key]));
}
}
}
} catch (Exception ex) {
tracingService.Trace("Error {0}", Tracing.Show(ex));
}
}
}
}
}
这个插件逻辑可能会有一些迭代,但到目前为止它运行良好。
Tracing.Show
是用于漂亮打印的自定义逻辑。
我提到了创建/更新。 我发现我不能只在目标环境中创建从源获取的非活动记录。 我必须首先将它们迁移(创建)为活动状态,然后稍后(更新)将它们设置为非活动状态。
迁移元表的额外好处是可以轻松撤消迁移,因为您迁移的所有内容都会被跟踪。 如果您想捕获与迁移相关的任何其他内容,您可以向其中添加其他字段。 我的控制台应用程序支持“迁移”和“清除”命令。 这使得它非常适合在测试环境中进行迭代开发。
由于
systemuser
记录是关键依赖项,因此我首先迁移它们,并且不费心修复它们的元事实。 您可以扫描源表以确定需要哪些。