When executing the OpenScript method of my release script I want to store the indexfields, batchfields and variables to lists. I created a snippet for this
Dictionary<string, string> indexFields = new Dictionary<string, string>();
Dictionary<string, string> batchFields = new Dictionary<string, string>();
Dictionary<string, string> kofaxValues = new Dictionary<string, string>();
foreach (Value val in documentData.Values)
{
if (val.TableName.IsEmpty())
{
string sourceName = val.SourceName;
string sourceValue = val.Value;
switch (val.SourceType)
{
case KfxLinkSourceType.KFX_REL_INDEXFIELD:
indexFields.Add(sourceName, sourceValue);
break;
case KfxLinkSourceType.KFX_REL_VARIABLE:
kofaxValues.Add(sourceName, sourceValue);
break;
case KfxLinkSourceType.KFX_REL_BATCHFIELD:
batchFields.Add(sourceName, sourceValue);
break;
}
}
}
I want to do this because I need the field value. Each field name is unique so I can use it as a key.
When storing my custom properties to the ReleaseSetupData I can read them from the ReleaseData. Let's say two custom properties would return me the field name and the field type, so I know that the field is an IndexField and it's name is "MyIndexField".
I can use these information to access the Dictionary<string, string> indexFields and get the value from that Indexfield.
Currently I setup my ReleaseSetupData with this code
releaseSetupData.CustomProperties.RemoveAll();
// Save all custom properties here
releaseSetupData.CustomProperties.Add("myCustomProperty", "fooBar");
releaseSetupData.Links.RemoveAll();
foreach (IndexField indexField in releaseSetupData.IndexFields) // Save all IndexFields
{
releaseSetupData.Links.Add(indexField.Name, KfxLinkSourceType.KFX_REL_INDEXFIELD, indexField.Name);
}
foreach (BatchField batchField in releaseSetupData.BatchFields) // Save all BatchFields
{
releaseSetupData.Links.Add(batchField.Name, KfxLinkSourceType.KFX_REL_BATCHFIELD, batchField.Name);
}
foreach (dynamic batchVariable in releaseSetupData.BatchVariableNames) // Save all Variables
{
releaseSetupData.Links.Add(batchVariable, KfxLinkSourceType.KFX_REL_VARIABLE, batchVariable);
}
When the OpenScript method of my release script gets executed, the dictionaries (shown in the first snippet) stay empty. This is because documentData.Values is empty.
How can I fill documentData.Values?
You can't. The order of events is as follows:
OpenScript() is called - once per batch.
ReleaseDoc() is called - once per document
CloseScript() is called - once per batch.
The Values collection holds information specific to an individual document and as such will be empty during OpenScript(). Sometimes this isn't what you want - you may want to access another document's values, or exporting them all at once - e.g. in a single web service call.
Here's what I would recommend:
Create a wrapper class for Kofax' Document object. Here's my approach (only properties are shown). This class has a constructor that accepts a ReleaseData object as the single parameter, and all respective properties are populated in said contructor.
public class Document
{
public Dictionary<string, string> BatchFields { get; private set; }
public Dictionary<string, string> IndexFields { get; private set; }
public Dictionary<string,string> KofaxValues { get; set; }
public Dictionary<string, string> TextConstants { get; set; }
public Dictionary<string, string> CustomProperties { get; private set; }
public Dictionary<string,string> ConfigurationSettings { get; set; }
public List<Table> Tables { get; private set; }
private List<Column> Columns;
public List<string> ImageFileNames { get; private set; }
public string KofaxPDFFileName { get; private set; }
public string XdcFilePath { get; private set; }
public XDocument XDocument { get; private set; }
public string ImageFilePath { get; private set; }
public string KofaxPDFPath { get; private set; }
public string TextFilePath { get; private set; }
public byte[] BinaryImage { get; private set; }
public char CellSeparator { get; set; }
}
Then, during ReleaseDoc(), I would just add all of my Documents to a collection. Note that the connection documents is a defined as private in your ReleaseScript:
public KfxReturnValue ReleaseDoc()
{
documents.Add(new Document(DocumentData));
}
You can then decide when and where to export your data. It could be during the CloseScript() event as well, but keep in mind that sanity checks and potential exceptions related to document data (invalid index field values, et cetera) must be thrown during ReleaseDoc(). Using a custom wrapper class and a collection adds a lot of flexibility and features native to .NET to your Export Connector, such as LINQ - here's an example (this is impossible with Kofax' COM objects):
var noPdfs = documents.Where(x => x.KofaxPDFPath.Length == 0);
Related
I am using the new Azure.Data.Tables library from Microsoft to deal with Azure Table Storage. With the old library when you had an entity that implemented ITableEntity and you had a property that you did not want to save to the storage table you would use the [IgnoreProperty] annotation. However, this does not seem to be available on the new library.
What would be the equivalent on the Azure.Data.Tables package or how do you now avoid saving a property to table storage now?
This is the class I want to persist:
public class MySpatialEntity : ITableEntity
{
public int ObjectId { get; set; }
public string Name { get; set; }
public int MonitoringArea { get; set; }
//This is the property I want to ignore because table storage cannot store it
public Point Geometry { get; set; }
//ITableEntity Members
public virtual string PartitionKey { get => MonitoringArea.ToString(); set => MonitoringArea = int.Parse(value); }
public virtual string RowKey { get => ObjectId.ToString(); set => ObjectId = int.Parse(value); }
public DateTimeOffset? Timestamp { get; set; }
public ETag ETag { get; set; }
}
As of version 12.2.0.beta.1, Azure.Data.Tables table entity models now support ignoring properties during serialization via the [IgnoreDataMember] attribute and renaming properties via the [DataMember(Name="<yourNameHere>")] attribute.
See the changelog here.
I don't think there's anything like [IgnoreProperty] available as of now (at least with version 12.1.0).
I found two Github issues which talk about this:
https://github.com/Azure/azure-sdk-for-net/issues/19782
https://github.com/Azure/azure-sdk-for-net/issues/15383
What you can do is create a custom dictionary of the properties you want to persist in the entity and use that dictionary for add/update operations.
Please see sample code below:
using System;
using System.Collections.Generic;
using System.Drawing;
using Azure;
using Azure.Data.Tables;
namespace SO68633776
{
class Program
{
private static string connectionString = "connection-string";
private static string tableName = "table-name";
static void Main(string[] args)
{
MySpatialEntity mySpatialEntity = new MySpatialEntity()
{
ObjectId = 1,
Name = "Some Value",
MonitoringArea = 2
};
TableEntity entity = new TableEntity(mySpatialEntity.ToDictionary());
TableClient tableClient = new TableClient(connectionString, tableName);
var result = tableClient.AddEntity(entity);
}
}
public class MySpatialEntity: ITableEntity
{
public int ObjectId { get; set; }
public string Name { get; set; }
public int MonitoringArea { get; set; }
//This is the property I want to ignore because table storage cannot store it
public Point Geometry { get; set; }
//ITableEntity Members
public virtual string PartitionKey { get => MonitoringArea.ToString(); set => MonitoringArea = int.Parse(value); }
public virtual string RowKey { get => ObjectId.ToString(); set => ObjectId = int.Parse(value); }
public DateTimeOffset? Timestamp { get; set; }
public ETag ETag { get; set; }
public IDictionary<string, object> ToDictionary()
{
return new Dictionary<string, object>()
{
{"PartitionKey", PartitionKey},
{"RowKey", RowKey},
{"ObjectId", ObjectId},
{"Name", Name},
{"MonitoringArea", MonitoringArea}
};
}
}
}
I'm using AutoMapper 6. Consider the following classes:
public class Source
{
public FlattenableClass Flattenable { get; set; }
public List<EmailAddress> EmailAddresses { get; set; }
}
public class Destination
{
public string FlattenableProp1 { get; set; }
public string FlattenableProp2 { get; set; }
public MappedEmailAddress EmailAddress1 { get; set; }
}
where FlattenableClass has properties named Prop1 and Prop2. As you can see, the source has a collection of EmailAddress but the destination only needs the first one because although our database allows a collection of email addresses, the application is going to support one. I believe I can arrange this mapping from Source to Destination like so:
CreateMap<Source, Destination>()
.ForMember(d => d.EmailAddress1, opt => opt.ResolveUsing(s => s.EmailAddresses?.FirstOrDefault()));
but, unsurprisingly, if I then call ReverseMap() on that, it doesn't know how to map EmailAddress1 back into the EmailAddresses collection. Is there any way to get it to map EmailAddress1 back to an EmailAddress and add it to the EmailAddresses collection?
I haven't found any support in AutoMapper but I found a work around. I just created a target property in the source to do the work for me:
public class Destination
{
public string FlattenableProp1 { get; set; }
public string FlattenableProp2 { get; set; }
public MappedEmailAddress EmailAddress1 { get; set; }
public List<MappedEmailAddress> EmailAddresses
{
get
{
List<MappedEmailAddress> emails = new List<MappedEmailAddress>();
if (EmailAddress1 != null) emails.Add(EmailAddress1);
return emails;
}
set
{
if (value == null || value.Count == 0)
EmailAddress1 = new MappedEmailAddress();
else
EmailAddress1 = value[0];
}
}
}
This lets AutoMapper map Source.EmailAddresses to Destination.EmailAddresses and then Destination.EmailAddresses does the work of mapping to EmailAddress1. It's not ideal in that I had to expose collection of email addresses property which I don't want to ever be used by anyone but auto-mapper, but it gets it done.
I have a situation where I need to map a single property as a combination of multiple source properties based on some conditions.
Destination :
public class Email
{
public Email() {
EmailRecipient = new List<EmailRecipient>();
}
public string Subject{get; set;}
public string Body {get; set;}
public virtual ICollection<EmailRecipient> EmailRecipient { get; set; }
}
public class EmailRecipient
{
public int EmaiId { get; set; }
public string RecipientEmailAddress { get; set; }
public int RecipientEmailTypeId { get; set; }
public virtual Email Email { get; set; }
}
Source:
public class EmailViewModel
{
public List<EmailRecipientViewModel> To { get; set; }
public List<EmailRecipientViewModel> Cc { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
}
public class EmailRecipientViewModel
{
public string RecipientEmailAddress { get; set; }
}
I want Mapper.Map<EmailViewModel,Email>()
Here I would like to map my Email.EmailRecipient as a combination of EmailViewModel.To and EmailViewModel.Cc.
However the condition is, Email.EmailRecipient.RecipientEmailTypeId will be 1 for To and 2 for Cc
Hope my question is clear.
One possible way to achieve this is to create a map that uses a specific method for this conversion. The map creation would be:
Mapper.CreateMap<EmailViewModel, Email>()
.ForMember(e => e.EmailRecipient, opt => opt.MapFrom(v => JoinRecipients(v)));
Where the JoinRecipients method would perform the conversion itself. A simple implementation could be something like:
private ICollection<EmailRecipient> JoinRecipients(EmailViewModel viewModel) {
List<EmailRecipient> result = new List<EmailRecipient>();
foreach (var toRecipient in viewModel.To) {
result.Add(new EmailRecipient {
RecipientEmailTypeId = 1,
RecipientEmailAddress = toRecipient.RecipientEmailAddress
});
}
foreach (var ccRecipient in viewModel.Cc) {
result.Add(new EmailRecipient {
RecipientEmailTypeId = 2,
RecipientEmailAddress = ccRecipient.RecipientEmailAddress
});
}
return result;
}
I'm a huge opponent of converters, mostly because for other people in your project, things will just happen 'like magic' after the mapping call.
An easier way of handling this would be to implement the property as a method that converts other properties on the viewmodel to the required formatting. Example:
public class EmailViewModel
{
public ICollection<EmailRecipient> EmailRecipient {
get {
return To.Union(Cc);
}
}
public List<EmailRecipientViewModel> To { get; set; }
public List<EmailRecipientViewModel> Cc { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
}
Now automapper automatically maps from EmailRecipient property to EmailRecipient property, and if someone is trying to figure out how it happens, they just need to look on the viewmodel.
Editing this some years later: Just as a warning, doing things this way means that every time you call EmailRecipient, you incur the o(n) task of unioning the To and Cc fields. This is fine if you're only dealing with one email, but if you're reusing the viewmodel and someone sticks it in a loop with say, every other email in the system, it might be a huge performance issue. In that case I'd go with the accepted answer so that you dodge this potential performance pitfall.
I am using Azure Table Storage as my data sink for my Semantic Logging Application Block. When I call a log to be written by my custom EventSource, I get columns similar to the ff.:
EventId
Payload_username
Opcode
I can obtain these columns by creating a TableEntity class that matches the column names exactly (except for EventId, for some reason):
public class ReportLogEntity : TableEntity
{
public string EventId { get; set; }
public string Payload_username { get; set; }
public string Opcode { get; set; }
}
However, I would like to store the data in these columns in differently named properties in my TableEntity:
public class ReportLogEntity : TableEntity
{
public string Id { get; set; } // maps to "EventId"
public string Username { get; set; } // maps to "Payload_username"
public string Operation { get; set; } // maps to "Opcode"
}
Is there a mapper/attribute I can make use of to allow myself to have the column name different from the TableEntity property name?
You can override ReadEntity and WriteEntity methods of interface ITableEntity to customize your own property names.
public class ReportLogEntity : TableEntity
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public string Id { get; set; } // maps to "EventId"
public string Username { get; set; } // maps to "Payload_username"
public string Operation { get; set; } // maps to "Opcode"
public override void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext)
{
this.PartitionKey = properties["PartitionKey"].StringValue;
this.RowKey = properties["RowKey"].StringValue;
this.Id = properties["EventId"].StringValue;
this.Username = properties["Payload_username"].StringValue;
this.Operation = properties["Opcode"].StringValue;
}
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
{
var properties = new Dictionary<string, EntityProperty>();
properties.Add("PartitionKey", new EntityProperty(this.PartitionKey));
properties.Add("RowKey", new EntityProperty(this.RowKey));
properties.Add("EventId", new EntityProperty(this.Id));
properties.Add("Payload_username", new EntityProperty(this.Username));
properties.Add("Opcode", new EntityProperty(this.Operation));
return properties;
}
}
I have one question about Windows Workflow Foundation 4. I have an activity named PositionArrayActivity. This activity has a Sequence activity inside it. I need that in Execute method (during the workflow execution) oneFund variable mapping his value to PORTFOLIO_NAME that is created in Create method.... What have I to do to mapping oneFund value to PORTFOLIO_NAME at runtime?
Thanks
public sealed class PositionArrayActivity : NativeActivity, IActivityTemplateFactory
{
[Browsable(false)]
public Dictionary<string, List<Entity>> dictionary = new Dictionary<string, List<Entity>>();
public ActivityAction<Entity[]> Body { get; set; }
public Entity[] PositionList { get; set; }
public SqlDataReader rdr;
public SqlDataReader sdr;
public Entity[] positionArray;
public List<String> fundList;
public String oneFund { get; set; }
public String date { get; set; }
public List<Entity> listToArrayPositions;
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
metadata.AddDelegate(Body);
}
protected override void Execute(NativeActivityContext context)
{
// A lot of code....
}
public Activity Create(DependencyObject target)
{
Variable<string> var = new Variable<string>
{
Name = "PORTFOLIO_NAME"
};
var fef = new PositionArrayActivity();
var aa = new ActivityAction<Entity[]>();
var da = new DelegateInArgument<Entity[]>();
da.Name = "positions";
fef.Body = aa;
aa.Argument = da;
aa.Handler = new Sequence
{
Variables = { var }
};
return fef;
}
}
You need to have an ActivityContext to set a variable value so first move the declaration of the var (did that name actually compile?) to a higher scope.
Then in Execute
var.Set(activityContext, oneFund);
One thing though, the oneFund property will only be set once at application startup so you may have some surprising results. If you wanted that to be for each instance, you need an inargument.