= Migration von der alten API
:description: Migration von TcpClient/Client zur ECM API (ECMSync/ECMAsync). Beschreibt Äquivalente für lol_query(), xml_import() und andere Methoden der veralteten Basisklasse.
:keywords: Migration, TcpClient, Client, ECMSync, ECMAsync, lol_query, xml_import, veraltete API
:page-tags: guide

Diese Seite zeigt, wie die Methoden der veralteten abstrakten `Client`-Basisklasse (aus `ecmind_blue_client.client`) auf die aktuelle xref:../api/index.adoc[ECM-API] umgestellt werden.

Die alte API basierte auf rohen `Job`-Objekten und untypisierten `dict`-Ergebnissen.
Die neue API nutzt typisierte Modellklassen, fluent Query Builder und strukturierte Exceptions.

== Setup

[tabs]
====
Legacy::
+
[source,python]
----
from ecmind_blue_client.tcp_client import Connection

client = Connection(host, port, "App", user, password, ssl)
result = client.execute(job)
----

ECM API::
+
[source,python]
----
from ecmind_blue_client.pool import SyncPoolClient
from ecmind_blue_client.ecm import ECM

client = SyncPoolClient(servers="host:4000:1", username=user, password=password)
ecm = ECM(client)
----
====

Der `SyncPoolClient` verwaltet den Verbindungspool automatisch.
Für asyncio-Kontexte (FastAPI etc.) wird der `AsyncPoolClient` verwendet.

== Methodenübersicht

=== `lol_query()` → `ecm.dms.select_lol()`

`lol_query()` führte paginierte Flachlisten-Abfragen (LOL) aus und lieferte untypisierte `dict`-Zeilen.
Das direkte Äquivalent in der neuen API ist `ecm.dms.select_lol()` — gleicher LOL-Format, aber mit typisierten Modellinstanzen.

[tabs]
====
Legacy::
+
[source,python]
----
from ecmind_blue_client.query_condition_group import QueryConditionGroup

results = list(client.lol_query(
    "InvoiceFolder",
    conditions=QueryConditionGroup("InvoiceFolder", [("Year", "=", "2024")]),
    result_fields=["Title", "Year"],
    page_size=100,
))
for row in results:
    print(row["Title"], row["Year"])
----

ECM API::
+
[source,python]
----
from ecmind_blue_client.ecm.model import make_folder_model

InvoiceFolder = make_folder_model("InvoiceFolder")

for folder in ecm.dms.select_lol(InvoiceFolder).where(
    InvoiceFolder["Year"] == 2024
).stream():
    print(folder["Title"], folder["Year"])
----
====

[TIP]
Die neue API unterstützt zusätzlich hierarchische (HOL) Abfragen über `ecm.dms.select()`.
HOL liefert reichere Objektgraphen mit Eltern-Kind-Beziehungen und ist für die meisten Anwendungsfälle die empfohlene Standardmethode.

Für statische Typprüfung empfiehlt sich die explizite Modelldeklaration statt `make_folder_model`:

[source,python]
----
from ecmind_blue_client.ecm.model import ECMFolderModel, ECMField

class InvoiceFolder(ECMFolderModel):
    _internal_name_ = "InvoiceFolder"
    Title: ECMField[str]
    Year: ECMField[int]

# LOL — Flachliste, gleicher Aufbau wie lol_query()
for folder in ecm.dms.select_lol(InvoiceFolder).where(
    InvoiceFolder.Year == 2024
).stream():
    print(folder.Title, folder.Year)

# HOL — hierarchisch, für die meisten Abfragen empfohlen
for folder in ecm.dms.select(InvoiceFolder).where(
    InvoiceFolder.Year == 2024
).order_by(InvoiceFolder.Year.DESC).stream():
    print(folder.Title, folder.Year)
----

Siehe xref:../api/dms/select_lol.adoc[`ecm.dms.select_lol()`] und xref:../api/dms/select.adoc[`ecm.dms.select()`].

=== `xml_import()` → `ecm.dms.insert()` / `update()` / `upsert()`

`xml_import()` kombinierte Insert- und Update-Logik über `action0`/`action1`-Parameter.
Die neue API trennt diese in klar benannte Methoden.

==== Insert

[tabs]
====
Legacy::
+
[source,python]
----
from ecmind_blue_client.const import ImportActions

