In the end, I decided to transfer the data in the inserted and deleted tables to the indexed table variables, and then work with them. Performance is not as good as it would be with CDC, but acceptable and linear, and time to market was much shorter. I wrote a code generator for generating triggers, a sample of which I included below:
IF TRIGGER_NESTLEVEL(OBJECT_ID('TR_su_type_code_audit_log')) > 1 RETURN DECLARE @user_key INT, @tp INT = 0 IF EXISTS(SELECT 1 FROM deleted) SET @tp += 1 IF EXISTS(SELECT 1 FROM inserted) SET @tp += 2 DECLARE @i TABLE (type_code_key int, audit_data VARCHAR(MAX), PRIMARY KEY (type_code_key)) DECLARE @d TABLE (type_code_key int, audit_data VARCHAR(MAX), PRIMARY KEY (type_code_key)) INSERT INTO @i SELECT type_code_key, (SELECT type_code_key, type_code_group, id, description, is_system_reserved, site_key, code_int, code_str, image_index, image_filename FOR XML RAW('audit')) FROM inserted INSERT INTO @d SELECT type_code_key, (SELECT type_code_key, type_code_group, id, description, is_system_reserved, site_key, code_int, code_str, image_index, image_filename FOR XML RAW('audit')) FROM deleted SET @user_key = dbo.f_get_current_user() IF @tp = 2 BEGIN INSERT INTO audit_trail (mod_type, mod_date, user_key, audit_rec_table, audit_rec_key, audit_rec_key_1, audit_rec_key_2, is_delta, data_to) SELECT 'I', GETDATE(), @user_key, 'su_type_code', t.type_code_key, NULL, NULL, 0, audit_data FROM @it END ELSE IF @tp = 1 BEGIN INSERT INTO audit_trail (mod_type, mod_date, user_key, audit_rec_table, audit_rec_key, audit_rec_key_1, audit_rec_key_2, is_delta, data_from) SELECT 'D', GETDATE(), @user_key, 'su_type_code', t.type_code_key, NULL, NULL, 0, audit_data FROM @dt END ELSE BEGIN INSERT INTO audit_trail (mod_type, mod_date, user_key, audit_rec_table, audit_rec_key, audit_rec_key_1, audit_rec_key_2, is_delta, data_to, data_from) SELECT 'U', GETDATE(), @user_key, 'su_type_code', t.type_code_key, NULL, NULL, 0, t.audit_data, d.audit_data FROM @it INNER JOIN @dd ON (t.type_code_key = d.type_code_key) WHERE ISNULL(t.audit_data, '') <> ISNULL(d.audit_data, '') END SET NOCOUNT OFF;