APEX CPU Limit. Use maps instead of lists in apex class - apex

I have created a flow that based on some requirements, finds a list of accounts. The flow then passes this list of accounts and a new account owner (id) to an apex class. The apex class then updates all the accounts with this new owner and also updates each opportunity and each task listed under each activity with the same account owner. This was working fine till I stated updated large number of accounts. I am now hitting APEX CPU Limit. My apex class is shown below. I think I need to use Maps, but I dont know how to. Any ideas on how to rewrite this code to make it more efficient so that I dont run into APEX CPU limits? Thank you
public class LCD_AccountinCounty {
#InvocableMethod(label='Account Owner Update flow' Description='Update Account Object with new owner')
public static void updateAccountOwner(List<FlowDetail> flowdetails) {
List<Account> accList = new List<Account>();
for(FlowDetail fd : flowdetails){
for(Account acct : fd.accounts){
acct.OwnerId = fd.newAccountOwnerName;
acc.Salesperson__c = SalespersonName;
accList.add(acct);
}
}
update accList;
List<Opportunity> opportunities = new List<Opportunity>();
for(Opportunity opp: [SELECT Id, OwnerId, AccountId, Account.OwnerId FROM Opportunity WHERE AccountId IN :accList and StageName !='Closed']){
opp.OwnerId = opp.Account.OwnerId;
opportunities.add(opp);
}
update opportunities;
List<Task> activities = new List<Task>();
for(Task t: [SELECT Id, OwnerId, WhatId, Account.OwnerId FROM Task WHERE WhatId IN :accList]){
t.OwnerId = t.Account.OwnerId;
activities.add(t);
}
update activities;
}
public with sharing class FlowDetail{
#InvocableVariable
public List<Account> accounts;
#InvocableVariable
public String newAccountOwnerName;
#InvocableVariable
public String SalespersonName;
}
}

Do you have triggers on these objects that are firing some additional logic?
You can't pass a wrapper class to a batch class.
You can however pass in complex data types using the Queueable interface.
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_queueing_jobs.htm
You could move your above code into a Queueable class and enqueue it like below:
public class LCD_AccountinCounty {
#InvocableMethod(label='...' Description='...')
public static void updateAccountOwner(List<FlowDetail> flowdetails) {
AsyncExecutionExample a = new AsyncExecutionExample(flowdetails);
System.enqueueJob(a);
}
}
public class AsyncExecutionExample implements Queueable {
public LCD_AccountinCounty.FlowDetail flowdetails;
public AsyncExecutionExample(LCD_AccountinCounty.FlowDetail flowdetails){
this.flowdetails = flowdetails;
}
public void execute(QueueableContext context) {
*Old updateAccountOwner code goes here...*
}
}

Related

Update a given list and its child objects using Salesforce Apex