client.xml_import(
    "InvoiceFolder",
    search_fields={},
    import_fields={"Title": "Rechnung 2024", "Year": "2024"},
    action0=ImportActions.INSERT,
    action1=ImportActions.ERROR,
)
----

ECM API::
+
[source,python]
----
folder_id, type_id = ecm.dms.insert(InvoiceFolder(Title="Rechnung 2024", Year=2024))
# oder Objekt direkt zurückgeben:
folder = ecm.dms.insert_and_get(InvoiceFolder(Title="Rechnung 2024", Year=2024))
----
====

==== Insert oder Update (Upsert)

[tabs]
====
Legacy::
+
[source,python]
----
from ecmind_blue_client.const import ImportActions

client.xml_import(
    "InvoiceFolder",
    search_fields={"Title": "Rechnung 2024"},
    import_fields={"Year": "2024"},
    action0=ImportActions.INSERT,
    action1=ImportActions.UPDATE,
)
----

ECM API::
+
[source,python]
----
object_id, type_id, hits, action = (
    ecm.dms.upsert(InvoiceFolder(Title="Rechnung 2024", Year=2024))
    .search(InvoiceFolder.Title == "Rechnung 2024")
    .execute()
)
----
====

==== Mehrere Treffer (action_multiple)

Wenn die Suche mehr als ein übereinstimmendes Objekt findet, wird standardmäßig ein Fehler
zurückgegeben. Mit `.action_multiple()` kann dieser Fall explizit behandelt werden:

[source,python]
----
from ecmind_blue_client.const import ImportActions

object_id, type_id, hits, action = (
    ecm.dms.upsert(InvoiceFolder(Title="Rechnung 2024", Year=2024))
    .search(InvoiceFolder.Title == "Rechnung 2024")
    .action_multiple(ImportActions.UPDATE)  # alle Treffer aktualisieren
    .execute()
)
----

[NOTE]
Die Parameter `main_type`, `variant_parent_id` und `options` von `xml_import()` haben
kein direktes Äquivalent im High-Level-Upsert-Builder.
Für Operationen, die diese Parameter benötigen, steht `ecm.execute()` als
Low-Level-Escape-Hatch zur Verfügung.

Siehe xref:../api/dms/insert.adoc[`insert()`], xref:../api/dms/update.adoc[`update()`], xref:../api/dms/upsert.adoc[`upsert()`].

=== `get_object_details()` → `ecm.dms.get()`

[tabs]
====
Legacy::
+
[source,python]
----
from ecmind_blue_client.const import SystemFields

details = client.get_object_details(
    "InvoiceFolder",
    object_id=42,
    system_fields=[SystemFields.OBJECT_ID],
)
print(details["Title"])
----

ECM API::
+
[source,python]
----
folder = ecm.dms.get(InvoiceFolder, object_id=42)
print(folder.Title)
print(folder.system.id)
----
====

Siehe xref:../api/dms/get.adoc[`ecm.dms.get()`].

=== `store_in_cache()` / `store_in_cache_by_id()` → `ecm.dms.files()`

`store_in_cache()` und `store_in_cache_by_id()` lieferten rohe `ResultFile`-Objekte.
`ecm.dms.files()` vereint beide Methoden und gibt strukturierte Datei-Metadaten zurück.

[tabs]
====
Legacy::
+
[source,python]
----
files = client.store_in_cache(object_id=42)
for f in files:
    data = f.bytes()
----

ECM API::
+
[source,python]
----
files = ecm.dms.files(document)
for f in files:
    data = f.bytes()
----
====

==== Mit Dateikonvertierung (`store_in_cache_by_id`)

[tabs]
====
Legacy::
+
[source,python]
----
from ecmind_blue_client.const import StoreInCacheByIdConversion

files = client.store_in_cache_by_id(
    object_id=42, convert=StoreInCacheByIdConversion.PDF
)
----

ECM API::
+
[source,python]
----
from ecmind_blue_client.const import StoreInCacheByIdConversion

files = ecm.dms.files(document, convert=StoreInCacheByIdConversion.PDF)
# oder per Objekt-ID:
files = ecm.dms.files(42, convert=StoreInCacheByIdConversion.PDF)
----
====

`StoreInCacheByIdConversion` unterstützt `NONE` (Standard), `PDF` (Haupttypen 1–4) und
`MULTIPAGE_TIFF` (TIFF-Haupttypen 2–3 werden zu einer einzigen Datei zusammengeführt).
Zusätzliche Parameter, die in der Legacy-API nicht vorhanden waren: `when_cold_then_tiff`
(COLD/ASCII-Dateien als TIFF zurückgeben) und `add_annotations` (Annotationen einbrennen).

