Any solution to this problem, whether provided by FRAME or implemented manually, would require storing the number of items currently in the map in some other storage location, since you would never want to figure out how many items are there by iterating through all the storage items.
So you could have:
#[pallet::storage]
pub type Info<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Twox64Concat,
T::ContextId,
Data,
>;
#[pallet::storage]
pub type CountForInfo<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
u32,
ValueQuery,
>;
And then, before you do any insert
you check this value:
fn try_insert_info(who: T::AccountId, context: T::ContextId, data: Data) -> DispatchResult {
let already_exists = Info::<T>::contains_key(who, context);
if !already_exists {
let count = CountForInfo::<T>::get(who);
if count < LIMIT {
CountForInfo::<T>::insert(who, count + 1);
} else {
return Err(Error::<T>::TooManyItems.into())
}
}
Info::<T>::insert(who, context, data);
Ok(())
}
You will notice this function, versus a normal insert
is way more expensive, since you need to check if this is a brand new storage item, or one that already exists, and if it is new, then you need to check and update the count before inserting the new storage item.
It will almost always be more efficient for you to track this yourself rather than building an abstraction over this behavior, since you will be able to avoid writing / modifying the count storage item in certain situations.
For example, imagine you do some code which removes an existing key, and then inserts a new key. In this case, you can likely avoid updating or checking the count at all, which can save you multiple lines of storage access.
The only time it makes sense to use an abstraction like this is when the code is so complex that avoiding any manual tracking errors outweighs the additional overhead, but even still, probably best to track it manually.