---
title: "SQL Server'da Fonksiyonu Başka Veritabanına Kopyalama: Kontrol, Kopyalama ve Test"
description: "Mikro'da 'Invalid object name dbo.fn_XXX' hatasının çözümü. Multi-tenant ERP yapılarında SQL fonksiyonlarının varlık kontrolü, DB'ler arası toplu..."
date: 2026-04-14
category: mikro-erp
tags: ["sql-server", "mikro-erp", "stored-procedure", "multi-database", "fn-fonksiyon", "db-admin", "astaflow"]
url: https://mikroerp.dev/blog/mikro-erp-fn-fonksiyonlari-sube-kopyalama/
---

## İş Problemi: "Invalid object name 'dbo.fn_Aysm...'"

Eğer AstaFlow gibi Multi-Tenant (Çoklu Şube) veya "Mikro Desktop Modeli" gibi çok veritabanlı bir kurulum yönetiyorsanız, başınıza gelecek bir numaralı kriz "Veritabanı Uyumsuzluklarıdır".
Yazılım ekibi, Merkez için yeni bir kâr marjı fonksiyonu (`fn_KarAnaliz`) veya Cari Yaşlandırma fonksiyonu (`fn_Aysm_v2_...`) yükler. Merkez sistem mükemmel çalışır. Ancak Antalya şubesindeki (MikroDB_Antalya) kullanıcı rapora bastığında API veya program şu hatayı fırlatır:

> `Invalid object name 'dbo.fn_Aysm_v2_CariHesapAnaDovizBakiye'`

**Neden Zor Bir Süreçtir?**
Mikro'da bir versiyon geçişi (Örneğin v16'dan Desktop Modeli olan 16.xx'e geçiş) yaptığınızda, Mikro sadece bağlandığınız veritabanının fonksiyonlarını günceller. Veritabanında 80 farklı şube DB'si varsa, 400 civarı kritik iç fonksiyonu 80 DB'ye el ile kopyalamak 3 gününüzü alır ve insan hatasına %100 açıktır.

## Çözüm: "Yazılımla Yönetilen Altyapı (IaC) Yaklaşımı"

Bir veritabanı uzmanı SQL scriptini sağ tıklayıp "Execute" yapmaz. Tüm şubelerin hedefte listelendiği, eksik fonksiyonların Source (Kaynak) DB'den alınıp, Target (Hedef) DB'lere Dinamik SQL kullanılarak inject edildiği bir otomasyon yazılır.

Aşağıda AstaFlow projesi devreye alınırken 8 şubenin saniyeler içinde senkronize edilmesini sağlayan dev scripti paylaşıyoruz.

### Tüm Eksikleri Analiz Edip Hedeflere Basan Auto-Sync Scripti:

