есть компонент справочник на vue и asp.net контроллер с методами, надо добавить возможность редактирования записей, использую linq2db
<template>
  <div class="reference-manager">
    <div class="controls">
      <select
        v-model="selectedReference"
        @change="fetchData"
        class="reference-select"
      >
        <option selected disabled value="default">Выберите справочник</option>
        <option
          v-for="option in referenceOptions"
          :key="option.id"
          :value="option.id"
        >
          {{ option.name }}
        </option>
      </select>

      <button
        @click="startAdding"
        :disabled="!selectedReference || !selectedOption?.canAdd"
        class="add-button"
      >
        + Добавить запись
      </button>
    </div>

    <div class="table-container">
      <table
        style="width: 100%"
        class="pure-table pure-table-striped"
        v-if="tableData.length || isAdding"
      >
        <thead>
          <tr>
            <th
              v-for="header in currentHeaders"
              :key="header.value"
              :style="{ width: header.width || 'auto' }"
            >
              {{ header.text }}
            </th>
          </tr>
        </thead>

        <tbody>
          <!-- Строка добавления -->
          <tr
            v-if="isAdding"
            class="adding-row"
            :class="{ 'invalid-row': hasErrors }"
          >
            <td v-for="header in currentHeaders" :key="header.value">
              <input
                v-if="header.editable"
                v-model="newItem[header.value]"
                :placeholder="header.text"
                :class="{ invalid: !isFieldValid(header.value) }"
                @keyup.enter="saveRow"
                ref="inputFields"
              />
              <span v-else class="non-editable">(авто)</span>
            </td>
          </tr>

          <!-- Существующие записи -->
          <tr v-for="(item, index) in tableData" :key="index">
            <td v-for="header in currentHeaders" :key="header.value">
              {{ formatValue(item[header.value], header.value) }}
            </td>
          </tr>
        </tbody>
      </table>

      <div v-if="!tableData.length && !isAdding" class="no-data">
        Нет данных для отображения
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { _AdminController } from '@/shared/scripts/api/clients-private/_AdminController';
import { ApkDto } from '@/shared/scripts/api/Models/Admin/Catalog/ApkDto';
import { CodeCategoryDto } from '@/shared/scripts/api/Models/Admin/Catalog/CodeCategoryDto';
import { LegalEntityTypeDto } from '@/shared/scripts/api/Models/Admin/Catalog/LegalEntityTypeDto';
import { OperatorDto } from '@/shared/scripts/api/Models/Admin/Catalog/OperatorDto';
import { ref, computed, nextTick } from 'vue';

interface HeaderConfig {
  text: string;
  value: string;
  width?: string;
  editable: boolean;
}

interface ReferenceOption {
  id: keyof typeof headersConfig;
  name: string;
  canAdd: boolean;
}

const headersConfig = {
  default: [],
  apks: [
    { text: 'ID', value: 'apkId', width: '80px', editable: false },
    { text: 'Название APK', value: 'apkName', editable: true },
  ],
  codeCategories: [
    {
      text: 'ID категории',
      value: 'codeCategoryId',
      width: '120px',
      editable: false,
    },
    { text: 'Наименование', value: 'codeCategoryName', editable: true },
  ],
  legalEntityTypes: [
    {
      text: 'ID типа',
      value: 'legalEntityTypeId',
      width: '100px',
      editable: false,
    },
    { text: 'Тип юрлица', value: 'legalEntityTypeName', editable: true },
  ],
  operators: [
    {
      text: 'ID оператора',
      value: 'operatorId',
      width: '100px',
      editable: false,
    },
    { text: 'Оператор связи', value: 'operatorName', editable: true },
  ],
  exceptionLogs: [
    {
      text: 'ID записи',
      value: 'exceptionLogId',
      width: '100px',
      editable: false,
    },
    {
      text: 'Время ошибки',
      value: 'timeStamp',
      width: '180px',
      editable: false,
    },
    {
      text: 'Имя пользователя',
      value: 'userName',
      width: '120px',
      editable: false,
    },
    { text: 'Сообщение', value: 'message', editable: false },

    // { text: 'Стек вызовов', value: 'stackTrace', editable: false },
  ],
} as const;