Update to question:
So I have created an invokable variable/method where the flow is passing the following parameters:
public static void updateAccountOwner(List<TransferDetail> transferDetails) {
for(TransferDetail td : transferDetails) { //THIS IS WHERE I AM COMPLETELY UNSURE ABOUT, AND I WAS TOLD A MAP WOULD BE BETTER TO BULKFY?
for(Account a : td.accounts){
// Account a = new Account(Id = td.a.accountId, AccountOwner__c = td.a.newOwnerId);
// accounts.add(a);
// }
}
}
public with sharing class Request{
#InvocableVariable
public List<Account> accounts;
#InvocableVariable
public String newAccountOwnerName;
#InvocableVariable
public String oldAccountOwnerName;
}
So each account in this list of accounts has opportunities of its own. But each account in the list will be updated with the same new owner, and also each opportunity will be transfered to the same owner
I have created a lightning flow where I have gotten a list of accounts based on criteria/information input by a user. I am trying to take this list of accounts and update its owner values and all the child contacts for each account in the list. So it is a list within a list. I know I have to use map for it but I am not sure what I am doing wrong/how to get started. Here is what I have:
public with sharing class LPP_UpdateOwner {
public static void updateAccountOwner(List<List<Account>> accounts, newOwnerId){ //accounts would be the input list from the flow and is a list/colection of accounts that the flow queried. newOWNERID would be the name the user typed in the flow
List<Account> aUpdated = new List<Account>(); //should this be a map??
for( Account a: accounts){
a.AccountOwner__c = newOwnerId;
aUpdated.add(a)
}
update aUpdated;
Map<Id, Opportunity> oppList = new Map<Id, Opportunity>([SELECT Id, Name from Opportunity where AccoundId IN :accounts.keySet() and Stage !='Closed Won']);
List<Opportunity> oppToUpdate = new Opportunity();
for (Opportunity opp :oppList.values()){
opp.OwnerId = aUpdated.AccountOwner__c ? not sure what to put here(opportunity owner and account owner ha to be same, which is newNameId
oppToUpdate.add(opp);
}
update OpptoUpdate;
So I am not sure if this is correct or not. Basically, I am trying to update all the accounts and each account's opportunity with a new name thats provided. Also, The reason why I am trying to use Maps is to avoid CPU Time limit because doing this in flow and process builder is casuing CPU time out errors.
Thank you
//I would recommend renaming this class, "Request" is far too generic...
public class Request {
#InvocableVariable
public List<Account> accounts;
#InvocableVariable
public String newAccountOwnerName; //Is this the name of a user or the actual user Id???
#InvocableVariable
public String oldAccountOwnerName; //This property isn't needed...
}
public class InvocableLPPOwnerUpdate {
#InvocableMethod(label='Assign New Owner' description='Will assign new ownership for the given account')
public static void updateAccountOwner(List<Request> requests) {
//You don't need a map in this situation, this approach is bulkified already because once you update
//the owner on the Account, you can just read the owner from there when updating the opportunities
List<Account> accounts = new List<Account>();
for(Request req : requests) {
for(Account acct : req.accounts) {
acct.AccountOwner__c = req.newAccountOwnerName;
accounts.add(acct);
}
}
update accounts;
List<Opportunity> opportunities = new List<Opportunity>();
for (Opportunity opp : [SELECT Id, Name, Account.AccountOwner__c FROM Opportunity WHERE AccoundId IN :accounts and Stage !='Closed Won']) {
//No Map needed because we can get the correct owner from the linked Account that was already updated above
opp.OwnerId = opp.Account.AccountOwner__c;
opportunities.add(opp);
}
update opportunities;
}
}

How to retrieve new ID in EF Core using UoW pattern

I'm having trouble retrieving the ID of newly added object in EF Core using the UoW pattern. I have this service:
public class OrderService : IOrderService
{
private IUnitOfWork _uow;
private IOrderRepository _orderRepository;
private IPaymentRepository _paymentRepository;
public OrderService(IUnitOfWork uow,
IOrderRepository orderRepository,
IPaymentRepository paymentRepository)
{
_uow = uow;
_orderRepository = orderRepository;
_paymentRepository = paymentRepository;
}
public int CreateOrder(Logic.Order order)
{
var id = _orderRepository.CreateOrder(order);
var payment = new Data.Payment();
payment.OrderId = id; // at this point, this is -2147353458 something
_paymentRepository.CreatePayment(payment);
// committed at this point but I can't get id unless I re-query
_uow.Commit();
// this is still -2147353458
return id;
}
}
So CreateOrder just adds a new order and then the newly generated ID is returned and used by the Payment object in CreatePayment. The problem with this since after adding, it is not committed yet so EF Core generates a temp id (something like -2147483324) so this is what I get. I then pass this to payment but this part is ok since I think EF is tracking it. The problem is what I return to the UI.
The service is called by the UI and after comitting, I can't get the ID. That's been my problem for hours now.
I've recently came across the same problem as well. Am here just to share my solution for reference.
Rather than to committing the transaction for the Id, you could try utilizing EF relationships.
Ex: the payment and order Model
public class Order
{
public int Id{ get; set; }
public Payment Payment { get; set; }
}
public class Payment
{
public int Id{ get; set; }
public int OrderId{ get; set; }
[ForeignKey("OrderId")]
public Order Order { get; set; }
}
Then in your transaction, you could simply assign the order to payment, EF will automatically insert the created Order Id to payment upon committing the transaction :
public int CreateOrder(Logic.Order order)
{
_orderRepository.CreateOrder(order);
var payment = new Data.Payment();
payment.Order = order;
_paymentRepository.CreatePayment(payment);
_uow.Commit();
return order.id;
}
You need to create an abstract method just like that "void Commit(EntityBase entity)" in your Uow, inside the method call your saveChanges this way you ensure that memory address is the same, so outside of the method you are able to access the property Id, be careful if you're using some Mapper because this may change you Memory Address. Make sure that your are using mapper only after Call UoW.Commit!
public class EntityBase
{
public int Id {get;set}
}
public class AnyOtherEntity : EntityBase
{
}
public void Commit(EntityBase entity)
{
yourContext.SaveChanges(entity);
}
Uow.Commit(AnyOtherEntity);
AnyOtherEntity.Id;
There is no option except commiting the transaction if you want to retrieve the generated unique Id immediately. Also,
// this is still -2147353458
return id;
You can't expect to be changed primitive type after commit. You should get it by order.Id, because after the transaction committed the EF will update entity because EF is tracking the entity.
public int CreateOrder(Logic.Order order)
{
_orderRepository.CreateOrder(order);
// commit the transaction
_uow.Commit();
var payment = new Data.Payment();
// Get the id of inserted row
payment.OrderId = order.Id;
_paymentRepository.CreatePayment(payment);
_uow.Commit();
return order.Id;
}

When does an Entity show up in the collection of the Model

I use entity framework and have a set of users:
public class DbModel : DbContext
{
public DbSet<User> Users { get; set; }
I add a User like so:
User UserOne = new User();
model.Users.Add( UserOne );
I request the users count:
int userCount = model.Users.Count();
userCount is "0" I would expect "1". Adding DetectChanges doen't help.
After "model.SaveChanges()" the Count = 1, but that is to late I need to combine the in memory stuff with the DB stuff for validation. Is there a way to do this?
SOLUTION
Using the answer of Erik Philips I wrote the following extension method for the DbSet
public static class DBSetExtentions
{
public static IEnumerable<T> AllMembers<T>(
this DbSet<T> target,
Func<T, bool> selection
) where T : class
{
return target.Local.Where(selection).Union(target.Where(selection));
}
}
it allows me to do selections an validations accross all entities like:
private void ValidateEmail(ValidationDto validationDto)
{
int usersWithSameEmail =
validationDto.Model.Users.AllMembers(
x => x.EmailAddress.Equals( EmailAddress ) ).Count();
if (usersWithSameEmail > 1)
{
validationDto.Result.Add(new ValidationResult("Email address is in use"));
}
}
You can query the client side of items (committed and uncomitted) to your data storage by using Local.
var count = model.Users.Local.Count();
One caveat is that this is only the local representation of Users. Meaning it could contain a partial amount of users from the database (changed and/or unchanged), and new users you've created and not saved.
Interesting Article - Using DbContext in EF 4.1 Part 7: Local Data

Entity persitance inside Domain Events using a repository and Entity Framework?

I am delving into domain events and need some advice about persisting updates to an entity for history reasons. My example deals with a User entity and Signing In:
public class UserService
{
private UserRepository _repository;
public UserService()
{
_repository = new UserRepository();
}
public User SignIn(string username, string password)
{
var user = _repository.FindByUsernameAndPassword(username, password);
//As long as the found object is valid and an exception has not been thrown we can raise the event.
user.LastLoginDate = DateTime.Now;
user.SignIn();
return user;
}
}
public class User
{
public User(IEntityContract entityContract)
{
if (!entityContract.IsValid)
{
throw new EntityContractException;
}
}
public Guid Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public DateTime LastLoginDate { get; set; }
public void SignIn()
{
DomainEvent.Raise(new UserSignInEvent() {User = this});
}
}
public class UserSignInEvent : IDomainEvent
{
public User User { get; set; }
}
public class UserSignInHandler : Handles<UserSignInEvent>
{
public void Handle(UserSignInEvent arguments)
{
//do the stuff
}
}
So where I have the do the stuff, I want to update the User object LastLoginDate and possibly log the date and time the user logged in for historical reasons.
My question is, would I create a new instance of my repository and context to save the changes in the handler or pass something into the Event? This is what I am struggling with right now.
So where I have the do the stuff, I want to update the User object LastLoginDate and possibly log the date and time the user logged in for historical reasons.
Remembering last login date should be concern of user itself.
You already have nice extension point - user has signIn method.
My question is, would I create a new instance of my repository and context to save the changes in the handler or pass something into the Event?
User shouldn't know anything about entity framework.
Therefore - User.Events shouldn't know anything either.
Domain event handlers shouldn't know too.
Those handlers that live "outside" (e.g. in application layer) are allowed to.
But they would figure out entity framework context from elsewhere and not from user or events if necessary.
As I see it - events here are necessary for logging functionality only.
I would write something like this:
public class LoginService{
private Users _users;
public LoginService(Users users){
_users = users;
}
public User SignIn(string username, string password){
var user = _users.ByUsernameAndPassword(username, password);
user.SignIn();
return user;
}
}
public class User{
public DateTime LastLoginDate { get; set; }
public void SignIn(){
LastLoginDate = DateTime.Now;
Raise(new SignedIn(this));
}
public class SignedIn:DomainEvent<User>{
public SignedIn(User user):base(user){}
}
}
//outside of domain model
public class OnUserSignedIn:IEventHandler<User.SignedIn>{
public void Handle(User.SignedIn e){
var u=e.Source;
var message="User {0} {1} logged in on {1}"
.With(u.Name,u.LastName,u.LastLoginDate);
Console.WriteLine(message);
}
}
Bad thing about this code is that service method is command and query simultaneously
(it modifies state and returns result).
I would resolve that with introducing UserContext which would be notified that user has signed in.
That would make need for returning signed in user unnecessary,
responsibility of serving current user would be shifted to UserContext.
About repository and updating Your user - I'm pretty sure entity framework is smart enough to know how to track entity state changes. At least in NHibernate - only thing I'm doing is flushing changes when httprequest finishes.

MVVM: Delete a CustomerViewModel, but how to get the Customer model inside it?

I have a list of CustomerViewModels in a ComboBox. The selected CustomerViewModel I want to delete and also the Customer wrapped inside it to remove it from the repository.
But how can I access the Customer model inside the CustomerViewModel?
Just a suggestion, make your collection of customerviewmodels an ObserableCollection of CustomerViewModels.
what this buys you is a CollectionChanged Event that you could listen on with a delegate for changes to the collection ie deletion, so from there you could manipulate you model accordingly
http://msdn.microsoft.com/en-us/library/ms653375(VS.85).aspx
perhaps something like
public class CustomersViewModel: ViewModelBase
{
public ObservableCollection<CustomersViewModel> Customers { get; private set; }
public CustomersViewModel()
{
Customers = new ObservableCollection<CustomersViewModel>(GetCustomers());
Customers.CollectionChanged +=
(sender, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Remove)
{
foreach (CustomerViewModel customerViewModel in args.NewItems)
{
DeleteCustomer(customerViewModel.Customer);
}
}
};
}
private void DeleteCustomer(Customer customer)
{
// Call into your repo and delete the customer.
}
private List<CustomersViewModel> GetCustomers()
{
// Call into your model and return customers.
}
... ICommands ect...
}
You might have already access to the Customer inside CustomerViewModel (the VieModel needs to expose the properties of the Customer so the View can databind on them; I usually do it by exposing the Customer or a copy of it directly).
The point is that you should not delete the Customer yourself. That's what the ViewModel is for, to expose an ICommand that deletes the associated Customer. Depending on which MVVM framework you are using, look into DelegateCommand or another equivalent.
Your CustomerViewModel would have a
public ICommand DeleteCommand { get; private set; }
and your View would bind a CommandTarget (probably a Button) to this command. When the command is executed a private method of CustomerViewModel will be run, and you can delete the Customer from there without exposing the deletion mechanism to other parts of the code. For example:
public CustomerViewModel()
{
this.DeleteCommand = new DelegateCommand(this.ExecuteDeleteCommand);
}
private void ExecuteDeleteCommand()
{
// remove the Customer from the ObservableCollection of customers
// and also delete it from the database, or do anything else you want
}