[NOTE]
Der `checkout`-Parameter von `store_in_cache()` hat kein Äquivalent in `ecm.dms.files()`.
Für Checkout-Funktionalität kann `ecm.execute()` mit dem rohen Job verwendet werden.

Siehe xref:../api/dms/files.adoc[`ecm.dms.files()`].

=== `get_object_type_by_id()` → `ecm.dms.get_object_type_by_id()`

Beide APIs bieten diese Methode mit identischer Signatur.

[tabs]
====
Legacy::
+
[source,python]
----
type_id = client.get_object_type_by_id(object_id=42)
----

ECM API::
+
[source,python]
----
type_id = ecm.dms.get_object_type_by_id(object_id=42)
----
====

Siehe xref:../api/dms/get_object_type_by_id.adoc[`ecm.dms.get_object_type_by_id()`].

=== `execute_sql()` → `ecm.db.select()`

[tabs]
====
Legacy::
+
[source,python]
----
rows = client.execute_sql("SELECT * FROM invoices WHERE year = ?", 2024)
for row in rows:
    print(row["id"], row["title"])
----

ECM API::
+
[source,python]
----
result = ecm.db.select("SELECT * FROM invoices WHERE year = ?", 2024)
for row in result.rows:
    print(row["id"], row["title"])
----
====

Siehe xref:../api/db/select.adoc[`ecm.db.select()`].

=== `execute()` — Low-Level-Escape-Hatch

Beide APIs behalten eine rohe `execute()`-Methode für Jobs, die noch nicht durch die High-Level-API abgedeckt werden.

[tabs]
====
Legacy::
+
[source,python]
----
from ecmind_blue_client import Job

result = client.execute(Job("krn.GetServerInfo", Flags=0, Info=6))
print(result.values["Value"])
----

ECM API::
+
[source,python]
----
from ecmind_blue_client.rpc import Jobs

result = ecm.execute(Jobs.KRN_GETSERVERINFO, Flags=0, Info=6)
print(result.values["Value"])
----
====

== Benutzerwechsel (Context-User)

Die alte API übergab `context_user` als Parameter an einzelne Methoden.
Die neue API verwendet einen Context Manager, der den Benutzer in alle Operationen innerhalb des Blocks injiziert.

[tabs]
====
Legacy::
+
[source,python]
----
client.xml_import(..., context_user="john")
----

ECM API::
+
[source,python]
----
with ecm.impersonate("john") as ecm_john:
    ecm_john.dms.insert(InvoiceFolder(Title="Rechnung 2024"))
----
====

== Neue Funktionen in der ECM API

Die folgenden Methoden sind in `ecm.dms` verfügbar, haben aber kein Äquivalent in der
veralteten `Client`-Basisklasse.

=== Löschen, Verschieben, Kopieren

[source,python]
----
# Löschen (Soft-Delete standardmäßig; hard_delete=True umgeht den Papierkorb)
ecm.dms.delete(folder)
ecm.dms.delete(folder, hard_delete=True, delete_cascading=True)

# Dokument in ein anderes Register verschieben
ecm.dms.move(document, folder_id=target_folder, register_id=target_register)

# Ordner inklusive aller Kinder kopieren
ecm.dms.copy(folder, folder_id=target_folder, copy_cascading=True)

# Verknüpfte Kopie eines Dokuments erstellen (zweiter Ablagort, gemeinsamer Indexsatz)
ecm.dms.copy(document, folder_id=target_folder, register_id=target_register, link_document=True)
----

=== Erweiterte Abfrageoptionen (`ecm.dms.select()`)

`ecm.dms.select()` bietet HOL-Abfragemodifikatoren ohne Legacy-Äquivalent:

[source,python]
----
# Rechte, Basisparameter und Datei-Metadaten in die Antwort einschließen
results = (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year == 2024)
    .rights()
    .base_params()
    .execute()
)

# Hierarchische Traversierung
results = ecm.dms.select(InvoiceFolder).with_children().execute()
results = ecm.dms.select(InvoiceFolder).with_parents().execute()

# Dokumentvarianten einschließen
results = ecm.dms.select(InvoiceDocument).variants().execute()

# Papierkorb abfragen
results = ecm.dms.select(InvoiceFolder).garbage_mode().execute()
----
