/// <summary> /// Prepares the specified bulk copy definition (freshly deserialized) before the loading process begins /// </summary> /// <param name="def">The bulk copy definition.</param> protected override NameValueCollection Prepare(FdoToolbox.Core.Configuration.FdoBulkCopyTaskDefinition def) { /* There is subtle precondition that would've resulted in all connection references being named to a * single reference, thus invalidating the whole task when loaded. * * If the task definition has any connection names to an *already* loaded connection, a rename operation * could overwrite a previous rename operation. Consider: * * Connection A) SDF_Desktop * Connection B) SDFConnection0 * * Loaded Connections: * - SDFConnection0 * - SDFConnection1 * * If during loading, SDF_Desktop matches to SDFConnection0, and SDFConnection0 matches to SDFConnection1 the rename operations * would then be: * * 1) Rename SDF_Desktop to SDFConnection0 * 2) Rename SDF_Connection0 to SDFConnection1 * * As a result, all referenced connections will eventually be renamed to SDFConnection1, which is not what we want. * * The solution bere is to "fix" the definition by renaming the named connections to something we know is not already a loaded * connection. This is done regardless to ensure consistent behaviour. Thsi method performs this solution. */ string prefix = "Connection"; int counter = 0; FdoConnectionManager connMgr = ServiceManager.Instance.GetService <FdoConnectionManager>(); NameValueCollection nameMappings = new NameValueCollection(); foreach (FdoConnectionEntryElement el in def.Connections) { string newName = prefix + counter; while (connMgr.GetConnection(newName) != null) { counter++; newName = prefix + counter; } string oldName = el.name; def.UpdateConnectionReferences(oldName, newName); nameMappings.Add(newName, oldName); counter++; } return(nameMappings); }
private FdoBulkCopyTaskDefinition Save() { FdoBulkCopyTaskDefinition def = new FdoBulkCopyTaskDefinition(); def.name = txtName.Text; List<FdoConnectionEntryElement> conns = new List<FdoConnectionEntryElement>(); foreach (DataGridViewRow row in grdConnections.Rows) { FdoConnectionEntryElement entry = new FdoConnectionEntryElement(); entry.name = row.Cells[0].Value.ToString(); entry.provider = row.Cells[1].Value.ToString(); entry.ConnectionString = row.Cells[3].Value.ToString(); conns.Add(entry); } List<FdoCopyTaskElement> tasks = new List<FdoCopyTaskElement>(); foreach (CopyTaskNodeDecorator dec in _tasks.Values) { FdoCopyTaskElement task = new FdoCopyTaskElement(); task.name = dec.Name; task.createIfNotExists = dec.CreateIfNotExists; task.Source = new FdoCopySourceElement(); task.Target = new FdoCopyTargetElement(); task.Options = new FdoCopyOptionsElement(); List<FdoPropertyMappingElement> pmaps = new List<FdoPropertyMappingElement>(); List<FdoExpressionMappingElement> emaps = new List<FdoExpressionMappingElement>(); //Source task.Source.@class = dec.SourceClassName; task.Source.connection = dec.SourceConnectionName; task.Source.schema = dec.SourceSchemaName; //Target task.Target.@class = dec.TargetClassName; task.Target.connection = dec.TargetConnectionName; task.Target.schema = dec.TargetSchemaName; //Options task.Options.BatchSize = dec.Options.BatchSize.ToString(); task.Options.FlattenGeometries = dec.Options.Flatten; task.Options.FlattenGeometriesSpecified = true; task.Options.DeleteTarget = dec.Options.Delete; task.Options.Filter = dec.Options.SourceFilter; task.Options.ForceWKB = dec.Options.ForceWkb; task.Options.ForceWKBSpecified = true; //Property Mappings NameValueCollection mappings = dec.PropertyMappings.GetPropertyMappings(); foreach (string srcProp in mappings.Keys) { string dstProp = mappings[srcProp]; FdoPropertyMappingElement p = new FdoPropertyMappingElement(); p.source = srcProp; p.target = dstProp; PropertyConversionNodeDecorator conv = dec.PropertyMappings.GetConversionRule(p.source); p.nullOnFailedConversion = conv.NullOnFailedConversion; p.truncate = conv.Truncate; p.createIfNotExists = conv.CreateIfNotExists; pmaps.Add(p); } foreach (string alias in dec.ExpressionMappings.GetAliases()) { FdoExpressionMappingElement e = new FdoExpressionMappingElement(); e.alias = alias; ExpressionMappingInfo exMap = dec.ExpressionMappings.GetMapping(alias); e.Expression = exMap.Expression; e.target = exMap.TargetProperty; PropertyConversionNodeDecorator conv = dec.ExpressionMappings.GetConversionRule(e.alias); e.nullOnFailedConversion = conv.NullOnFailedConversion; e.truncate = conv.Truncate; e.createIfNotExists = conv.CreateIfNotExists; emaps.Add(e); } task.PropertyMappings = pmaps.ToArray(); task.ExpressionMappings = emaps.ToArray(); tasks.Add(task); } def.Connections = conns.ToArray(); def.CopyTasks = tasks.ToArray(); return def; }
/// <summary> /// Saves this process to a file /// </summary> /// <param name="file">The file to save this process to</param> /// <param name="name">The name of the process</param> public override void Save(string file, string name) { FdoBulkCopyTaskDefinition def = new FdoBulkCopyTaskDefinition(); def.name = name; List<FdoConnectionEntryElement> connList = new List<FdoConnectionEntryElement>(); List<FdoCopyTaskElement> copyTasks = new List<FdoCopyTaskElement>(); foreach (string connName in _options.ConnectionNames) { FdoConnection conn = _options.GetConnection(connName); FdoConnectionEntryElement entry = new FdoConnectionEntryElement(); entry.name = connName; entry.provider = conn.Provider; entry.ConnectionString = conn.ConnectionString; if (conn.HasConfiguration) { string path = Path.GetDirectoryName(file); path = Path.Combine(path, entry.name + "_configuration.xml"); conn.SaveConfiguration(path); entry.configPath = path; } connList.Add(entry); } foreach (FdoClassCopyOptions copt in _options.ClassCopyOptions) { copyTasks.Add(copt.ToElement()); } def.Connections = connList.ToArray(); def.CopyTasks = copyTasks.ToArray(); using (StreamWriter writer = new StreamWriter(file, false)) { XmlSerializer ser = new XmlSerializer(typeof(FdoBulkCopyTaskDefinition)); ser.Serialize(writer, def); } }
/// <summary> /// Prepares the specified bulk copy definition (freshly deserialized) before the loading process begins /// </summary> /// <param name="def">The bulk copy definition.</param> protected override NameValueCollection Prepare(FdoBulkCopyTaskDefinition def) { return new NameValueCollection(); }
private static string GetSourceSchemaForMapping(FdoBulkCopyTaskDefinition def, string targetSchema, string[] classNames) { ISet<string> matches = new HashSet<string>(); List<string> classes = new List<string>(classNames); foreach (var task in def.CopyTasks) { if (classes.Contains(task.Target.@class) && task.Target.schema.Equals(targetSchema)) { matches.Add(task.Source.schema); } } if (matches.Count > 1) throw new TaskValidationException("The specified class names have various parent schema names"); if (matches.Count == 0) throw new TaskValidationException("Could not determine parent schema name for the given class names"); return new List<string>(matches)[0]; //The price to pay for targeting linq-less fx 2.0 }
/// <summary> /// Loads bulk copy options from deserialized xml /// </summary> /// <param name="def">The deserialized definition.</param> /// <param name="name">The name.</param> /// <param name="owner">if set to <c>true</c> [owner].</param> /// <returns></returns> public FdoBulkCopyOptions BulkCopyFromXml(FdoBulkCopyTaskDefinition def, ref string name, bool owner) { var nameMap = Prepare(def); // TODO/FIXME/HACK: // // The introduction of on-the-fly schema modifications before copying has introduced a // potential problem which will only occur when multiple copy tasks copy to the same target // feature class. // // What happens is because a target will be multiple sources copying to it, if we need // to create/update the target class definition, the current infrastructure will be confused // as to which source feature class to clone from. // // A possible solution is to return the first matching name. This will cause a "create" operation // to be queued. We then alter the pre-modification process so that subsequent "create" operations // becomes "update" operations instead. // // On second thought, the current infrastructure should be smart enought to prevent this, as // the UI only allows: // // 1. Mapping to an existing class // 2. Mapping to a class of the same name (create if necessary) // // It won't be possible to map to a class that doesn't exist, as the requirement for auto-create/update // is that the class name will be the same. // // ie. It won't be possible to create this mapping // // a. Foo -> Foo (create if necessary) // b. Bar -> Foo // // Rule 1 prevents mapping b) from happening // Rule 2 ensures that Foo will only ever be created once // // If multiple sources are mapped to the same target, I believe the bcp infrastructure should ensure // that target must already exist first. name = def.name; Dictionary<string, FdoConnection> connections = new Dictionary<string, FdoConnection>(); Dictionary<string, string> changeConnNames = new Dictionary<string, string>(); //TODO: Prepare() ensured that all connections have unique names that don't already exist //now in the effort to re-use existing connections, see which connection entries //already exist and can be renamed back to the old name foreach (FdoConnectionEntryElement entry in def.Connections) { string connName = entry.name; FdoConnection conn = CreateConnection(entry.provider, entry.ConnectionString, entry.configPath, ref connName); connections[connName] = conn; if (connName != entry.name) { changeConnNames[entry.name] = connName; } } foreach (string oldName in changeConnNames.Keys) { def.UpdateConnectionReferences(oldName, changeConnNames[oldName]); } //Compile the list of classes to be queried Dictionary<string, MultiSchemaQuery> queries = new Dictionary<string, MultiSchemaQuery>(); foreach (FdoCopyTaskElement task in def.CopyTasks) { MultiSchemaQuery src = null; MultiSchemaQuery dst = null; //Process source if (!queries.ContainsKey(task.Source.connection)) src = queries[task.Source.connection] = new MultiSchemaQuery(task.Source.connection, SchemaOrigin.Source); else src = queries[task.Source.connection]; var sq = src.TryGet(task.Source.schema); if (sq != null) { sq.AddClass(task.Source.@class); } else { sq = new SchemaQuery(task.Source.schema); sq.AddClass(task.Source.@class); src.Add(sq); } //Process target if (!queries.ContainsKey(task.Target.connection)) dst = queries[task.Target.connection] = new MultiSchemaQuery(task.Target.connection, SchemaOrigin.Target); else dst = queries[task.Target.connection]; var tq = dst.TryGet(task.Target.schema); if (tq != null) { tq.AddClass(task.Target.@class); } else { tq = new SchemaQuery(task.Target.schema); tq.AddClass(task.Target.@class); dst.Add(tq); } } List<TargetClassModificationItem> modifiers = new List<TargetClassModificationItem>(); using (var schemaCache = new FeatureSchemaCache()) { //Now populate the schema cache with source schemas foreach (string connName in queries.Keys) { if (connections.ContainsKey(connName)) { var mqry = queries[connName]; var conn = connections[connName]; FeatureSchemaCollection schemas = new FeatureSchemaCollection(null); using (var svc = conn.CreateFeatureService()) { foreach (var sq in mqry.SchemaQueries) { //Source schema queries are expected to fully check out so use original method if (mqry.Origin == SchemaOrigin.Source) { schemas.Add(svc.PartialDescribeSchema(sq.SchemaName, new List<string>(sq.ClassNames))); } } } if (schemas.Count > 0) schemaCache.Add(connName, schemas); } } //Now populate the schema cache with target schemas, taking note of any //classes that need to be created or updated. foreach (string connName in queries.Keys) { if (connections.ContainsKey(connName)) { var mqry = queries[connName]; var conn = connections[connName]; FeatureSchemaCollection schemas = new FeatureSchemaCollection(null); using (var svc = conn.CreateFeatureService()) { foreach (var sq in mqry.SchemaQueries) { //Source schema queries are expected to fully check out so use original method if (mqry.Origin == SchemaOrigin.Target) { //Because we may need to create new classes, the old method will break down on non-existent class names //So use the new method string[] notFound; var schema = svc.PartialDescribeSchema(sq.SchemaName, new List<string>(sq.ClassNames), out notFound); //if (notFound.Length > 0) //{ // //If we can't modify schemas we'll stop right here. This is caused by elements containing createIfNotExists = true // if (!conn.Capability.GetBooleanCapability(CapabilityType.FdoCapabilityType_SupportsSchemaModification)) // throw new NotSupportedException("The connection named " + connName + " does not support schema modification. Therefore, copy tasks and property/expression mappings cannot have createIfNotExists = true"); // //This cumbersome, but we need the parent schema name of the class that we will need to copy // string srcSchema = GetSourceSchemaForMapping(def, sq.SchemaName, notFound); // foreach (string className in notFound) // { // modifiers.Add(new CreateTargetClassFromSource(srcSchema, className)); // } //} schemas.Add(schema); } } } if (schemas.Count > 0) schemaCache.Add(connName, schemas); } } FdoBulkCopyOptions opts = new FdoBulkCopyOptions(connections, owner); foreach (FdoCopyTaskElement task in def.CopyTasks) { TargetClassModificationItem mod; FdoClassCopyOptions copt = FdoClassCopyOptions.FromElement(task, schemaCache, connections[task.Source.connection], connections[task.Target.connection], out mod); opts.AddClassCopyOption(copt); if (mod != null) copt.PreCopyTargetModifier = mod; //opts.AddClassModifier(mod); } return opts; } }
/// <summary> /// Prepares the specified bulk copy definition (freshly deserialized) before the loading process begins /// </summary> /// <param name="def">The bulk copy definition.</param> /// <returns>A collection of [old name] - [new name] mappings</returns> protected abstract NameValueCollection Prepare(FdoBulkCopyTaskDefinition def);