Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 5 years ago.
Improve this question
I am trying to use Threading in my code, I have used this code in my solution without Threading.
I am trying to create bulk records in MS CRM. Sometimes the record count goes very high.
//Without Threading
foreach (EntityCollection col in RetrieveContactsFromStaticML(ConnectToCrm(),marketingListRecord))
{
CreateRecordsAgainstContact(_orgService, col, rewardDetails, errorLoger);
}
But this is taking too much time to execute. Because every time "CreateRecordsAgainstContact" method gets called, the process waits for this method to execute.
So I decided to use Threading.
//With Threading
foreach (EntityCollection col in RetrieveContactsFromStaticML(ConnectToCrm(),marketingListRecord))
{
var t1 = Task.Factory.StartNew(delegate ()
{
CreateRecordsAgainstContact(_orgService, col, rewardDetails,errorLoger);
});
}
But I am not sure about: Is this the best way to use threading?
Here is my code:
namespace ActivityDistribution
{
public class Distribution : IDistribution
{
private static IOrganizationService _orgService = null;
private static Logger errorLoger = new Logger();
public void CreateRewardsAndOfferRecords(Guid campaignActivityId)
{
RewardsAndOffers rewardDetails = new RewardsAndOffers();
Entity CampaignActivity = null;
Entity ParentCampaign = null;
EntityCollection marketingListCollection = null;
try
{
_orgService = ConnectToCrm();
#region RewardsAndOffers Details
CampaignActivity = _orgService.Retrieve("campaignactivity", campaignActivityId, new ColumnSet("subject", "regardingobjectid", "channeltypecode"));
ParentCampaign = _orgService.Retrieve("campaign", new Guid(((EntityReference)CampaignActivity["regardingobjectid"]).Id.ToString()), new ColumnSet("name", "istemplate", "statuscode", "new_rewardcategory", "new_rewardsubcategory"));
rewardDetails.CampaignActivity = new EntityReference(CampaignActivity.LogicalName, CampaignActivity.Id);
rewardDetails.ParentCampaign = new EntityReference(ParentCampaign.LogicalName, ParentCampaign.Id);
#endregion
#region Get MarketingList of given Campaign Activity
QueryExpression queryMarketingList = new QueryExpression("list")
{
ColumnSet = new ColumnSet("query", "listname", "type"),
LinkEntities =
{
new LinkEntity
{
JoinOperator = JoinOperator.Inner,
LinkFromAttributeName = "listid",
LinkFromEntityName = "list",
LinkToAttributeName = "itemid",
LinkToEntityName = "campaignactivityitem",
LinkEntities =
{
new LinkEntity
{
JoinOperator = JoinOperator.Inner,
LinkFromAttributeName = "campaignactivityid",
LinkFromEntityName = "campaignactivityitem",
LinkToAttributeName = "activityid",
LinkToEntityName = "campaignactivity",
LinkCriteria =
{
Conditions =
{
new ConditionExpression("activityid", ConditionOperator.Equal, rewardDetails.CampaignActivity.Id)
}
}
}
},
}
}
};
marketingListCollection = _orgService.RetrieveMultiple(queryMarketingList);
#endregion
#region Fetch MarketingList's Contacts and Create Rewards&Offers Redords against MarketingList's Contacts
if (marketingListCollection.Entities.Count() > 0)
foreach (var marketingListRecord in marketingListCollection.Entities)
{
//With Threading
foreach (EntityCollection col in RetrieveContactsFromStaticML(ConnectToCrm(), marketingListRecord))
{
var t1 = Task.Factory.StartNew(delegate ()
{
CreateRecordsAgainstContact(_orgService, col, rewardDetails, errorLoger);
});
}
//Without Threading
foreach (EntityCollection col in RetrieveContactsFromStaticML(ConnectToCrm(), marketingListRecord))
{
CreateRecordsAgainstContact(_orgService, col, rewardDetails, errorLoger);
}
}
#endregion
}
catch (Exception ex)
{
errorLoger.Log(ex);
}
}
private static IOrganizationService ConnectToCrm()
{
IOrganizationService orgService = null;
ClientCredentials credentials = new ClientCredentials();
credentials.UserName.UserName = Credentials.UserName;
credentials.UserName.Password = Credentials.Password;
Uri serviceUri = new Uri(Credentials.OrganizationService);
OrganizationServiceProxy proxy = new OrganizationServiceProxy(serviceUri, null, credentials, null);
proxy.EnableProxyTypes();
proxy.Timeout = new TimeSpan(4, 0, 0);
orgService = (IOrganizationService)proxy;
return orgService;
}
public static IEnumerable<EntityCollection> RetrieveContactsFromStaticML(IOrganizationService service, Entity entity)
{
var queryExpression = new QueryExpression()
{
Distinct = false,
EntityName = "contact",
ColumnSet = new ColumnSet("fullname", "telephone1"),
LinkEntities =
{
new LinkEntity
{
JoinOperator = JoinOperator.Inner,
LinkFromAttributeName = "contactid",
LinkFromEntityName = "contact",
LinkToAttributeName = "entityid",
LinkToEntityName = "listmember",
LinkEntities =
{
new LinkEntity
{
JoinOperator = JoinOperator.Inner,
LinkFromAttributeName = "listid",
LinkFromEntityName = "listmember",
LinkToAttributeName = "listid",
LinkToEntityName = "list",
LinkCriteria =
{
Conditions =
{
new ConditionExpression("listid", ConditionOperator.Equal, entity.Id)
}
}
}
}
}
}
};
foreach (EntityCollection col in RetrieveMultipleRecords(service, queryExpression))
{
yield return col;
}
}
public static IEnumerable<EntityCollection> RetrieveMultipleRecords(IOrganizationService service, QueryExpression queryExpression)
{
int fetchCount = 5000;
int pageNumber = 1;
queryExpression.PageInfo = new PagingInfo();
queryExpression.PageInfo.Count = fetchCount;
queryExpression.PageInfo.PageNumber = pageNumber;
queryExpression.PageInfo.PagingCookie = null;
while (true)
{
EntityCollection col = new EntityCollection();
EntityCollection collection = service.RetrieveMultiple(queryExpression);
if (collection.Entities.Count > 0)
foreach (Entity e in collection.Entities)
{
col.Entities.Add(e);
}
queryExpression.PageInfo.PageNumber++;
queryExpression.PageInfo.PagingCookie = collection.PagingCookie;
yield return col;
if (!collection.MoreRecords)
yield break;
}
}
public static void CreateRecordsAgainstContact(IOrganizationService service, EntityCollection contactColletion, RewardsAndOffers rewardsAndOffers, Logger errorLoggger)
{
var requestWithResults = new ExecuteMultipleRequest()
{
Settings = new ExecuteMultipleSettings()
{
ContinueOnError = true,
ReturnResponses = false
},
Requests = new OrganizationRequestCollection()
};
int maxRecordExecuteCount = 0;
int totalRecords = 0;
foreach (Entity contact in contactColletion.Entities)
{
Entity entity = new Entity("new_rewardsandoffers");
entity["new_campaignactivity"] = rewardsAndOffers.CampaignActivity;
entity["new_campaign"] = rewardsAndOffers.ParentCampaign;
entity["new_contact"] = new EntityReference("contact", contact.Id);
CreateRequest createRequest = new CreateRequest { Target = entity };
requestWithResults.Requests.Add(createRequest);
maxRecordExecuteCount++;
totalRecords++;
if (maxRecordExecuteCount == 1000 || totalRecords == contactColletion.Entities.Count())
{
service.Execute(requestWithResults);
maxRecordExecuteCount = 0;
requestWithResults.Requests = new OrganizationRequestCollection();
}
}
}
}
public class RewardsAndOffers
{
public EntityReference ParentCampaign { get; set; }
public EntityReference CampaignActivity { get; set; }
public OptionSetValue RewardCategory { get; set; }
public OptionSetValue RewardSubCategory { get; set; }
}
class Credentials
{
public static string OrganizationService
{
get
{
return ConfigurationManager.AppSettings["OrganizationService"].ToString();
}
}
public static string UserName
{
get
{
return ConfigurationManager.AppSettings["UserName"].ToString();
}
}
public static string Password
{
get
{
return ConfigurationManager.AppSettings["Password"].ToString();
}
}
}
}
This will not work. Concurrent executing tasks cannot share the same IOrganizationService instance. You need an object pool providing reusable service instances. Limit the number of instances in the pool to a maximum of 10.
I recommend the Producer Consumer pattern using a BlockingCollection<T> for scheduling the work that needs to be done. (See Blocking Collection and the Producer-Consumer Problem.)
I'd like to use manually created threads and control it's count. In this example there is a common queue which is processed by multiple threads:
public class InsertBulkRecordsTask
{
private ConcurrentQueue<Contact> _contactsQueue;
public void Execute()
{
try
{
var contacts = RetrieveContactsFromStaticML(ConnectToCrm(), marketingListRecord);
_contactsQueue = new ConcurrentQueue<Contact>(contacts);
var threadsCount = AppConfigReader.ThreadsCount;
var threads = new List<Thread>();
for (int i = 0; i < threadsCount; i++)
threads.Add(new Thread(ProcessContactsQueue) { IsBackground = true });
threads.ForEach(r => r.Start());
threads.ForEach(r => r.Join());
}
catch (Exception ex)
{
// TODO: log
}
}
private void ProcessContactsQueue()
{
try
{
while (_contactsQueue.IsEmpty == false)
{
Contact contact;
if (_contactsQueue.TryDequeue(out contact) && contact != null)
{
try
{
// Save contact
}
catch (Exception ex)
{
// TODO: log
}
}
}
}
catch (Exception ex)
{
// TODO: log
}
}
}
ExecuteMultipleRequest is meant for bulk operations, I recommend checking it out (and avoid the effort of implementing threading):
https://msdn.microsoft.com/en-us/library/jj863631.aspx
This is the example provided in the MSDN page, it demonstrates multiple Create requests:
// Get a reference to the organization service.
using (_serviceProxy = new OrganizationServiceProxy(serverConfig.OrganizationUri, serverConfig.HomeRealmUri,serverConfig.Credentials, serverConfig.DeviceCredentials))
{
// Enable early-bound type support to add/update entity records required for this sample.
_serviceProxy.EnableProxyTypes();
#region Execute Multiple with Results
// Create an ExecuteMultipleRequest object.
requestWithResults = new ExecuteMultipleRequest()
{
// Assign settings that define execution behavior: continue on error, return responses.
Settings = new ExecuteMultipleSettings()
{
ContinueOnError = false,
ReturnResponses = true
},
// Create an empty organization request collection.
Requests = new OrganizationRequestCollection()
};
// Create several (local, in memory) entities in a collection.
EntityCollection input = GetCollectionOfEntitiesToCreate();
// Add a CreateRequest for each entity to the request collection.
foreach (var entity in input.Entities)
{
CreateRequest createRequest = new CreateRequest { Target = entity };
requestWithResults.Requests.Add(createRequest);
}
// Execute all the requests in the request collection using a single web method call.
ExecuteMultipleResponse responseWithResults =
(ExecuteMultipleResponse)_serviceProxy.Execute(requestWithResults);
The full sample is here https://msdn.microsoft.com/en-us/library/jj863604.aspx
Related
Trying to create kind of market scanner. Code below is supposed to return chain of option contracts. Call to TWS API is an async method that returns some data only if I get ContractEnd or Error response from TWS. On the first call to reqContractDetails() it works as expected, I get list of contracts, receive message "ContractEnd", and exit from the method.
Obstacle
In some reason, on the second call to reqContractDetails() I don't get any notification from TWS. I have to stop and restart my application, initiating new connection to the server to make it working again.
Update
After refactoring my code I'm getting an error on a second call that says "Unable to read beyond the end of the stream". Call stack looks this way.
IBLibrary.dll!IBLibrary.OptionService.GetOptionsChain.AnonymousMethod__3(IBLibrary.Messages.ErrorMessage data) Line 64
IBLibrary.dll!IBLibrary.Classes.Client.error(string str) Line 42
CSharpAPI.dll!IBApi.EReader.putMessageToQueue() Line 94
CSharpAPI.dll!IBApi.EReader.Start.AnonymousMethod__9_0() Line 48
My implementation of the wrapper in C#
public class BaseService : IDisposable
{
protected Client Sender { get; set; }
protected EReader Receiver { get; set; }
public BaseService()
{
Sender = new Client();
Sender.Socket.eConnect("127.0.0.1", 7496, 0);
Receiver = new EReader(Sender.Socket, Sender.Signal);
Receiver.Start();
var process = new Thread(() =>
{
while (Sender.Socket.IsConnected())
{
Sender.Signal.waitForSignal();
Receiver.processMsgs();
}
})
{
IsBackground = true
};
process.Start();
}
public void Dispose()
{
Sender.Socket.eDisconnect();
}
}
public class OptionService : BaseService
{
public Task<List<OptionModel>> GetOptionsChain(OptionModel query)
{
if (query == null)
{
query = new OptionModel();
}
var process = Task.Run(() =>
{
var done = false;
var id = new Random(DateTime.Now.Millisecond).Next();
var contract = new Contract
{
Symbol = query.Symbol,
SecType = "OPT",
Exchange = "SMART",
Currency = "USD",
LastTradeDateOrContractMonth = query.Expiration
};
var contracts = new List<OptionModel>();
Action<ErrorMessage> errorMessage = null;
Action<ContractDetailsMessage> contractMessage = null;
Action<ContractDetailsEndMessage> contractMessageEnd = null;
contractMessage = (ContractDetailsMessage data) =>
{
contracts.Add(new OptionModel
{
Symbol = data.ContractDetails.Contract.Symbol,
Right = data.ContractDetails.Contract.Right,
Strike = data.ContractDetails.Contract.Strike,
Expiration = data.ContractDetails.RealExpirationDate
});
};
// I receive this message at first, but not the second time
contractMessageEnd = (ContractDetailsEndMessage data) =>
{
done = true;
};
errorMessage = (ErrorMessage data) =>
{
var notifications = new List<int>
{
(int) ErrorCode.MarketDataFarmConnectionIsOK,
(int) ErrorCode.HmdsDataFarmConnectionIsOK
};
if (notifications.Contains(data.ErrorCode) == false)
{
done = true;
}
};
Sender.ErrorEvent += errorMessage;
Sender.ContractDetailsEvent += contractMessage;
Sender.ContractDetailsEndEvent += contractMessageEnd;
Sender.Socket.reqContractDetails(id, contract);
// Execute method until we get all contracts
// The econd call to reqContractDetails doesn't return
// any notification, so obviously this line hangs forever
while (done == false);
Sender.ErrorEvent -= errorMessage;
Sender.ContractDetailsEvent -= contractMessage;
Sender.ContractDetailsEndEvent -= contractMessageEnd;
return contracts;
});
return process;
}
}
As far as nobody has the answer, even IB itself, the only solution that I see is, to convert my API controller to a synchronous controller and close socket connection to IB server after every request.
Old version.
public class ServiceOptionsController : BaseServiceController
{
OptionService Service = new OptionService();
[AcceptVerbs("POST")]
public async Task<List<OptionModel>> Options([FromBody] dynamic data)
{
var selectors = data.ToObject<QueryModel>();
var optionModel = new OptionModel
{
Symbol = "MSFT",
Expiration = "201806"
};
var processes = new List<Task<List<OptionModel>>>
{
Service.GetOptionsChain(optionModel)
};
return (await Task.WhenAll(processes)).SelectMany(o => o).ToList();
}
}
Working version.
public class ServiceOptionsController : BaseServiceController
{
[AcceptVerbs("POST")]
public List<OptionModel> Options([FromBody] dynamic data)
{
var selectors = data.ToObject<QueryModel>();
var optionModel = new OptionModel
{
Symbol = "MSFT",
Expiration = "201806"
};
var optionService = new OptionService();
var processes = new List<Task<List<OptionModel>>>
{
optionService.GetOptionsChain(optionModel)
};
var items = Task.WhenAll(processes).Result.SelectMany(o => o).ToList();
optionService.Dispose(); // Ridiculous fix for ridiculous API
return items;
}
}
I have an ASP.NET Web API with SQL Database / Entity Framework that I will be using as the server side of an application that I am building. The client side will be WPF for now. I am having some trouble determining the best method for the MVVM controls to access the data from the server. All of the tutorials and courses I have done for MVVM connect directly to a local database, and they update when the control is loaded. What I want is a constant loop where the API is called once per minute and the data in the control automatically refreshed at that point. I have this working for the two main controls (ran into a problem updating the collection from a different thread and had to use App.Current.Dispatcher.Invoke), however I am not sure if I am keeping to best practice running the API calls directly from the view model. Also, I need to be able to share data between the view models.
ScheduledForwardsViewModel has data that ActiveForwardsViewModel needs, and ActiveForwardsViewModel has data that CreateForwardsViewModel needs. The ActiveForward for each entry needs to be able to see the scheduled date for each corresponding scheduled item so it can find the next event and display the time left until that event occurs in the ActiveForwardsView. The CreateForwardView simply needs access to the AllUsers,AllGroups,AllContacts observable collections created during the ActiveForwards data manipulation so it can use that data in the drop down fields when scheduling a new forward. I haven't been able to get the dropdowns to work yet. I do have access to the info I need from ScheduledForwardsView using a static object, but I feel like there may be a better way. I would prefer to refrain from making multiple calls to the API for the same data when part of my app already has the data I need.
Basic Layout:
MainWindow
CreateForwardView (with view model)
[Drop down boxes to select user and schedule forward]
[Buttons, etc.]
[TabControl]
[TabItem: Active Forwards]
ActiveForwardsView (with view model)
[TabItem: Scheduled Forwards]
ScheduledForwardsView (with view model)
[EndTabControl]
The ActiveForwardsViewModel is below. The ScheduledForwardsViewModel is essentially the same thing, but it calls a different API method and contains a static object that I am using in ActiveForwardsViewModel (see the part where I wait for ScheduledForwardsViewModel to complete 1 run before I continue with UpdateUserObjects). That didn't seem like the right way to do it, but I set it up that way just so I could move onto other things.
public class ActiveForwardsViewModel : INotifyPropertyChanged
{
private CancellationToken updater = default(CancellationToken);
private AppLog log;
private FFObject _selectedObject;
private ObservableCollection<FFObject> _activeForwards;
public ObservableCollection<FFObject> ActiveForwards
{
get { return _activeForwards; }
set
{
if (_activeForwards != value)
{
_activeForwards = value;
PropertyChanged(this, new PropertyChangedEventArgs("ActiveForwards"));
}
}
}
public ObservableCollection<FFObject> AllUsers { get; set; }
public ObservableCollection<FFObject> AllGroups { get; set; }
public ObservableCollection<FFObject> AllContacts { get; set; }
public ObservableCollection<FFObject> AllObjects { get; set; }
public bool Running = false;
public bool FirstRunComplete = false;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ActiveForwardsViewModel()
{
if (DesignerProperties.GetIsInDesignMode(new System.Windows.DependencyObject()))
{
updater.ThrowIfCancellationRequested();
return;
}
ActiveForwards = new ObservableCollection<FFObject>();
AllUsers = new ObservableCollection<FFObject>();
AllGroups = new ObservableCollection<FFObject>();
AllContacts = new ObservableCollection<FFObject>();
AllObjects = new ObservableCollection<FFObject>();
StartUpdater(updater);
RemoveForwardCommand = new RelayCommand(OnRemove, CanRemove);
}
public async Task StartUpdater(CancellationToken token = default(CancellationToken))
{
while (!token.IsCancellationRequested)
{
Running = true;
await this.Update();
FirstRunComplete = true;
try
{
await Task.Delay(TimeSpan.FromMinutes(1), token);
}
catch (TaskCanceledException)
{
Running = false;
break;
}
}
}
private List<FFObject> UpdateAllObjectList(string strJson)
{
var serializer = new JavaScriptSerializer();
return serializer.Deserialize<List<FFObject>>(strJson);
//return AllObjects;
}
public string GetString(string method)
{
using (var client = new WebClient())
{
var url = string.Format("https://mywebapi.domain.com/api/{0}", method);
return client.DownloadString(url);
}
}
public async Task Update()
{
Func<string, List<FFObject>> getUsersJson = UpdateAllObjectList;
await Task<string>.Factory.StartNew(() => GetString("Users"))
.ContinueWith(antecendent => getUsersJson(antecendent.Result))
.ContinueWith(antecendent => UpdateUserObjects(antecendent.Result));
}
private void CheckRemoved(List<FFObject> all)
{
List<FFObject> RemoveObjects = new List<FFObject>();
foreach (var obj in ActiveForwards)
{
var newObj = all.FirstOrDefault(i => i.ID == obj.ID);
if (newObj == null)
{
RemoveObjects.Add(obj);
}
else
{
if (!(bool) newObj.IsForwarded)
{
RemoveObjects.Add(obj);
}
}
}
foreach (var obj in RemoveObjects)
{
App.Current.Dispatcher.Invoke((Action)delegate
{
ActiveForwards.Remove(obj);
});
}
}
private void UpdateUserObjects(List<FFObject> all)
{
Debug.WriteLine("Starting UpdateUserObject");
if (all != null)
{
AllObjects = new ObservableCollection<FFObject>(all);
CheckRemoved(all);
var x = 0;
while (!ScheduledForwardsViewModel.RunOnce && x < 5)
{
System.Threading.Thread.Sleep(1000);
x++;
}
foreach (var obj in all)
{
if (obj.ObjectType.ToLower() == "user")
{
var existing = AllUsers.FirstOrDefault(i => i.DistinguishedName == obj.DistinguishedName);
if (existing != null)
{
existing.ForwardedTo = obj.ForwardedTo;
existing.ForwardedToAd = obj.ForwardedToAd;
existing.ForwardedToDn = obj.ForwardedToDn;
existing.ForwardedToPk = obj.ForwardedToPk;
existing.ForwardedToDisplay = obj.ForwardedToDisplay;
existing.IsForwarded = obj.IsForwarded;
existing.DeliverAndRedirect = obj.DeliverAndRedirect;
existing.IsScheduled = obj.IsScheduled;
existing.TimeLeft = obj.TimeLeft;
}
else
{
//AllUsers.Add(obj);
App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
{
AllUsers.Add(obj);
});
}
if (obj.IsForwarded ?? false)
{
existing = ActiveForwards.FirstOrDefault(i => i.DistinguishedName == obj.DistinguishedName);
obj.TimeLeft = "";
var now = DateTime.Now;
var TimeRemaining = new TimeSpan?();
foreach (var schedule in ScheduledForwardsViewModel.ScheduledForwards)
{
if (schedule.DistinguishedName == obj.DistinguishedName)
{
if (schedule.StopJobStatus == "Scheduled")
{
if (TimeRemaining == null)
{
TimeRemaining = schedule.StopTime - now;
}
else
{
if (schedule.StopTime - now < TimeRemaining)
{
TimeRemaining = schedule.StopTime - now;
}
}
}
}
}
if (TimeRemaining != null)
{
var remaining = (TimeSpan)TimeRemaining;
var min = new int();
if (remaining.Seconds > 30)
{
min = remaining.Minutes + 1;
}
double m = remaining.Minutes / (double)60;
double hm = remaining.Hours + m;
double h = hm / 24;
double dh = remaining.Days + h;
if (remaining.Days > 0)
{
var daysleft = Math.Round(dh, 2);
var quarterRound = Math.Round(dh * 4, MidpointRounding.ToEven) / 4;
obj.TimeLeft = quarterRound + "d";
}
else if (remaining.Hours > 0)
{
var hoursleft = Math.Round(hm, 2);
var quarterRound = Math.Round(hm * 4, MidpointRounding.ToEven) / 4;
obj.TimeLeft = quarterRound + "h";
obj.TimeLeft = remaining.Hours + "h" + remaining.Minutes + "m";
}
else
{
if (min == 0)
{
obj.TimeLeft = "< 30s";
}
else
{
obj.TimeLeft = min + "m";
}
}
}
if (existing != null)
{
existing.ForwardedTo = obj.ForwardedTo;
existing.ForwardedToAd = obj.ForwardedToAd;
existing.ForwardedToDn = obj.ForwardedToDn;
existing.ForwardedToPk = obj.ForwardedToPk;
existing.ForwardedToDisplay = obj.ForwardedToDisplay;
existing.IsForwarded = obj.IsForwarded;
existing.DeliverAndRedirect = obj.DeliverAndRedirect;
existing.IsScheduled = obj.IsScheduled;
existing.TimeLeft = obj.TimeLeft;
}
else
{
//ActiveForwards.Add(obj);
App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
{
ActiveForwards.Add(obj);
});
}
}
}
else if (obj.ObjectType.ToLower() == "group")
{
if (obj.IsForwarded ?? false)
{
var existing = AllGroups.FirstOrDefault(i => i.DistinguishedName == obj.DistinguishedName);
if (existing != null)
{
existing.ForwardedTo = obj.ForwardedTo;
existing.ForwardedToAd = obj.ForwardedToAd;
existing.ForwardedToDn = obj.ForwardedToDn;
existing.ForwardedToPk = obj.ForwardedToPk;
existing.ForwardedToDisplay = obj.ForwardedToDisplay;
existing.IsForwarded = obj.IsForwarded;
existing.DeliverAndRedirect = obj.DeliverAndRedirect;
existing.IsScheduled = obj.IsScheduled;
existing.TimeLeft = obj.TimeLeft;
}
else
{
//AllGroups.Add(obj);
App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
{
AllGroups.Add(obj);
});
}
}
}
else if (obj.ObjectType.ToLower() == "contact")
{
if (obj.IsForwarded ?? false)
{
var existing = AllContacts.FirstOrDefault(i => i.DistinguishedName == obj.DistinguishedName);
if (existing != null)
{
existing.ForwardedTo = obj.ForwardedTo;
existing.ForwardedToAd = obj.ForwardedToAd;
existing.ForwardedToDn = obj.ForwardedToDn;
existing.ForwardedToPk = obj.ForwardedToPk;
existing.ForwardedToDisplay = obj.ForwardedToDisplay;
existing.IsForwarded = obj.IsForwarded;
existing.DeliverAndRedirect = obj.DeliverAndRedirect;
existing.IsScheduled = obj.IsScheduled;
existing.TimeLeft = obj.TimeLeft;
}
else
{
//AllContacts.Add(obj);
App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
{
AllContacts.Add(obj);
});
}
}
}
else
{
throw new NotImplementedException();
}
}
}
RunOnce = true;
}
Hello,
I'm having trouble getting my class to call a Moq'd method. My situation is thus:
public class ResetPasswordsTask : IRefreshTimeTask
{
public long ExecutionId => 2100;
public bool Enabled => true;
public Dictionary<string, object> Params { get; set; }
public KeyValuePair<string, Type>[] RequiredParams => new[]
{
new KeyValuePair<string, Type>("targetConfigs", typeof(InMemoryConfiguration))
};
public ILogger Logger { get; set; }
internal IConfiguration SandboxConfig;
internal IPartnerService PartnerService;
internal ISalesForceBulkDataInserter DataInserter;
public void Execute()
{
SandboxConfig = (IConfiguration)Params["targetConfigs"];
PartnerService = Login.SalesforceApiLogin(true, SandboxConfig);
DataInserter = new SalesForceBulkDataInserter();
//InitialiseImapClient();
// Retrieve users
var users = TestAutomation.Salesforce.Pages.Base.User.GetUsers(PartnerService, SandboxConfig.Refresh_Usernames);
// Upsert emails
var emailUpsertResults = UpsertEmails(users, SandboxConfig.IMAP_Email);
// Hit mailbox and visit confirmation links
var activatedUsers = emailUpsertResults.Values.Where(r => r.Status == EmailResetStatusEnum.Success).Select(r => r.User).ToList();
var confirmationLinkResults = ConfirmEmailChanges(activatedUsers);
// Upsert passwords
// All the users, except those for whom email update failed
var passwordUpdateUsers = users.Except(confirmationLinkResults.Values.Where(r => !r.Success).Select(r => r.User)).ToList();
var passwordUpsertResults = UpsertPasswords(passwordUpdateUsers);
// Hit mailbox for new tokens
var completeResetResults = RetrieveTokens(passwordUpsertResults.Values.Where(r => r.Success));
var output = string.Join("\n", completeResetResults.Values.Where(c => c.Success).Select(result => string.Join("\t", new List<string> { result.User.Username, result.Password, result.SecurityToken })));
//Logger.Info(output);
Logger.Info(System.IO.Path.GetDirectoryName("."));
Logger.Info(System.Reflection.Assembly.GetExecutingAssembly().Location);
OpenSslEncryption.EncryptToFile(SandboxConfig.EncryptionPassword, "passwords.enc", output);
}
// Results are keyed by username
internal IDictionary<string, EmailResetResult> UpsertEmails(ICollection<User> users, string newEmail)
{
var results = users.ToDictionary(u => u.Username, u => new EmailResetResult(u));
Logger.Info($"Updating emails for {users.Count} users");
foreach (var user in users)
{
Logger.Info($"updating email for {user.Username} users");
var result = results[user.Username];
// Prevent upserting the profile
if (user.ProfileId != null)
{
Logger.Info("Preventing profile upsert");
user.ProfileId.SalesForceId = user.ProfileId.OriginId;
}
// If the user has had their email set to what we're setting now, they can be skipped
if (user.Email.Equals(newEmail, StringComparison.InvariantCultureIgnoreCase) && user.IsActive)
{
Logger.Info($"User {user.Username} has their email set to {newEmail}; skipping");
result.Status = EmailResetStatusEnum.Skipped;
continue;
}
// Otherwise, set the email and carry on
user.Email = newEmail;
user.IsActive = true;
// dataInserter.Upsert won't produce errors individually, and instead only log faulty upserts
try
{
DataInserter.Upsert(new List<User> { user });
Logger.Info($"Activated user {user.Username}");
result.Status = EmailResetStatusEnum.Success;
}
catch (Exception e)
{
var error = $"Failed to update the email for user {user.Username} to {newEmail}. Error details: {e}";
Logger.Error(TestAutomation.Framework.Core.Logger.Logger.FormatJson(error));
result.ErrorMessage = e.ToString();
result.Status = EmailResetStatusEnum.Failure;
}
}
return results;
}
internal IDictionary<string, Result> ConfirmEmailChanges(ICollection<User> users)
{
var results = users.ToDictionary(u => u.Username, u => new Result(u));
// Ran as a task to have a timeout
Task.Run(() => ConfirmEmailChangesTask(results, users)).Wait(TimeSpan.FromMinutes(users.Count * 5));
return results;
}
internal void ConfirmEmailChangesTask(IDictionary<string, Result> results, ICollection<User> users)
{
var remainingUsers = new HashSet<User>(users);
while (true)
{
// Wait a bit; either for the emails to come in, or to give the webserver breathing room
Thread.Sleep(new TimeSpan(0, 0, 15));
Logger.Info($"Opening mailbox for {SandboxConfig.IMAP_Email}");
using (var imapClient = CreateImapClient())
{
var messages = imapClient.SearchMessages(SearchQuery.NotSeen.And(SearchQuery.DeliveredAfter(DateTime.Now.AddHours(-1))));
Logger.Info($"Found {messages.Count} messages");
var remainingUsersCopy = new HashSet<User>(remainingUsers);
Logger.Info($"Attempting to confirm emails for {remainingUsers.Count} users");
foreach (var user in remainingUsersCopy)
{
Logger.Info("Attempting to confirm email change for " + user.Username);
foreach (var message in messages.Where(m => m.MimeMessage.TextBody.Contains(user.Username) && m.MimeMessage.Subject.Contains("Sandbox: Finish changing your Salesforce")))
{
Logger.Info("Message found");
var confirmLink = GetEmailConfirmationLink(message);
if (confirmLink == null) continue;
// Visit the URL
var request = WebRequest.Create(confirmLink);
request.Timeout = (int)TimeSpan.FromSeconds(20).TotalMilliseconds;
var result = results[user.Username];
try
{
using (var response = (HttpWebResponse)request.GetResponse())
{
var statusCode = response.StatusCode;
if (statusCode != HttpStatusCode.OK)
{
var error = $"Failed to load the email change confirmation link: {confirmLink}. HTTP Response: ({statusCode})";
Logger.Error(TestAutomation.Framework.Core.Logger.Logger.FormatJson(error));
result.Success = false;
result.ErrorMessage = error;
}
}
}
catch (WebException e)
{
Logger.Error($"Request failed: {e.Message}\nWill retry later");
continue;
}
result.Success = true;
remainingUsers.Remove(user);
imapClient.MarkAsRead(message);
//Break down mailbox checks
Thread.Sleep(new TimeSpan(0, 0, 1));
}
}
}
if (!remainingUsers.Any())
break;
}
}
#region MailboxInteraction
internal static string GetEmailConfirmationLink(Message message)
{
// Extract confirmation URL
var confirmLinkMatch = Regex.Match(message.MimeMessage.TextBody, #"([a-z]+:\/\/.*\.salesforce\.com\/\S*)");
return !confirmLinkMatch.Success ? null : confirmLinkMatch.Groups[1].Value;
}
internal static string GetSecurityToken(Message message)
{
var tokenMatch = Regex.Match(message.MimeMessage.TextBody, #"Security token \(case-sensitive\): (?<token>\w+)");
return !tokenMatch.Success ? null : tokenMatch.Groups[1].Value;
}
internal virtual IMailClient CreateImapClient()
{
return new IMAPClient(SandboxConfig.IMAP_Username, SandboxConfig.IMAP_Password, SandboxConfig.IMAP_URL);
}
#endregion
}
Test class:
[TestFixture]
public class WhenResettingUserPasswords
{
private const string ConfirmationLink = "test://testdomain.salesforce.com/test/";
[OneTimeSetUp]
public void WebRequestSetup()
{
WebRequest.RegisterPrefix("test", TestableWebRequestCreateFactory.GetFactory());
var uri = new Uri("test://testdomain.salesforce.com/test/");
var expectedRequest = new TestableWebRequest(uri);
expectedRequest.EnqueueResponse(HttpStatusCode.OK, "Success", "Even more success!", false);
TestableWebRequestCreateFactory.GetFactory().AddRequest(expectedRequest);
}
private static SetupBag Setup()
{
var bag = new SetupBag
{
Logger = new InMemoryLogger(),
EmailConfirmationLink = ConfirmationLink,
SecurityToken = "TheSecurityToken",
Environment = "EnvName",
EnvironmentUrl = "http://aaa.bbb.ccc/",
User = new User
{
IsActive = false,
Username = "joe.bloggs#company.com",
Email = "joe.bloggs=company.com#example.com",
OriginId = "ABCDEFGHIJKLMNO"
}
};
var task = new Mock<Tasks.ResetPasswordsTask>(MockBehavior.Strict) { CallBase = true };
task.Object.Logger = bag.Logger;
var confirmMessage = new Message
{
UID = new UniqueId(0),
MimeMessage = new MimeMessage
{
Subject = "Sandbox: Finish changing your Salesforce",
Body = new TextPart("plain") { Text = "Confirm email change for joe.bloggs#company.com: " + bag.EmailConfirmationLink }
}
};
var tokenMessage = new Message
{
UID = new UniqueId(1),
MimeMessage = new MimeMessage
{
Subject = "Sandbox: Your new Salesforce security token",
Body = new TextPart("plain") { Text = "New security token for joe.bloggs#company.com: " + bag.SecurityToken }
}
};
var mailClientMock = new Mock<IMailClient>(MockBehavior.Strict);
mailClientMock.Setup(m => m.SearchMessages(It.IsAny<SearchQuery>())).Returns(new List<Message> { confirmMessage, tokenMessage });
task.Setup(t => t.CreateImapClient()).Returns(() => mailClientMock.Object);
var dataInserterMock = new Mock<ISalesForceBulkDataInserter>();
dataInserterMock.Setup(m => m.Upsert(It.IsAny<List<User>>(), false));
var config = new InMemoryConfiguration
{
IMAP_Email = "test.email#company.com"
};
task.Object.SandboxConfig = config;
bag.Task = task;
return bag;
}
[Test]
public void UpsertEmailsTest()
{
var bag = Setup();
var task = bag.Task;
var output = task.Object.ConfirmEmailChanges(new[] { bag.User });
Assert.IsTrue(output.ContainsKey(bag.User.Username));
Assert.IsTrue(output[bag.User.Username].Success);
Assert.IsEmpty(output[bag.User.Username].ErrorMessage);
Assert.AreEqual(task.Object.SandboxConfig.IMAP_Email, output[bag.User.Username].User.Username);
}
}
Now, the result is that calling task.Object.ConfirmEmailChanges() raises an exception about arguments to new IMAPClient() being null, which should not have been called at all. I could not find anything obviously wrong, but that might just be because I'm not familiar enough with Moq or the way my codebase does testing.
Yes, I know I'm mocking a class I want to test. I know it's a bad idea, and I'm aiming for a redesign. I cannot inject IMailClient as a dependency because it needs to be instantiated anew frequently. I have also seen this sort of technique used in our codebase, and it seemed to work.
If you are mocking a concrete class and not an interface then you must make the method you want to mock virtual.
Edit: in your new code the method is also marked as internal. See this question.
You need to declare your method as virtual or Moq is unable to override it. C# is not like Java - only members that are specifically marked are able to be overridden.
EDIT
The code you've posted should work - here is a Linqpad MVCE I generated based on your code, demonstrating that it works correctly. Therefore, you will need to post your actual code, as it has a bug in it.
void Main()
{
// Arrange
var foo = new Mock<Foo> { CallBase = true };
var bar = new Mock<IBar>();
bar.Setup(b => b.Value).Returns(2);
// setup an IBar mock
foo.Setup(f => f.CreateBar()).Returns(bar.Object);
// Act
var results = foo.Object.DoStuff();
results.Dump(); // Prints "2"
}
public class Foo
{
public int DoStuff()
{
var bar = CreateBar();
return bar.Value;
}
public virtual IBar CreateBar()
{
return new RealBar();
}
}
public interface IBar
{
int Value { get;}
}
public class RealBar : IBar
{
public int Value
{
get { return 1; }
}
}
We are using entity framework codefirst approach
I am new to entity framework and I am facing error while trying to do "New transaction is not allowed because there are other threads running in the session.
public class DatabaseBackup : IDataBackup
{
private readonly IMonarchDbContext m_db;
public DatabaseBackup(IMonarchDbContext podb)
{
if (podb == null)
throw new ArgumentNullException("podb");
m_db = podb;
}
public DBBackupHistory GetLatestBackupHistory(DBBackupFrequency backupFrequency = DBBackupFrequency.Periodic)
{
DBBackupHistory result = null;
// get the backup history of the given backuptype and populate the objects
var configId = m_db.DBBackupConfigurations.Where(c => c.ScheduleType == (int)backupFrequency && c.BackupStatus == 1).Distinct().Select(c => c.ConfigurationId).DefaultIfEmpty(-1).First();
if (configId > 0)
{
result = m_db.DBBackupHistorys.Where(b => b.Status == 1 && b.ConfigurationId == configId).OrderByDescending(lb => lb.BackupDatetime).FirstOrDefault();
}
return result;
}
public IEnumerable<DBBackupConfiguration> GetAllConfiguration()
{
var result = m_db.DBBackupConfigurations.Where(c => c.BackupStatus == 1).OrderByDescending(c => c.ConfigurationId);
return result;
}
public void Backup(DBBackupConfiguration config, int fileIndex)
{
Console.WriteLine("Running DB Backup type {0} to device {1}", (DBBackupType)config.BackupType, fileIndex);
m_db.StoredProc.SPBackup(config, fileIndex);
}
I am calling the below methods in another class as follows
private readonly IDataBackup m_dataBackup;
public int PerformBackup(int defaultPollIntervalInMinutes = 15)
{
// polling interval in Minutes
int pollInterval = defaultPollIntervalInMinutes;
int fileIndex = getCurrentDumpFileIndex();
// check for the backup configuration
var configurations = m_dataBackup.GetAllConfiguration();
foreach (var config in configurations)
{
var lastBackup = m_dataBackup.GetLatestBackupHistory(DBBackupFrequency.Weekly);
if (lastBackup == null)
{
m_dataBackup.Backup(config, fileIndex + 1);
break;
}
Here is the Db Context class is as below
public class MonarchDbContext:DbContext,IMonarchDbContext
{
private IStoredProcedure m_storedProc;
private static object m_dbIntializerSet;
public MonarchDbContext(string nameOrConnectionString)
: base( nameOrConnectionString )
{
//-- Set the DB initializer only once.
System.Threading.LazyInitializer.EnsureInitialized( ref m_dbIntializerSet,()=>{
Database.SetInitializer<MonarchDbContext>(null);
//-- Give debug builds a chance to overwrite the above.
_SetInitializerForDebugBuilds();
return new object();
});
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
var csb = new SqlConnectionStringBuilder( this.Database.Connection.ConnectionString );
csb.MultipleActiveResultSets = true;
this.Database.Connection.ConnectionString = csb.ToString();
var objectContext = ( this as IObjectContextAdapter ).ObjectContext;
objectContext.CommandTimeout = 3600;
}
#region Public "Tables"
public IDbSet<DBBackupConfiguration> DBBackupConfigurations { get; set; }
public IDbSet<DBBackupHistory> DBBackupHistorys { get; set; }
public IStoredProcedure StoredProc
{
get
{
return System.Threading.LazyInitializer.EnsureInitialized(ref m_storedProc, () => new BackupStoredProc(this.Database));
}
}
#endregion
please let me know how can i solve the issue.
I found the issue
I need to add toList() at the end of the Linq code and it just worked for me.
public IEnumerable<DBBackupConfiguration> GetAllConfiguration()
{
var result = m_db.DBBackupConfigurations.Where(c => c.BackupStatus == 1).OrderByDescending(c => c.ConfigurationId).ToList();
return result;
}
Just add the List to Ienumerbale types
I have a method called get Data which executes my SQL and returns some rows of ContactLists containing Aggregated Labels.At the moment this method is in my code behind and would like to move it to a separate Data Access class. I would appreciate your assistance. Thanks!
Is normal, if i understand your code, you do this operation after ContactList initialization:
contactList.Labels = new ObservableCollection<Label>()
{
new Label() {
Name = dr["LABEL_NAME"].ToString(),
Count = Convert.ToInt32(dr["LABEL_COUNT"])
}
};
For each ContactList is always added one item, you will do something like this:
contactList.Labels = new ObservableCollection<Label>();
foreach(var item in <yourLabelDataSource>)
contactList.Labels.Add(new Label(...));
The solution is like this:
Dictionary<int, ContactList> myContactDictionary = new Dictionary<int, ContactList>();
using (DB2DataReader dr = command.ExecuteReader())
{
while (dr.Read())
{
int id = Convert.ToInt32(dr["CONTACT_LIST_ID"]);
if (!myContactDictionary.ContainsKey(id))
{
ContactList contactList = new ContactList();
contactList.ContactListID = id;
contactList.ContactListName = dr["CONTACT_LIST_NAME"].ToString();
contactList.Labels = new ObservableCollection<Label>()
{
new Label()
{
Name = dr["LABEL_NAME"].ToString(),
Count = Convert.ToInt32(dr["LABEL_COUNT"])
}
};
myContactDictionary.Add(id, contactList);
}
else
{
//Add new label because CONTACT_LIST_ID Exists
ContactList contactList = myContactDictionary[id];
contactList.Labels.Add(
new Label()
{
Name = dr["LABEL_NAME"].ToString(),
Count = Convert.ToInt32(dr["LABEL_COUNT"])
}
);
}
}
}
Ben, for your last question you can use this solution:
else
{
//Add new label because CONTACT_LIST_ID Exists
ContactList contactList = myContactDictionary[id];
string name = dr["LABEL_NAME"].ToString();
var label = contactList.Labels.Where(l => l.Name == name).FirstOrDefault();
if( label != null )
label.Count += Convert.ToInt32(dr["LABEL_COUNT"]);
else
{
contactList.Labels.Add(
new Label()
{
Name = dr["LABEL_NAME"].ToString(),
Count = Convert.ToInt32(dr["LABEL_COUNT"])
}
);
}
I hope this code is readable and helpfulL!
}
This is other response:
Create and Object Model that can contain your required data:
public class DataResult
{
public ObservableCollection<AggregatedLabel> AggregatedLabels { get; set; }
public ObservableCollection<ContactList> ContactLists { get; set; }
}
You can build a method that return DataResult object, in your method (GetData()), you can valorize the two different properties (AggregatedLabels and ContactsList) with your DB Result. In the and you can return DataResult Object.
A little example here:
public DataResult GetData()
{
DataResult result = new DataResult();
result.AggregatedLabels = new ObservableCollection<AggregatedLabel>();
result.ContactLists = new ObservableCollection<ContactList>();
// Manipulate data result with your method logic like in this examle:
foreach(var something in dbResult)
{
ContactList cl = new ContactList() {
//Binding from something
}
result.ContactLists.Add(cl);
}
return result; //return your Object Model with required Data!
}
I hope it is conceptually clear