const referenceOptions: ReferenceOption[] = [
  { id: 'apks', name: 'APK', canAdd: true },
  { id: 'codeCategories', name: 'Категории кодов', canAdd: true },
  { id: 'legalEntityTypes', name: 'Типы юрлиц', canAdd: true },
  { id: 'operators', name: 'Операторы', canAdd: true },
  { id: 'exceptionLogs', name: 'Логи ошибок', canAdd: false },
];

// Реактивные состояния
const selectedReference = ref<keyof typeof headersConfig>('default');
const tableData = ref<any[]>([]);
const isAdding = ref(false);
const newItem = ref<Record<string, any>>({});
const hasErrors = ref(false);
const inputFields = ref<HTMLInputElement[]>([]);

// Вычисляемые свойства
const currentHeaders = computed(
  () => headersConfig[selectedReference.value] || [],
);

const selectedOption = computed(() =>
  referenceOptions.find(opt => opt.id === selectedReference.value),
);

// Методы
const fetchData = async () => {
  try {
    switch (selectedReference.value) {
      case 'apks':
        tableData.value = await _AdminController.getApks(null);
        break;
      case 'codeCategories':
        tableData.value = await _AdminController.getCodeCategories(null);
        break;
      case 'legalEntityTypes':
        tableData.value = await _AdminController.getLegalEntityTypes(null);
        break;
      case 'operators':
        tableData.value = await _AdminController.getOperators(null);
        break;
      case 'exceptionLogs':
        tableData.value = await _AdminController.getExceptionLogs(null);
        break;
    }
  } catch (err) {
    console.error('Ошибка загрузки:', err);
  }
};

const startAdding = async () => {
  isAdding.value = true;
  newItem.value = {};
  await nextTick();
  if (inputFields.value.length > 0) inputFields.value[0].focus();
};

const isFieldValid = (field: string) => {
  const header = currentHeaders.value.find(h => h.value === field);
  return !header?.editable || !!newItem.value[field];
};

const saveRow = async () => {
  const requiredFields = currentHeaders.value
    .filter(h => h.editable)
    .map(h => h.value);

  if (!requiredFields.every(f => !!newItem.value[f])) {
    hasErrors.value = true;
    return;
  }

  try {
    switch (selectedReference.value) {
      case 'apks':
        await _AdminController.addApk(null, newItem.value as ApkDto);
        break;
      case 'codeCategories':
        await _AdminController.addCodeCategory(
          null,
          newItem.value as CodeCategoryDto,
        );
        break;
      case 'legalEntityTypes':
        await _AdminController.addLegalEntityType(
          null,
          newItem.value as LegalEntityTypeDto,
        );
        break;
      case 'operators':
        await _AdminController.addOperator(null, newItem.value as OperatorDto);
        break;
    }

    isAdding.value = false;
    await fetchData();
  } catch (err) {
    console.error('Ошибка сохранения:', err);
  }
};

const formatValue = (value: any, field: string) => {
  if (field === 'timeStamp') return new Date(value).toLocaleString();
  return value?.toString() || '';
};
</script>

<style scoped>
.reference-manager {
  padding: 20px;
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  height: 100%;
}

.controls {
  display: flex;
  gap: 15px;
  margin-bottom: 20px;
}

.reference-select {
  padding: 8px 12px;
  border: 1px solid #ccc;
  border-radius: 4px;
  width: 250px;
}

.add-button {
  background: #28a745;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 8px 16px;
  cursor: pointer;
}

.add-button:disabled {
  background: #6c757d;
  cursor: not-allowed;
}

.table-container {
  overflow: auto;
  height: 100%;
  width: 100%;
}

