Apex LINQ is a high-performance Salesforce LINQ library designed to work seamlessly with object collections, delivering performance close to native operations. For optimal results, refer to the guidelines in Apex CPU Limit Optimization.
Environment | Installation Link | Version |
---|---|---|
Production, Developer | ![]() |
ver 1.1.1 |
Sandbox | ![]() |
ver 1.1.1 |
Apex LINQ supports both sObject lists and custom object lists.
Use Q.of()
to operate on a list of sObjects. This is the most common use case. The following example simply returns the original list.
List<Account> accounts = [SELECT Name FROM Account];
List<Account> results = (List<Account>) Q.of(accounts).toList();
To use the library with custom classes, provide the custom class type as the second parameter to Q.of()
. The following example returns the original list.
List<Model> models = new List<Model> { m1, m2, m3 };
List<Model> results = (List<Model>) Q.of(models, Model.class).toList();
To filter records, implement the Q.Filter
interface.
- In the example below,
AnnualRevenue
is cast fromDecimal
toDouble
before comparison. - It is recommended to implement
Q.Filter
as an inner class, close to where it is used.
public class AccountFilter implements Q.Filter {
public Boolean matches(Object record) {
Account acc = (Account) record;
return (Double) acc.AnnualRevenue > 10000;
}
}
Apply the filter as shown below. This example returns all accounts with annual revenue greater than 10,000.
List<Account> accounts = [SELECT Name, Industry, AnnualRevenue FROM Account];
Q.Filter filter = new AccountFilter();
List<Account> results = (List<Account>) Q.of(accounts).filter(filter).toList();
To sort records, implement the Q.Sorter
interface. Sorting is CPU-intensive, so only sort when necessary.
public class AccountSorter implements Q.Sorter {
public Integer compare(Object arg1, Object arg2) {
Double revenue1 = ((Account) arg1).AnnualRevenue;
Double revenue2 = ((Account) arg2).AnnualRevenue;
if (revenue1 < revenue2) {
return -1;
} else if (revenue1 > revenue2) {
return 1;
} else {
return 0;
}
}
}
Apply the sorter as shown below. This example returns filtered accounts sorted in ascending order by annual revenue.
List<Account> accounts = [SELECT Name, AnnualRevenue FROM Account];
Q.Filter filter = new AccountFilter();
Q.Sorter sorter = new AccountSorter();
List<Account> results = (List<Account>) Q.of(accounts)
.filter(filter)
.sortBy(sorter)
.toList();
Apex LINQ provides several common slicing operations. When chained, each operation acts on the result of the previous one.
List<Account> accounts = [SELECT Name, Industry, AnnualRevenue FROM Account];
List<Account> results = (List<Account>) Q.of(accounts)
.skip(5) // Skip the first 5 elements and return the rest
.take(4) // Take the next 4 elements from the current result
.tail(3) // Take the last 3 elements from the current result
.slice(0, 2) // Select elements at positions 0 and 1 (zero-based, upper bound exclusive)
.toList();
To perform rollup operations, implement the Q.Rollup
interface. Specify which fields to use as rollup summary keys, and define the logic to compute summary values for each group.
public class AccountRollup implements Q.Rollup {
public List<Object> getKeys(Object record) {
Account acc = (Account) record;
return new List<Object>{ acc.Industry };
}
public Map<String, Object> summary(List<Object> mapKeys, List<Object> records) {
Map<String, Object> result = new Map<String, Object>();
Double sumRevenue = 0;
Double maxRevenue = 0;
for (Object record : records) {
Account acc = (Account) record;
sumRevenue += (Double) acc.AnnualRevenue;
if (acc.AnnualRevenue > maxRevenue) {
maxRevenue = acc.AnnualRevenue;
}
}
result.put('MaxRevenue', maxRevenue);
result.put('SumRevenue', sumRevenue);
result.put('AvgRevenue', sumRevenue / records.size());
return result;
}
}
Apply the rollup as shown below. The result is a list of Q.Aggregate
objects, which provide convenient access to the summary data.
List<Account> accounts = [SELECT Name, Industry, AnnualRevenue FROM Account];
Q.Filter filter = new AccountFilter();
Q.Rollup rollup = new AccountRollup();
List<Q.Aggregate> results = (List<Q.Aggregate>) Q.of(accounts)
.filter(filter).rollup(rollup).toList();
Integer INDUSTRY_INDEX = 0;
for (Q.Aggregate aggregate : results) {
String industry = (String) aggregate.getKeyAt(INDUSTRY_INDEX);
Double maxRevenue = (Double) aggregate.getValue('MaxRevenue');
Double sumRevenue = (Double) aggregate.getValue('SumRevenue');
Double avgRevenue = (Double) aggregate.getValue('AvgRevenue');
}
The diff operation is mainly used to compare the Trigger.new
and Trigger.old
lists to identify records that have changed. Diff compares two lists of equal length, matching records by their position in the list.
public class AccountDiffer implements Q.Differ {
public Boolean changed(Object arg1, Object arg2) {
Double revenue1 = ((Account) arg1).AnnualRevenue;
Double revenue2 = ((Account) arg2).AnnualRevenue;
return revenue1 != revenue2;
}
}
Apply the differ as shown below. You can compare Trigger.new
with Trigger.old
or vice versa, depending on which set of changed records you want to retrieve. You can also further filter the changed records.
Q.Differ differ = new AccountDiffer();
Q.Filter filter = new AccountFilter();
List<Account> newList = (List<Account>) Q.of(Trigger.new).diff(differ, Trigger.old)
.filter(filter).toList();
List<Account> oldList = (List<Account>) Q.of(Trigger.old).diff(differ, Trigger.new)
.filter(filter).toList();
The toList()
method returns different types of results depending on whether a rollup operation is used:
- If a rollup is applied,
toList()
returns a list of aggregated results. Each entry contains the group keys and their corresponding summary values. - If no rollup is applied,
toList()
returns a list of the original records, which may be filtered or sorted as specified.
// With rollup
List<Q.Aggregate> results = (List<Q.Aggregate>) Q.of(accounts).rollup(rollup).toList();
// Without rollup
List<Account> results = (List<Account>) Q.of(accounts).filter(filter).toList();
To transform each record in a collection, implement the Q.Mapper
interface.
public class AccountMapper implements Q.Mapper {
public Object convert(Object record) {
Account acc = (Account) record;
return acc.Name;
}
}
Apply the mapper using toList(Q.Mapper, Type)
, providing the target type as the second parameter. For example, to obtain a list of account names:
Q.Filter filter = new AccountFilter();
Q.Mapper mapper = new AccountMapper();
List<String> results = (List<String>) Q.of(accounts)
.filter(filter).toList(mapper, String.class);
To accumulate results, implement the Q.Reducer
interface.
public class AccountReducer implements Q.Reducer {
public Object reduce(Object state, Object record) {
Double currentSum = (Double) state;
Account acc = (Account) record;
return currentSum + (Double) acc.AnnualRevenue;
}
}
Apply the reducer as shown below. For example, to calculate the total annual revenue of filtered accounts:
List<Account> accounts = [SELECT Name, Industry, AnnualRevenue FROM Account];
Q.Filter filter = new AccountFilter();
Q.Reducer reducer = new AccountReducer();
Double result = (Double) Q.of(accounts).filter(filter).reduce(reducer, 0.0);
Apache 2.0