Reputation: 2327
I have two objects, lets call them A and B.
Each contain the following property:
[IgnoreDataMember]
public string SalesforceId { get; set; }
Then I have another two objects, lets call them UpdatedA and UpdatedB, which respectively extend A and B, and include nothing but:
[DataMember(Name = "sf__Id")]
public new string SalesforceId { get; set; }
[DataMember(Name = "sf__Created")]
public bool SalesforceCreated { get; set; }
The reason for this is so that I can use ServiceStack
to convert A and B to CSV files and then use it again to convert CSV files from Salesforce back to C# Objects (If I don't ignore SalesforceId
, the upload to Salesforce Bulk API 2.0 will fail).
So, the first question part of this question is do I really need to create two separate classes for UpdatedA and UpdatedB, as these classes are nearly identical and are actually both poltergeists, because I only use them in the following two methods:
private Dictionary<string, A> Update(Dictionary<string, A> aByExternalIds, RelayerContext context) {
IConfiguration config = context.Config;
string url = $"{config["SalesforceInstanceBaseUrl"]}/services/data/{config["SalesforceVersion"]}/jobs/ingest/{context.job.Id}/successfulResults";
this.restClient.Get(url, context.token)
.FromCsv<List<UploadedA>>()
.ForEach((updatedA) => {
if (aByExternalIds.TryGetValue(updatedA.ExternalId, out A oldA)) {
oldA.SalesforceId = updatedA.SalesforceId;
}
});
return aByExternalIds;
}
private Dictionary<string, B> Update(Dictionary<string, B> bBySalesforceAId, RelayerContext context) {
IConfiguration config = context.Config;
string url = $"{config["SalesforceInstanceBaseUrl"]}/services/data/{config["SalesforceVersion"]}/jobs/ingest/{context.job.Id}/successfulResults";
this.restClient.Get(url, context.token)
.FromCsv<List<UploadedB>>()
.ForEach((updatedB) => {
if (bBySalesforceAId.TryGetValue(updatedB.A__c, out B oldB)) {
oldB.SalesforceId = updatedB.SalesforceId;
}
});
return bBySalesforceAId;
}
Which leads to the second part of this question.
Both of these questions are very similar. We can see that the inputs are mapped by different properties on A and B... so I think I could do something like create an interface:
public interface Identifiable {
public string getIdentifier();
}
which would could be used to return either updatedA.ExternalId
or updatedB.A__c
.
But I'm not sure what the method signature would look like if I'm using generics.
Also, if I don't know how I could handle FromCsv<List<UploadedA>>()
and FromCsv<List<UploadedB>>()
in a generic way (maybe passing in a function?)
Anyway, to sum up, what I'd like to do is reduce those these two methods to just one, and if I can remove one or both of those Uploaded
classes, so much the better.
Any ideas?
Upvotes: 2
Views: 53
Reputation: 36629
How about something like this:
public interface IBase
{
string SalesforceId { get; set; }
}
public class A : IBase
{
public string SalesforceId { get; set; }
}
public class UploadedA : A
{
public new string SalesforceId {
get => base.SalesforceId;
set => base.SalesforceId = value; }
public bool SalesforceCreated { get; set; }
}
public static void Update<T, TU>(Dictionary<string, T> oldBySalesForceId, Func<TU, string> updatedId)
where TU : T
where T : IBase
{
// Call service and read csv to produce a list of uploaded objects...
// Substituting with an empty list in the example
var list = new List<TU>();
foreach (var updated in list)
{
if (oldBySalesForceId.TryGetValue(updatedId(updated), out var old))
{
old.SalesforceId = updated.SalesforceId;
}
}
}
I have removed some details that did not seem relevant for the example. This uses generics with constraints and a interface to ensure both the updated and old value has a SalesForceId.
I changed the derived class so that it uses the same SalesforceId as the base class, you could change it to virtual/override if you prefer, but it is probably not a good idea that the base and derived class both have independent properties with the same name since it will be confusing.
It uses a delegate to describe the id/key for UpdatedA/UpdatedB. You could use an interface instead if you prefer.
Upvotes: 1