```sql
SET NOCOUNT ON;

-- ═══════════════════════════════════════════
-- GÜVENLİK AYARLARI (Senkron Edilecek Mimari)
-- ═══════════════════════════════════════════
DECLARE @SourceDB NVARCHAR(128) = 'MikroDB_Merkez';  -- En doğru, güncel fonksiyonların olduğu amiral gemisi DB.

-- Hedef Şube Veritabanları (Dağıtım Yapılacak Noktalar)
DECLARE @Targets TABLE (DBName NVARCHAR(128));
INSERT INTO @Targets VALUES
    ('MikroDB_Adana'),
    ('MikroDB_Antalya'),
    ('MikroDB_Bursa'),
    ('MikroDB_Izmir');

-- Kopyalanacak Fonksiyon Ailesi (Pattern)
-- Mikro'nun Cari veya Stok fonksiyonları genellikle belirli bir pattern ile başlar.
DECLARE @Pattern NVARCHAR(50) = 'fn_Aysm_v2_%';

-- ═══════════════════════════════════════════
-- ADIM 1: Hafızaya (RAM) Kaynak Fonksiyonları Okuma
-- ═══════════════════════════════════════════
DECLARE @AllFunctions TABLE (FuncName NVARCHAR(256), FuncDef NVARCHAR(MAX));

-- Kaynak veritabanındaki sys.sql_modules'u sorgula ve Create scriptlerini (Definition) RAM'e al!
DECLARE @FindSQL NVARCHAR(MAX) = N'
SELECT o.name, m.definition
FROM [' + @SourceDB + N'].sys.objects o
INNER JOIN [' + @SourceDB + N'].sys.sql_modules m ON o.object_id = m.object_id
WHERE o.name LIKE ''' + @Pattern + '''
AND o.type IN (''FN'', ''IF'', ''TF'')';

INSERT INTO @AllFunctions EXEC sp_executesql @FindSQL;

PRINT '📦 Kaynak Çekildi: ' + @SourceDB + ' (Toplam ' + CAST((SELECT COUNT(*) FROM @AllFunctions) AS VARCHAR) + ' fonksiyon hazırlandı)';
PRINT '---------------------------------------------------';

-- ═══════════════════════════════════════════
-- ADIM 2: Hedef Veritabanlarına Dinamik Dağıtım (Deploy)
-- ═══════════════════════════════════════════
DECLARE @TargetDB NVARCHAR(128), @FuncName NVARCHAR(256), @FuncDef NVARCHAR(MAX);
DECLARE @DeploySQL NVARCHAR(MAX), @CheckSQL NVARCHAR(MAX);
DECLARE @Exists INT;

DECLARE target_cursor CURSOR FOR SELECT DBName FROM @Targets;
OPEN target_cursor;
FETCH NEXT FROM target_cursor INTO @TargetDB;

WHILE @@FETCH_STATUS = 0
BEGIN
    PRINT '🚀 Deploying -> [' + @TargetDB + ']';
    
    DECLARE func_cursor CURSOR FOR SELECT FuncName, FuncDef FROM @AllFunctions;
    OPEN func_cursor;
    FETCH NEXT FROM func_cursor INTO @FuncName, @FuncDef;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        -- FONKSİYON hedefte zaten var mı?
        SET @CheckSQL = N'SELECT @Exists = COUNT(*) FROM [' + @TargetDB + N'].sys.objects 
                          WHERE name = ''' + @FuncName + N''' AND type IN (''FN'', ''IF'', ''TF'')';
        SET @Exists = 0;
        EXEC sp_executesql @CheckSQL, N'@Exists INT OUTPUT', @Exists OUTPUT;

        BEGIN TRY
            IF @Exists > 0
            BEGIN
                -- VARSA: DROP et ve yeniden CREATE et (ALTER yerine daha güvenli geçiş sağlar)
                SET @DeploySQL = N'USE [' + @TargetDB + N']; DROP FUNCTION IF EXISTS dbo.' + @FuncName;
                EXEC sp_executesql @DeploySQL;
                
                SET @DeploySQL = N'USE [' + @TargetDB + N']; EXEC sp_executesql @stmt';
                EXEC sp_executesql @DeploySQL, N'@stmt NVARCHAR(MAX)', @stmt = @FuncDef;
            END
            ELSE
            BEGIN
                -- YOKSA: Sıfırdan Yarat
                SET @DeploySQL = N'USE [' + @TargetDB + N']; EXEC sp_executesql @stmt';
                EXEC sp_executesql @DeploySQL, N'@stmt NVARCHAR(MAX)', @stmt = @FuncDef;
            END
            
        END TRY
        BEGIN CATCH
            PRINT '   ❌ CRASH [' + @FuncName + ']: ' + ERROR_MESSAGE();
        END CATCH

        FETCH NEXT FROM func_cursor INTO @FuncName, @FuncDef;
    END
    CLOSE func_cursor;
    DEALLOCATE func_cursor;
    
    PRINT '   ✅ Bağlantı güncellendi ve kapatıldı.';
    FETCH NEXT FROM target_cursor INTO @TargetDB;
END

CLOSE target_cursor;
DEALLOCATE target_cursor;
PRINT '---------------------------------------------------';
PRINT '🏁 BÜTÜN SUNUCU ROLLOUT İŞLEMİ TAMAMLANDI!';
```

## Performans / Ölçekleme (Rollout State) Testi

Script'in en büyük artısı, Target DB'lere sırayla (sequential) bağlanıp işlemi kendi Context'inde (`USE TargetDB`) yapmasıdır. 8 şubeli bir holding yapısında 42 adet `fn_Aysm` fonksiyonunun incelenip güncellenmesi saniyede tamamlanır. Dağıtım sırasında SQL Server üzerinde CPU spike (zıplama) oluşturmaz. Çünkü sadece Metadata update yapılmaktadır.

## Edge Cases (Ölümcül Hata Riskleri)

1. **Bağımlılık Tuzağı (Dependency Trees):** Kopyaladığınız fonksiyon Eğer `[MikroDB_Merkez].[dbo].[OZEL_KURLAR]` gibi merkeze has bir View veya SQL tablosuna okuma yapıyorsa, hedef veritabanında bu tablo bulunmadığı (veya içi boş olduğu) için kod başarıyla CREATE edilse dahi *çalıştırıldığın an* Run-Time Error patlatır. Bunu çözmenin yolu Schema-Binding yapmaktır.
2. **"Create Function must be the first statement" Hatası:** DB engine kodu derlerken bir `GO` komutu arar. Dinamik SQL (`EXEC sp_executesql @stmt`) bu yüzden kullanıldı; bu komut kendi izolasyon katmanını yaratır ve bu hatayı baypas eder.
3. **Hard-coded Master DB Referansları:** Fonksiyonları yazarken hiçbir zaman DB ismini (örn: `FROM MikroDB_Merkez.dbo.Stoklar`) fonksiyon içine gömmeyin (Hardcode etmeyin). Her DB kendi datasını okuyacak şekilde bağıllı (`FROM dbo.STOKLAR`) yazılmalıdır.

## Bu Bilgiyi Nereden Biliyoruz? (Kaynaklar)

*   **AstaFlow Case Study:** [AstaFlow Case Study (8 Şube Migration Planlaması)](/case-study/)
*   **İlgili Çözüm:** [Cross-Database Update Mimarileri](/blog/sql-server-cross-database-update-senkronizasyon/)