.adding-row td {
  padding: 0;
  background: #fff3cd;
}

.adding-row input {
  width: 100%;
  height: 100%;
  padding: 12px;
  border: none;
  background: transparent;
  box-sizing: border-box;
}

.adding-row input:focus {
  outline: 2px solid #007bff;
  background: white;
}

.invalid {
  outline: 2px solid #dc3545 !important;
}

.invalid-row {
  box-shadow: inset 0 0 0 2px #dc3545;
}

.no-data {
  padding: 20px;
  text-align: center;
  color: #6c757d;
}

.non-editable {
  color: #6c757d;
  font-style: italic;
  padding: 12px;
  display: inline-block;
}
</style>

       public async Task<List<ApkDto>> Apks() =>
            await _connectionAlphaNames
                .Apks.Select(a => new ApkDto { ApkId = a.ApkId, ApkName = a.ApkName })
                .ToListAsync();

        public async Task<List<CodeCategoryDto>> CodeCategories() =>
            await _connectionAlphaNames
                .CodeCategories.Select(c => new CodeCategoryDto
                {
                    CodeCategoryId = c.CodeCategoryId,
                    CodeCategoryName = c.CodeCategoryName
                })
                .ToListAsync();

        public async Task<List<LegalEntityTypeDto>> LegalEntityTypes() =>
            await _connectionAlphaNames
                .LegalEntityTypes.Select(l => new LegalEntityTypeDto
                {
                    LegalEntityTypeId = l.LegalEntityTypeId,
                    LegalEntityTypeName = l.LegalEntityTypeName
                })
                .ToListAsync();

        public async Task<List<OperatorDto>> Operators() =>
            await _connectionAlphaNames
                .Operators.Select(o => new OperatorDto
                {
                    OperatorId = o.OperatorId,
                    OperatorName = o.OperatorName
                })
                .ToListAsync();

        public async Task<List<SmsDistributionTypeDto>> SmsDistributionTypes() =>
            await _connectionAlphaNames
                .SmsDistributionTypes.Select(s => new SmsDistributionTypeDto
                {
                    DistributionTypeId = s.DistributionTypeId,
                    DistributionTypeName = s.DistributionTypeName
                })
                .ToListAsync();

        public async Task<List<ExceptionLogDto>> ExceptionLogs() =>
            await _connectionAlphaNames
                .ExceptionLogs.Select(e => new ExceptionLogDto
                {
                    ExceptionLogId = e.ExceptionLogId,
                    Message = e.Message,
                    UserName = e.Users != null ? e.Users.FullName : null,
                    TimeStamp = e.TimeStamp
                })
                .OrderByDescending(x => x.ExceptionLogId)
                .ToListAsync();

        [HttpPost]
        public async Task AddApk([FromBody] ApkDto dto) =>
            await _connectionAlphaNames.InsertAsync(new Apk { ApkName = dto.ApkName });

        [HttpPost]
        public async Task AddCodeCategory([FromBody] CodeCategoryDto dto) =>
            await _connectionAlphaNames.InsertAsync(
                new CodeCategory { CodeCategoryName = dto.CodeCategoryName }
            );

        [HttpPost]
        public async Task AddLegalEntityType([FromBody] LegalEntityTypeDto dto) =>
            await _connectionAlphaNames.InsertAsync(
                new LegalEntityType { LegalEntityTypeName = dto.LegalEntityTypeName }
            );

        [HttpPost]
        public async Task AddOperator([FromBody] OperatorDto dto) =>
            await _connectionAlphaNames.InsertAsync(
                new Operator { OperatorName = dto.OperatorName }
            );

        [HttpPost]
        public async Task AddSmsDistributionType([FromBody] SmsDistributionTypeDto dto) =>
            await _connectionAlphaNames.InsertAsync(
                new SmsDistributionType { DistributionTypeName = dto.DistributionTypeName }
            );