Update a given list and its child objects using Salesforce Apex - 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;
}
}

Related

Javers - Can't track mongo updates inside a document

I am trying to keep a track of changes made on specific fields inside a document.
This is my user class. I want to keep a track of any change made in count using Javers.
#Document
public class User {
#Id
private String userId;
private String name;
private int count;
private Date creationDate = new Date();
private Map<String, String> userSettings = new HashMap<>();
}
I have added the following code to keep a track of whenever a new Object of User is created.
#Override public User addNewUser(User user) {
mongoTemplate.save(user);
javers.commit("Added", user);
return user;
}
This is the updateCount method using MongoTemplate:
#Override public boolean updateCount(String name) {
Query query = new Query();
query.addCriteria(Criteria.where("name").is(name));
query.addCriteria(Criteria.where("count").gt(0));
Update update = new Update();
update.inc("count", -1);
return Objects
.requireNonNull(reactiveMongoTemplate.updateFirst(query, update, User.class).block())
.getModifiedCount() == 1;
}
Now what I want to track is to commit some kind of change at the end of updateCount method call.
But these changes can't be committed unless I have an entity object. How do I go about this?
Javers is the object auditing framework, you can't track changed made by direct db updates in Javers. All you can do is to take some Java object (an entity), and call javers.commit() on it

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

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...*
}
}

Add related database entry in Azure Mobile Services controller

In my Azure Mobile Service I have a controller class UserController : TableController<User> and in it is a get method:
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<User> GetUser(string id)
{
return Lookup(id);
}
I want to record each time a user is accessed and so I add a simple type to the model:
public class UserVisit : Microsoft.WindowsAzure.Mobile.Service.EntityData
{
public string VisitingUser { get; set; }
public DateTime TimeOfVisit { get; set; }
}
and include the property public DbSet<UserVisit> UserVisits { get; set; } in my VCollectAPIContext : DbContext class (and update the database with a code-first migration).
To add a UserVisit to the database when a user id is queried I change my controller method to
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<SingleResult<User>> GetUser(string id)
{
var userVisit = new UserVisit { VisitingUser = id, TimeOfVisit = DateTime.UtcNow };
context.UserVisits.Add(userVisit);
await context.SaveChangesAsync();
return Lookup(id);
}
But the SaveChangesAsync fails with a System.Data.Entity.Validation.DbEntityValidationException. Digging around in the exception's EntityValidationErrors property I find that the problem is "The Id field is required."
That's a little odd. The Id field is one of the properties in the base-class Microsoft.WindowsAzure.Mobile.Service.EntityData that I would expect to be added automatically on insert. No matter, I can add it and several of the other base-class's properties thus:
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<SingleResult<User>> GetUser(string id)
{
var userVisit = new UserVisit { Id = Guid.NewGuid().ToString(), Deleted = false, VisitingUser = id, TimeOfVisit = DateTime.UtcNow, CreatedAt = DateTimeOffset.Now };
context.UserVisits.Add(userVisit);
await context.SaveChangesAsync();
return Lookup(id);
}
This time I get a System.Data.Entity.Infrastructure.DbUpdateException because we "Cannot insert the value NULL into column 'CreatedAt'". It was not null in the call to Add. So CreatedAt has been set to null somewhere outside my code and then the insert fails as a result!
I also tried setting up an EntityDomainManager<UserVisit> userVisitDomainManager; instance variable in the controller's initializer, and then rewriting my controller get method as
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<SingleResult<User>> GetUser(string id)
{
var userVisit = new UserVisit { VisitingUser = id, TimeOfVisit = DateTime.UtcNow };
await userVisitDomainManager.InsertAsync(userVisit);
return Lookup(id);
}
That fails with the same message, "Cannot insert the value NULL into column 'CreatedAt'"
How should I perform the seemingly simple task of inserting a related data item within my controller method?
The solution is likely similar to this answer. I'm guessing that your migration is not using the Mobile Services SqlGenerator so some of the custom SQL settings aren't getting applied. What that means is that:
Id doesn't get a default value of NEWID() -- this explains your "Id field is required" error.
CreatedAt doesn't get a default value of SYSUTCDATETIME() -- this, combined with the [DatabaseGenerated] attribute on EntityData.CreatedAt, explains the "NULL CreatedAt" error.
Try updating your migration according to the link above and see if that works for you.
To fix the problem of "The Id field is required" following brettsam's instructions.
Add this in your model:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[TableColumn(TableColumnType.Id)]
public new string Id { get; set; }
It will auto generate a GUID when you add an entity.

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

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
}