select() und select_lol()

Beide Methoden geben einen verkettbaren Query-Builder für dieselben typisierten Modelle zurück, über zwei unterschiedliche Abfrageformate des Servers:

select() (HOL, Hierarchical Object List)

Gibt einen ECMModelQuerySync / ECMModelQueryAsync zurück. Der Server antwortet mit einem objektbasierten XML-Baum, der die Hierarchie und alle optionalen Zusatzdaten trägt: Dateiinformationen, Basisparameter, Varianten, Notizen, unter- und übergeordnete Objekte.

select_lol() (LOL, Linear Object List)

Gibt einen ECMModelQueryLolSync / ECMModelQueryLolAsync zurück. Der Server antwortet mit einer kompakten, spaltenorientierten Trefferliste. Sie ist bei großen, flachen Ergebnismengen in der Regel schneller, trägt aber weder die Hierarchie noch diese Zusatzdaten.

Bedingungen, Sortierung, Paginierung, Feldbeschränkung, Volltextsuche, Rechte und der Transport der Trefferliste verhalten sich in beiden Modi gleich. Alles auf dieser Seite gilt daher für beide, sofern es nicht mit nur HOL oder nur LOL gekennzeichnet ist.

ECM-Modell-Referenz — vollständige Beschreibung aller system-Eigenschaften (system.id, system.rights, system.base_params, system.file_properties usw.) und der Änderungsverfolgung.

1. Modus wählen

Table 1. Was die beiden Modi können
Fähigkeit select() (HOL) select_lol() (LOL)

Performance

Langsamer

Schneller bei großen, flachen Mengen

Sortierung

Verfügbar

Verfügbar

.file_properties()

Verfügbar

Nicht verfügbar

.base_params()

Verfügbar (strukturiert)

Nicht verfügbar (nur via Systemfelder)

.variants()

Verfügbar

Nicht verfügbar

.icons()

Verfügbar

Ignoriert (Server liefert keine Icons)

.with_children() / .with_parents()

Verfügbar

Nicht verfügbar

Tabellenfeld-Werte

Typisiert, mit row_id

Untypisierte Rohstrings, row_id immer None

Wenn Dateiinformationen, Basisparameter, Varianten oder hierarchische Abfragen benötigt werden, ist select() die richtige Wahl.

In der Praxis folgt der Modus daraus, was das Ergebnis tragen muss:

  • Sync

  • Async

# Empfohlen für: große Mengen, nur Indexdaten (ohne Varianten/Dateiinfos/Hierarchie)
for doc in (
    ecm.dms.select_lol(InvoiceDocument)
    .where(InvoiceDocument.Year == 2024)
    .order_by(InvoiceDocument.Title.ASC)
    .pagesize(500)
    .stream()
):
    print(doc.system.id, doc.Title)

# Empfohlen für: Dateiinfos, Basisparameter, Varianten, Hierarchien → select() verwenden
for doc in (
    ecm.dms.select(InvoiceDocument)
    .where(InvoiceDocument.Year == 2024)
    .file_properties()
    .stream()
):
    print(doc.system.file_properties.extension)
# Empfohlen für: große Mengen, nur Indexdaten (ohne Varianten/Dateiinfos/Hierarchie)
async for doc in (
    ecm.dms.select_lol(InvoiceDocument)
    .where(InvoiceDocument.Year == 2024)
    .order_by(InvoiceDocument.Title.ASC)
    .pagesize(500)
    .stream()
):
    print(doc.system.id, doc.Title)

# Empfohlen für: Dateiinfos, Basisparameter, Varianten, Hierarchien → select() verwenden
async for doc in (
    ecm.dms.select(InvoiceDocument)
    .where(InvoiceDocument.Year == 2024)
    .file_properties()
    .stream()
):
    print(doc.system.file_properties.extension)

2. Signatur

Beide Builder werden synchron erzeugt, auch in der async-Variante. Das await gehört an den abschließenden Aufruf (.execute()) oder an die Iteration (async for … .stream()).

  • Sync

  • Async

ecm.dms.select(model_class: type[T]) -> ECMModelQuerySync[T]
ecm.dms.select_lol(model_class: type[T]) -> ECMModelQueryLolSync[T]
ecm.dms.select(model_class: type[T]) -> ECMModelQueryAsync[T]
ecm.dms.select_lol(model_class: type[T]) -> ECMModelQueryLolAsync[T]

3. Parameter

Name Typ Beschreibung

model_class

type[ECMFolderModel | ECMRegisterModel | ECMDocumentModel]

Die Modelklasse, die den Objekttyp beschreibt. Kann auch über make_folder_model() / make_register_model() / make_document_model() dynamisch erzeugt werden.

Wenn nur der interne Typname bekannt ist (und nicht vorab feststeht, ob es sich um Ordner, Register oder Dokument handelt), liefert ecm.dms.model_by_name() automatisch die passende dynamische Modellklasse für select().

4. Query-Builder Methoden

Die Builder-Methoden sind verkettbar. Die Spalte Modus sagt, wo eine Methode existiert:

Methode Modus Beschreibung

.where(*conditions)

beide

Filterbedingungen hinzufügen. Mehrere Argumente werden mit AND verknüpft. Bedingungen lassen sich mit & (AND) und | (OR) kombinieren.

.order_by(*sort_orders)

beide

Sortierung festlegen. Die Argumentposition bestimmt die Sortierpriorität. Jedes Argument ist ein ECMSortOrder über .ASC / .DESC auf einem ECMField, auch auf Systemfeldern (Model.system.id.DESC).

.limit(n)

beide

Maximale Gesamtzahl der Treffer über alle Seiten (LOL: wird auf MaxHits abgebildet).

.pagesize(n)

beide

Anzahl Objekte pro Serveranfrage (Standard: 1000). Beeinflusst die Effizienz bei großen Ergebnismengen.

.offset(n)

beide

Nullbasierte Startposition. Überspringt die ersten n Treffer.

.fields(*fields)

beide

Nur die angegebenen Felder zurückgeben (setzt field_schema="MIN"). Index- und Systemfelder sind erlaubt. Ohne Argumente wird die Einschränkung zurückgesetzt.

.fulltext(term, **engine_options)

beide

Fügt eine <Fulltext>-Bedingung für den abgefragten Objekttyp hinzu. Erfordert eine konfigurierte Volltext-Engine auf dem Server. Mehrfacher Aufruf ersetzt die vorherige Einstellung.

.garbage_mode()

beide

Nur Objekte aus dem Papierkorb zurückgeben.

.rights()

beide

Rechteinformationen mitladen (füllt obj.system.rights). Die Insert-Kontingente werden mit angefordert, weil der Server insert sonst immer als verweigert meldet.

.result_as_file()

beide

Trefferliste als Antwortdatei statt im XML-Parameter anfordern (Flags=16). Standardmäßig aus. Siehe Abschnitt Transport der Trefferliste.

.execute()

beide

Abfrage ausführen und alle Treffer als Liste zurückgeben (alle Seiten im Speicher).

.stream()

beide

Abfrage seitenweise ausführen und einen Generator zurückgeben. Empfohlen für große Ergebnismengen.

.base_params()

nur HOL

Audit-Metadaten mitladen (füllt obj.system.base_params): Ersteller, Änderungsdatum usw.

.file_properties()

nur HOL

Dateiinformationen mitladen (füllt obj.system.file_properties, nur Dokumente).

.variants()

nur HOL

Varianten-Daten von Dokumenten mitladen.

.remarks()

nur HOL

Notizen der Objekte mitladen.

.icons()

nur HOL

Icon-IDs der Objekte mitladen. Im LOL-Modus wird der Aufruf ignoriert, weil der Server dort keine Icons zurückgibt.

.with_children(*specs) / .with_parents(*specs)

nur HOL

Wechselt zu einer HOL-Abfrage mit unter- bzw. übergeordneten Objekten. Gibt einen ECMModelQueryHolSync zurück.

.execute() lädt alle Objekte vollständig in den Speicher, bevor die Verarbeitung beginnt. Bei großen Ergebnismengen kann dies zu Speicherproblemen führen.

.stream() arbeitet seitenbasiert — das Paging in enaio ist nicht transaktional. Werden während des Iterierens Objekte verändert, können Seiten inkonsistente Zustände liefern oder Objekte doppelt erscheinen. In diesem Fall sollte .execute() bevorzugt werden.

5. Filterbedingungen (where)

Bedingungen werden über .where() übergeben. Mehrere Argumente in einem .where()-Aufruf werden automatisch mit AND verknüpft. Für OR-Verknüpfungen werden Bedingungen mit dem |-Operator kombiniert, für AND mit &.

Jeder Vergleich braucht eigene Klammern

| und & binden in Python stärker als ==, eine ungeklammerte Verknüpfung tut also nicht, wonach sie aussieht:

# TypeError: unsupported operand type(s) for |: 'str' and 'ECMField'
# Python liest das als: Status == ("Open" | InvoiceFolder.Status) == "In Progress"
.where(InvoiceFolder.Status == "Open" | InvoiceFolder.Status == "In Progress")

# Richtig: jeder Vergleich in eigenen Klammern
.where((InvoiceFolder.Status == "Open") | (InvoiceFolder.Status == "In Progress"))

Die Schlüsselwörter or und and sind kein Ausweg. Python kann sie nicht überladen: die Operatoren rufen bool() auf einem Operanden auf und geben einen der beiden unverändert zurück, or würde also zur ersten und and zur zweiten Bedingung auswerten und die jeweils andere verwerfen. Bedingungen verweigern deshalb den Wahrheitswert:

# TypeError: ECM conditions cannot be combined with 'and', 'or' or 'not' ...
.where((InvoiceFolder.Status == "Open") or (InvoiceFolder.Status == "In Progress"))

# Richtig
.where((InvoiceFolder.Status == "Open") | (InvoiceFolder.Status == "In Progress"))

Dasselbe gilt für not bedingung und für if bedingung:. Nur | und & bauen eine Bedingungsgruppe. Für eine Wertemenge auf einem Feld erübrigt .in_() die Frage.

Der Bedingungswert muss zum deklarierten Feldtyp passen

Bei einem generierten Modell sind die Vergleichsoperatoren gegen den Feldtyp typisiert: ein ECMField[bool] nimmt True / False, nicht "1". Ein Type-Checker meldet die Abweichung — bei == und != am .where()-Aufruf, bei <, , >, >= direkt am Operator.

.where(InvoiceDocument.Bezahlt == True)   # richtig
.where(InvoiceDocument.Bezahlt == "1")    # Typfehler (beide Schreibweisen kommen als 1 am Server an)

Zwei Kalibrierungen halten funktionierenden Code gültig: ein datetime-Feld nimmt auch ein einfaches date, und ein Katalogfeld nimmt sein generiertes Enum-Member oder den einfachen String. None und die serverseitigen Platzhalter (DmsQuerySpecialValue, DmsQueryParamValue, DmsQueryLinkedValue) sind auf jedem Feld erlaubt.

Dynamische Modelle aus model_by_name() / make_*_model() deklarieren keine Feldtypen, ihre Bedingungen werden deshalb nicht geprüft — Model["Bezahlt"] == True und == "1" sind beide zulässig. Tabellenfeldspalten werden über den Namen aufgelöst und bleiben ebenfalls ungeprüft.

5.1. Vergleichsoperatoren

Syntax Operator Beispiel

Field == value

Gleichheit

InvoiceFolder.Year == 2024

Field != value

Ungleichheit

InvoiceFolder.Status != "Archiviert"

Field < value

Kleiner als

InvoiceFolder.Year < 2024

Field ⇐ value

Kleiner oder gleich

InvoiceFolder.Year ⇐ 2024

Field > value

Größer als

InvoiceFolder.Year > 2020

Field >= value

Größer oder gleich

InvoiceFolder.Year >= 2020

Field.in_(v1, v2, …​)

Enthält einen der Werte

InvoiceFolder.Year.in_(2022, 2023, 2024)

Field.not_in(v1, v2, …​)

Enthält keinen der Werte

InvoiceFolder.Status.not_in("Offen", "Entwurf")

Field.between(lower, upper)

Zwischen zwei Werten (inklusiv)

InvoiceFolder.Year.between(2020, 2024)

5.2. AND-Verknüpfung

Mehrere Argumente in .where() werden mit AND verknüpft. Alternativ kann & verwendet werden:

# Variante 1: mehrere Argumente → AND
ecm.dms.select(InvoiceFolder).where(
    InvoiceFolder.Year >= 2020,
    InvoiceFolder.Status == "Freigegeben",
)

# Variante 2: explizites & → gleiches Ergebnis
ecm.dms.select(InvoiceFolder).where(
    (InvoiceFolder.Year >= 2020) & (InvoiceFolder.Status == "Freigegeben")
)

5.3. OR-Verknüpfung

Bedingungen werden mit | zu einer OR-Gruppe kombiniert:

ecm.dms.select(InvoiceFolder).where(
    (InvoiceFolder.Status == "Offen") | (InvoiceFolder.Status == "In Bearbeitung")
)

Für Wertemengen ist .in_() kürzer:

ecm.dms.select(InvoiceFolder).where(
    InvoiceFolder.Status.in_("Offen", "In Bearbeitung")
)

5.4. Gemischte AND/OR-Gruppen

& und | können beliebig verschachtelt werden. Python-Klammern steuern die Auswertungsreihenfolge:

  • Sync

  • Async

# (Jahr >= 2020 AND Jahr <= 2024) AND (Status = "Offen" OR Status = "In Bearbeitung")
for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(
        (InvoiceFolder.Year >= 2020) & (InvoiceFolder.Year <= 2024),
        InvoiceFolder.Status.in_("Offen", "In Bearbeitung"),
    )
    .stream()
):
    print(folder.Title, folder.Year)
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(
        (InvoiceFolder.Year >= 2020) & (InvoiceFolder.Year <= 2024),
        InvoiceFolder.Status.in_("Offen", "In Bearbeitung"),
    )
    .stream()
):
    print(folder.Title, folder.Year)

5.5. between()

.between(lower, upper) ist eine kompakte Alternative zu >= + :

  • Sync

  • Async

for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year.between(2020, 2024))
    .stream()
):
    print(folder.Title)
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year.between(2020, 2024))
    .stream()
):
    print(folder.Title)

5.6. Tabellenfeld-Spaltenbedingungen

Die Bedingungen funktionieren in beiden Modi. Unterschiedlich ist das Ergebnis: LOL liefert Tabellenfeldwerte als untypisierte Rohstrings und row.row_id ist immer None, HOL liefert sie typisiert. Tabellenfeld-Werte werden als untypisierte Rohstrings geliefert und nicht in den deklarierten Python-Typ konvertiert. row_id ist für alle Tabellenzeilen aus LOL-Abfragen immer None.

Bedingungen auf eine Tabellenfeld-Spalte (Spalte einer Untertabelle / eines Mehrfachfeldes) werden über den Zugriff auf die Spalte am ECMTableField des Models geschrieben (z. B. Invoice.Positions). Der Builder erzeugt dann eine <TableCondition> / <TableColumn> statt einer flachen Feldbedingung, sodass der Server auf die Tabellenspalte filtert, anstatt ein unbekanntes Objektfeld abzulehnen.

# beliebige Zeile, deren ArticleNo == "A-100"
ecm.dms.select(Invoice).where(Invoice.Positions.ArticleNo == "A-100").execute()

ergibt:

<ConditionObject internal_name="Invoice">
    <TableCondition internal_name="Positions">
        <TableColumn internal_name="ArticleNo" operator="=">
            <Value>A-100</Value>
        </TableColumn>
    </TableCondition>
</ConditionObject>

Alle Vergleichs- und Mengenoperatoren funktionieren (==, !=, <, , >, >=, .in_(), .not_in(), .between()), und eine Tabellenspalten-Bedingung lässt sich im selben .where()-Aufruf mit gewöhnlichen Feldbedingungen kombinieren.

Um die Bedingung auf eine einzelne Zeile zu beschränken, wird das Tabellenfeld mit der Server-Zeilennummer indiziert — Invoice.Positions[3] (der explizite Alias Invoice.Positions.row(3) ist gleichwertig):

# Quantity in Zeile 3 == 5
ecm.dms.select(Invoice).where(Invoice.Positions[3].Quantity == 5).execute()
<TableCondition internal_name="Positions" row="3">
    <TableColumn internal_name="Quantity" operator="="><Value>5</Value></TableColumn>
</TableCondition>

Die Indizierung ist Klassenebenen-Abfragesyntax. Auf Instanzebene ist obj.Positions[i] ein 0-basierter Index in die geladenen Zeilen. Ohne Zeilenbeschränkung bedeuten zwei separate Tabellenspalten-Bedingungen auf derselben Tabelle „irgendeine Zeile erfüllt A" und „irgendeine Zeile erfüllt B" — möglicherweise unterschiedliche Zeilen. Tabellenspalten-Bedingungen erfordern ein typisiertes Model (generiert oder von Hand deklariert); bei dynamischen Models aus make_folder_model() stehen sie nicht zur Verfügung.

5.7. Kombinierte Abfragen über mehrere Objekttypen

Nur HOL. select_lol() bietet kein Äquivalent, siehe Modus wählen.

Bedingungen in .where() können gleichzeitig Felder verschiedener Objekttypen desselben Schranks referenzieren. Der Server gibt nur diejenigen Objekte zurück, bei denen alle Bedingungen erfüllt sind — unabhängig davon, auf welchen Objekttyp sich eine Bedingung bezieht.

Folgende Kombinationen sind möglich:

  • Suche nach einem Dokument mit Bedingungen auf Feldern des Dokuments selbst, seines Registers und seines Ordners.

  • Suche nach einem Register oder Ordner, in dem sich ein Unterobjekt befindet, das einer bestimmten Bedingung entspricht.

Bei verschachtelten Registern steht nur das unmittelbar übergeordnete Register zur Verfügung, nicht dessen Eltern-Register.

Dokument suchen — mit Bedingungen auf Register und Ordner:

  • Sync

  • Async

from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Register, Unittest_DMS_Document

for doc in (
    ecm.dms.select(Unittest_DMS_Document)
    .where(
        Unittest_DMS_Document.StringField == "Rechnung",
        Unittest_DMS_Register.Name == "Eingangsrechnungen",
        Unittest_DMS.Name == "Lieferant GmbH",
    )
    .stream()
):
    print(doc.system.id, doc.Name)
async for doc in (
    ecm.dms.select(Unittest_DMS_Document)
    .where(
        Unittest_DMS_Document.StringField == "Rechnung",
        Unittest_DMS_Register.Name == "Eingangsrechnungen",
        Unittest_DMS.Name == "Lieferant GmbH",
    )
    .stream()
):
    print(doc.system.id, doc.Name)

Ordner suchen — der ein Dokument mit bestimmten Eigenschaften enthält:

  • Sync

  • Async

from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Document

for folder in (
    ecm.dms.select(Unittest_DMS)
    .where(
        Unittest_DMS.Name == "Lieferant GmbH",
        Unittest_DMS_Document.StringField == "Rechnung",
    )
    .stream()
):
    print(folder.system.id, folder.Name)
async for folder in (
    ecm.dms.select(Unittest_DMS)
    .where(
        Unittest_DMS.Name == "Lieferant GmbH",
        Unittest_DMS_Document.StringField == "Rechnung",
    )
    .stream()
):
    print(folder.system.id, folder.Name)

5.8. Systemfelder als Bedingung

Neben den Indexfeldern lassen sich auch Systemfelder in .where() (und .order_by()) verwenden. Das system-Attribut ist dafür zugriffsabhängig:

  • Instanz (obj.system.id) liefert den geladenen Wert.

  • Klasse (Modell.system.id) liefert ein ECMField, das wie ein Indexfeld in Bedingungen einsetzbar ist.

Damit funktioniert derselbe punktierte Pfad in einer Abfrage. Häufigster Fall — alle Dokumente in einem bekannten Ordner laden, über eine schrankübergreifende Bedingung auf die Ordner-ID (Kombination mit dem vorigen Abschnitt):

  • Sync

  • Async

docs = (
    ecm.dms.select(Unittest_DMS_Document)
    .where(Unittest_DMS.system.id == folder.system.id)  # links Klasse (ECMField), rechts Instanzwert (int)
    .execute()
)
docs = await (
    ecm.dms.select(Unittest_DMS_Document)
    .where(Unittest_DMS.system.id == folder.system.id)
    .execute()
)

Die Bedingung wird an den Objekttyp gebunden, auf dem sie aufgerufen wird (Unittest_DMS), und wie eine Cross-Type-Bedingung in ein eigenes ConditionObject einsortiert. Systemfelder erhalten automatisch das system="1"-Attribut — identisch zu Unittest_DMS["OBJECT_ID"]:

<ConditionObject internal_name="Unittest_DMS">
    <FieldCondition internal_name="OBJECT_ID" operator="=" system="1">
        <Value>4711</Value>
    </FieldCondition>
</ConditionObject>

Abfragbar sind nur Systemfelder mit einer einzelnen Backing-Spalte. Verfügbar auf allen Objekttypen: id, owner_guid, creator, creation_date, last_modifier, last_modified, creation_time, deleted_at sowie die Teilmenge system.base_params.{creator, creation_date, modifier, modified_date, links_count, locked_user_id}. Typabhängig: system.folder_id, system.parent_register_id (Register); system.folder_id, system.register_id, system.register_type_id, system.system_id, system.foreign_id, system.lock_user_id, system.archivist, system.archiving_date und system.file_properties.{count, size} (Dokument).

Generische Alternative — beliebiges Systemfeld per Item-Zugriff: Modell["NAME"] (Subscript auf der Modellklasse) liefert ein ECMField für jeden Feldnamen. Ist NAME ein SystemFields-Member, wird automatisch system="1" gesetzt. Das deckt auch Systemfelder ohne .system-Eigenschaft ab (OBJECT_FLAGS, OBJECT_RETENTION, …) und funktioniert bei dynamischen Modellen ohne deklarierte Felder:

ecm.dms.select(Unittest_DMS_Document).where(Unittest_DMS["OBJECT_ID"] == folder.system.id)   # = Unittest_DMS.system.id
ecm.dms.select(Unittest_DMS_Document).where(Unittest_DMS["OBJECT_FLAGS"] == 0)

Der Item-Zugriff ist untypisiert (ECMField[str]); Modell.system.<feld> ist typisiert und entdeckbar, aber auf die kuratierte Teilmenge beschränkt. Beide erzeugen dieselbe Bedingung.

Auch als Sortierfeld: Dieselben Systemfelder funktionieren in .order_by() (HOL und LOL). Im Ergebnisfeld-Element wird system="1" automatisch gesetzt:

ecm.dms.select(Unittest_DMS_Document).order_by(Unittest_DMS_Document.system.id.DESC)
<Field internal_name="OBJECT_ID" system="1" sortpos="1" sortorder="DESC"/>

Aggregierte Werte ohne abfragbare Serverspalte (system.rights, system.name, system.is_modified, system.base_params.version, system.file_properties.extension u. a.) sind ausschließlich auf geladenen Instanzen verfügbar und können nicht als Bedingung verwendet werden. Vollständige Liste aller system-Eigenschaften: ECM-Modell-Referenz.

6. Volltextsuche

Die hier gezeigte Variante pro Objekttyp funktioniert in beiden Modi. Die archivweite Variante (<FulltextQuery>, Treffer über mehrere Objekttypen) ist nur HOL.

.fulltext(term) ergänzt eine <Fulltext>-Bedingung am ConditionObject des abgefragten Objekttyps. Sie ist eine zusätzliche Restriktion und kann mit .where(…​)-Bedingungen kombiniert werden. Voraussetzung: am enaio®-Server ist eine Volltext-Engine konfiguriert und der Objekttyp ist indexiert.

Wird kein Suchtext geliefert oder ist keine Suchmaschine konfiguriert, antwortet der Server mit einer Fehlermeldung im <Messages>-Block des Trefferlisten-XML (z. B. „The full-text search could not be performed because no search text was specified.“).

  • Sync

  • Async

for doc in (
    ecm.dms.select(MedicalLetter)
    .fulltext("Meningitis")
    .where(MedicalLetter.SeniorPhysician == "Müller")
    .stream()
):
    print(doc.system.id, doc.Type)
async for doc in (
    ecm.dms.select(MedicalLetter)
    .fulltext("Meningitis")
    .where(MedicalLetter.SeniorPhysician == "Müller")
    .stream()
):
    print(doc.system.id, doc.Type)

6.1. RetrievalWare-Engine-Parameter

Optional können RetrievalWare-Engine-Attribute als Keyword-Argumente gesetzt werden. Sie wirken nur, wenn am Server RetrievalWare als Volltext-Engine im Einsatz ist. Nicht gesetzte Parameter verwenden die Server-Defaults.

Parameter Server-Default Beschreibung

mode

PATTERN

Suchmodus. Erlaubte Werte: BOOLEAN, PATTERN, CONCEPT.

expansion_level

4

Wort-Expansionslevel für Thesaurus-Lookups.

fuzzy_spell_half_words

False

Fuzzy-Spelling für Halbwörter aktivieren.

fuzzy_spell_threshold

0

Ähnlichkeitsschwelle für Fuzzy-Spelling.

max_fuzzy_spell

15

Maximale Anzahl Fuzzy-Spelling-Treffer.

max_reg_expr

4

Maximale Anzahl Expansionen regulärer Ausdrücke.

warn_max_reg_expr

False

Warnung ausgeben, wenn das Expansionslimit erreicht ist.

word_expansion_limit

20

Maximale Anzahl Wort-Expansionen.

ecm.dms.select(MedicalLetter).fulltext(
    "Meningitis OR Encephalitis",
    mode="BOOLEAN",
    expansion_level=2,
).execute()

7. Sortierung

Die Sortierung wird über .order_by() festgelegt. Jedes Argument ist ein ECMSortOrder, erzeugt über .ASC oder .DESC auf einem ECMField auf Klassenebene. Die Reihenfolge der Argumente bestimmt die Sortierpriorität.

  • Sync

  • Async

# Primär nach Jahr absteigend, sekundär nach Titel aufsteigend
for folder in (
    ecm.dms.select(InvoiceFolder)
    .order_by(InvoiceFolder.Year.DESC, InvoiceFolder.Title.ASC)
    .stream()
):
    print(folder.Year, folder.Title)
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .order_by(InvoiceFolder.Year.DESC, InvoiceFolder.Title.ASC)
    .stream()
):
    print(folder.Year, folder.Title)

7.1. Keyset-Pagination über Systemfelder

.fields() und .order_by() akzeptieren auch Systemfelder über den Klassen-Zugriff (Modell.system.<feld>, siehe Modell-Referenz). Systemfelder erhalten im erzeugten <Field>-Element automatisch das system="1"-Attribut; ein Feld, das gleichzeitig selektiert und sortiert wird, erzeugt nur einen <Field>-Eintrag.

Damit lässt sich z. B. eine stabile Keyset-Pagination über die Objekt-ID bauen:

  • Sync

  • Async

cursor: int | None = None
batch = 200
while True:
    query = (
        ecm.dms.select_lol(InvoiceDocument)
        .fields(InvoiceDocument.system.id)
        .order_by(InvoiceDocument.system.id.DESC)
        .limit(batch)
        .pagesize(batch)
    )
    if cursor is not None:
        query = query.where(InvoiceDocument.system.id < cursor)
    docs = query.execute()
    if not docs:
        break
    for doc in docs:
        ...  # Batch verarbeiten
    cursor = docs[-1].system.id
cursor: int | None = None
batch = 200
while True:
    query = (
        ecm.dms.select_lol(InvoiceDocument)
        .fields(InvoiceDocument.system.id)
        .order_by(InvoiceDocument.system.id.DESC)
        .limit(batch)
        .pagesize(batch)
    )
    if cursor is not None:
        query = query.where(InvoiceDocument.system.id < cursor)
    docs = await query.execute()
    if not docs:
        break
    for doc in docs:
        ...  # Batch verarbeiten
    cursor = docs[-1].system.id

8. Paginierung

Mit .limit(), .pagesize() und .offset() wird die Ergebnismenge eingeschränkt und die interne Seitennavigation gesteuert.

Methode Standard Beschreibung

.limit(n)

unbegrenzt

Maximale Gesamtanzahl Treffer über alle Seiten.

.pagesize(n)

1000

Anzahl Objekte pro Server-Anfrage. Kleinere Werte reduzieren den Speicherbedarf pro Seite, erhöhen aber die Anzahl der Anfragen. Größere Werte können den Server überlasten — siehe Hinweis unten.

.offset(n)

0

Überspringt die ersten n Treffer. Nützlich für manuelle Seitennavigation.

  • Sync

  • Async

# Seite 3 mit je 20 Einträgen (Offset = 2 × 20 = 40)
for folder in (
    ecm.dms.select(InvoiceFolder)
    .order_by(InvoiceFolder.Year.DESC)
    .limit(20)
    .offset(40)
    .stream()
):
    print(folder.system.id, folder.Title)
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .order_by(InvoiceFolder.Year.DESC)
    .limit(20)
    .offset(40)
    .stream()
):
    print(folder.system.id, folder.Title)

Eine zu große pagesize kann den Server zum Absturz bringen. Der Speicherverbrauch pro Seite steigt proportional zur Anzahl der Objekte multipliziert mit den angeforderten Metadaten. Besonders kritisch ist die Kombination großer Seiten mit .rights(), .base_params(), .file_properties() oder .variants(), da der Server für jedes Objekt zusätzliche Datenbankabfragen durchführt.

Als Richtwert gilt: Den Standard von 1000 beibehalten und nur bei belegten Performanceproblemen anpassen — dann eher verkleinern.

9. Felder einschränken (fields)

Die Einschränkung betrifft die Indexfelder. Die Standard-Systemfelder, darunter die Ablagefelder des Objekts, bleiben in beiden Modi in der Anfrage, siehe Ordner und Register eines Treffers.

Mit .fields() werden nur die angegebenen Indexfelder vom Server geladen. Der Server setzt dabei intern field_schema="MIN" und überträgt ausschließlich die explizit aufgeführten Felder. Systemfelder (system.id, system.name etc.) und Sortierfelder werden immer mitgeliefert, unabhängig von dieser Liste.

Das reduziert die übertragene Datenmenge erheblich, wenn nur wenige Felder benötigt werden. Nicht angeforderte Felder sind am Objekt None.

Felder können als ECMField-Klassenattribut oder als interner Feldname (String) übergeben werden. Ein Aufruf ohne Argumente hebt die Einschränkung wieder auf.

  • Sync

  • Async

# Nur Title und Year laden — alle anderen Felder sind None
for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields(InvoiceFolder.Title, InvoiceFolder.Year)
    .stream()
):
    print(folder.Title, folder.Year)
    # folder.Status ist None (nicht geladen)
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields(InvoiceFolder.Title, InvoiceFolder.Year)
    .stream()
):
    print(folder.Title, folder.Year)

Felder können alternativ als Strings übergeben werden, wenn kein typisiertes Model vorliegt:

  • Sync

  • Async

from ecmind_blue_client.ecm.model import make_folder_model

InvoiceFolder = make_folder_model("InvoiceFolder")

for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields("Title", "Year")
    .stream()
):
    print(folder["Title"], folder["Year"])
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields("Title", "Year")
    .stream()
):
    print(folder["Title"], folder["Year"])

10. Transport der Trefferliste

dms.GetResultList liefert die Trefferliste auf zwei Wegen: inline als BASE64-Parameter XML (Flags=0) oder als Antwortdatei (Flags=16). Der Client fragt standardmässig den Parameter an, weil das eine Netzwerk-Rundreise pro Seite spart: der Server schreibt Parameterblock und Ergebnis in einem Durchgang, während er bei der Datei zweimal schreibt und der Client auf den 32-Byte-Dateiheader warten muss.

Gemessen gegen einen Testserver mit 20 ms RTT:

Szenario Antwortdatei Parameter Differenz

Erste Seite, pagesize(10)

94,5 ms

53,3 ms

-44 %

1471 Zeilen, pagesize(10)

13 020 ms

8 238 ms

-37 %

1471 Zeilen, pagesize(1000)

339 ms

278 ms

-18 %

Die Seitengrösse wirkt dabei deutlich stärker als der Transport: dieselben 1471 Zeilen kosten mit pagesize(10) 146 Server-Anfragen, mit pagesize(1000) nur zwei. Der Standard von 1000 ist bewusst hoch gewählt; ihn zu senken ist der teuerste Fehler in einer Listenabfrage.

.result_as_file() bleibt für den Fall, dass eine einzelne Seite grösser wird als file_cache_byte_limit des Pool-Clients (Standard 32 MiB). Die RPC-Schicht legt die Antwort dann als temporäre Datei ab, und .stream() bzw. .execute() lesen sie inkrementell von dort, sodass die Trefferliste nie vollständig in den Speicher muss. Unterhalb dieser Grenze wird die Datei ohnehin im Speicher gepuffert und die Option kostet nur die zusätzliche Rundreise.

# Standard: inline, eine Rundreise pro Seite
for folder in ecm.dms.select_lol(InvoiceFolder).pagesize(1000).stream():
    ...

# Sehr grosse Einzelseite: über eine temporäre Datei, konstanter Speicherbedarf
for folder in ecm.dms.select_lol(InvoiceFolder).pagesize(200_000).result_as_file().stream():
    ...

.stream() verarbeitet jede Seite inkrementell und verwirft jede Zeile, sobald das Modell daraus erzeugt ist. Der Speicherbedarf bleibt damit unabhängig von der Seitengrösse konstant (gemessen 1,2 MB für ein 44 MB grosses Antwortdokument mit 74 250 Zeilen, gegenüber 223 MB beim vollständigen Aufbau des XML-Baums). .execute() hält am Ende naturgemäss alle Modelle im Speicher; bei sehr großen Ergebnismengen ist .stream() deshalb vorzuziehen.

Nicht verfügbar ist beim inkrementellen Lesen alles, was im Dokument hinter den Treffern steht, also <Statistics> und <Messages>. Die Query-Methoden greifen darauf nicht zu; wer diese Abschnitte braucht, verwendet DmsContentParser direkt.

11. Rechteinformationen

Mit .rights() werden die Zugriffsrechte des angemeldeten Benutzers für jedes Objekt abgerufen. Die Rechte stehen danach in obj.system.rights als ECMModelRights bereit.

.rights() fordert zusätzlich die Einfüge-Kontingente (object_inserts) an, weil der Server das Einfügerecht sonst für jedes Objekt als nicht gewährt meldet. Das kostet weitere Serverabfragen pro Objekt und ist bei grossen Seiten mit einzuplanen.

Attribut Beschreibung

insert

Darf Kindobjekte anlegen (Register/Dokumente in einem Ordner, Dokumente in einem Register).

edit_metadata

Darf Indexfelder des Objekts ändern.

read_file

Darf die Datei des Dokuments lesen.

edit_file

Darf die Datei des Dokuments ändern.

delete

Darf das Objekt löschen.

  • Sync

  • Async

for folder in ecm.dms.select(InvoiceFolder).rights().stream():
    r = folder.system.rights
    if r.edit_metadata:
        print(f"{folder.system.id}: Bearbeitung erlaubt")
    if not r.delete:
        print(f"{folder.system.id}: Löschen nicht erlaubt")
async for folder in ecm.dms.select(InvoiceFolder).rights().stream():
    r = folder.system.rights
    if r.edit_metadata:
        print(f"{folder.system.id}: Bearbeitung erlaubt")
    if not r.delete:
        print(f"{folder.system.id}: Löschen nicht erlaubt")

12. Basisparameter

Im LOL-Modus sind dieselben Werte über Systemfelder erreichbar:

.base_params() ist nicht verfügbar — obj.system.base_params ist immer None. Audit-Metadaten sind nur zugänglich, wenn die entsprechenden Systemfelder über ecm_system_fields am Modell deklariert sind:

Systemfeld Bedeutung

OBJECT_CRID

Ersteller-ID

OBJECT_CRDATE

Erstellungsdatum

OBJECT_USERGUID

GUID des Eigentümers

OBJECT_MODIFYUSER

Letzter Bearbeiter

OBJECT_MODIFYTIME

Datum der letzten Änderung

OBJECT_LINKS

Anzahl Verlinkungen

OBJECT_TXTNOTICECOUNT

Anzahl Textnotizen

Nur HOL. select_lol() bietet kein Äquivalent, siehe Modus wählen.

Mit .base_params() werden Verwaltungsinformationen des Servers für jedes Objekt abgerufen. Die Daten stehen in obj.system.base_params als ECMModelBaseParams bereit.

Attribut Beschreibung

creator

Benutzername des Erstellers.

creation_date

Datum der Erstellung.

owner

Aktueller Eigentümer des Objekts.

modifier

Benutzername der letzten Änderung.

modified_date

Zeitstempel der letzten Änderung.

links_count

Anzahl der Verlinkungen.

text_notice_count

Anzahl der Textnotizen.

  • Sync

  • Async

for folder in ecm.dms.select(InvoiceFolder).base_params().stream():
    bp = folder.system.base_params
    print(f"Erstellt von {bp.creator} am {bp.creation_date}")
    print(f"Zuletzt geändert von {bp.modifier} am {bp.modified_date}")
async for folder in ecm.dms.select(InvoiceFolder).base_params().stream():
    bp = folder.system.base_params
    print(f"Erstellt von {bp.creator} am {bp.creation_date}")
    print(f"Zuletzt geändert von {bp.modifier} am {bp.modified_date}")

13. Dateiinformationen

Im LOL-Modus beschränken sich Dateiinformationen auf das, was die Systemfelder tragen:

.file_properties() steht nicht zur Verfügung. Teilweise Datei-Metadaten sind nur über Systemfelder zugänglich, die explizit im Modell deklariert werden müssen:

Systemfeld Bedeutung

OBJECT_FILESIZE

Dateigröße in Bytes

OBJECT_COUNT

Anzahl der Dateien

OBJECT_DOCPAGECOUNT

Anzahl der Dokumentseiten

Dateiendung, MIME-Typ und MIME-Gruppe werden im LOL-Modus vom Server grundsätzlich nicht zurückgegeben.

Nur HOL. select_lol() bietet kein Äquivalent, siehe Modus wählen.

Mit .file_properties() werden Metadaten zur Datei eines Dokuments abgerufen. Nur für ECMDocumentModel-Typen verfügbar; obj.system.file_properties liefert eine ECMModelFileProperties-Instanz.

Attribut Beschreibung

count

Anzahl der Dateien (Haupt- und Nebendateien).

size

Dateigröße in Bytes.

extension

Dateiendung (z.B. pdf, docx).

mimetype

MIME-Typ (z.B. application/pdf).

mimetypegroup

MIME-Gruppe (z.B. application).

iconid

ID des Dateityp-Icons.

documentpagecount

Anzahl der Seiten (sofern bekannt).

  • Sync

  • Async

for doc in ecm.dms.select(InvoiceDocument).file_properties().stream():
    fp = doc.system.file_properties
    print(f"{doc.system.id}: {fp.extension}, {fp.size} Bytes, {fp.documentpagecount} Seiten")
async for doc in ecm.dms.select(InvoiceDocument).file_properties().stream():
    fp = doc.system.file_properties
    print(f"{doc.system.id}: {fp.extension}, {fp.size} Bytes, {fp.documentpagecount} Seiten")

14. Varianten

Nur HOL. select_lol() bietet kein Äquivalent, siehe Modus wählen.

Mit .variants() werden die Versionsverzweigungen eines Dokuments aus dem W-Modul abgerufen. Nur für Dokumenttypen mit aktiviertem W-Modul relevant. Das Ergebnis steht in obj.system.variants als Liste von ECMModelDocumentVariant-Instanzen bereit.

Jede Variante hat die Attribute doc_id, doc_ver, is_active, doc_parent, children und level (Verschachtelungstiefe, 0 für die Einträge in obj.system.variants).

  • Sync

  • Async

for doc in ecm.dms.select(InvoiceDocument).variants().stream():
    for variant in doc.system.variants:
        active = "✓" if variant.is_active else " "
        print(f"[{active}] {variant.doc_ver} (ID {variant.doc_id})")
async for doc in ecm.dms.select(InvoiceDocument).variants().stream():
    for variant in doc.system.variants:
        active = "✓" if variant.is_active else " "
        print(f"[{active}] {variant.doc_ver} (ID {variant.doc_id})")

15. Notizen

Nur HOL. select_lol() bietet kein Äquivalent, siehe Modus wählen.

Mit .remarks() werden die Notizen eines Objekts mitgeliefert. Die Daten stehen in obj.system.remarks bereit.

  • Sync

  • Async

for folder in ecm.dms.select(InvoiceFolder).remarks().stream():
    for remark in folder.system.remarks:
        print(remark)
async for folder in ecm.dms.select(InvoiceFolder).remarks().stream():
    for remark in folder.system.remarks:
        print(remark)

16. Papierkorb

.garbage_mode() schränkt die Abfrage auf gelöschte Objekte ein. Ohne diesen Aufruf werden nur nicht gelöschte Objekte zurückgegeben.

  • Sync

  • Async

# Alle gelöschten Rechnungsordner auflisten
for folder in ecm.dms.select(InvoiceFolder).garbage_mode().stream():
    print(f"Gelöscht: {folder.system.id} – {folder.Title}")
async for folder in ecm.dms.select(InvoiceFolder).garbage_mode().stream():
    print(f"Gelöscht: {folder.system.id} – {folder.Title}")

17. Ordner und Register eines Treffers

Die Ablage-Systemfelder eines Dokuments (SDSTA_ID, SDREG_ID, SDREG_TYPE) gehören in beiden Modi zum Standardfeldsatz, brauchen also keinen .fields()-Eintrag und sind über den system-Namespace jedes Treffers lesbar:

  • Sync

  • Async

for document in ecm.dms.select_lol(InvoiceDocument).stream():
    print(document.system.id, document.system.folder_id, document.system.register_id)
async for document in ecm.dms.select_lol(InvoiceDocument).stream():
    print(document.system.id, document.system.folder_id, document.system.register_id)

Das gilt auch, wenn .fields() das Ergebnis einschränkt: die Einschränkung betrifft die Indexfelder, der Systemblock bleibt unverändert. Bei einem Register-Modell sind die entsprechenden Felder REG_STAID (system.folder_id) und REG_PARID (system.parent_register_id).

Dieselben Felder funktionieren als Bedingung und als Sortierkriterium, etwa für alle Dokumente in einem bekannten Ordner:

  • Sync

  • Async

documents = (
    ecm.dms.select_lol(InvoiceDocument)
    .where(InvoiceDocument.system.folder_id == folder.system.id)
    .order_by(InvoiceDocument.system.id.ASC)
    .execute()
)
documents = await (
    ecm.dms.select_lol(InvoiceDocument)
    .where(InvoiceDocument.system.folder_id == folder.system.id)
    .order_by(InvoiceDocument.system.id.ASC)
    .execute()
)

Zu beachten ist die Asymmetrie: links vom Vergleich steht der Klassenzugriff, der ein ECMField liefert, rechts ein Instanzwert. Ein Systemfeld ohne .system-Eigenschaft ist über den Item-Zugriff erreichbar, InvoiceDocument["SDSTA_ID"].

18. Untergeordnete Objekte (with_children)

Nur HOL. select_lol() bietet kein Äquivalent, siehe Modus wählen.

.with_children() wechselt zu einer HOL-Abfrage, die zusammen mit jedem Hauptobjekt seine Kindobjekte liefert. Der Aufruf gibt einen ECMModelQueryHolSync zurück. Vor execute() oder stream() muss .limit() gesetzt sein, da HOL-Antworten keine Paginierung unterstützen.

Jedes Ergebnis ist ein ECMHolResult mit:

  • .main — das Hauptobjekt (z.B. der Ordner)

  • .children_of(ECMChildSpec(ChildModel)) — Liste der Kindobjekte dieses Typs

  • Sync

  • Async

from ecmind_blue_client.ecm.model import ECMChildSpec

results = (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year == 2024)
    .with_children(ECMChildSpec(InvoiceDocument))
    .limit(100)
    .execute()
)
for r in results:
    print(f"Ordner {r.main.Title}:")
    for doc in r.children_of(ECMChildSpec(InvoiceDocument)):
        print(f"  Dokument {doc.system.id}")
from ecmind_blue_client.ecm.model import ECMChildSpec

results = await (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year == 2024)
    .with_children(ECMChildSpec(InvoiceDocument))
    .limit(100)
    .execute()
)
for r in results:
    print(f"Ordner {r.main.Title}:")
    for doc in r.children_of(ECMChildSpec(InvoiceDocument)):
        print(f"  Dokument {doc.system.id}")

19. Übergeordnete Objekte (with_parents)

Nur HOL. select_lol() bietet kein Äquivalent, siehe Modus wählen.

.with_parents() kehrt die Antwortstruktur um: Der äußerste Elterntyp (erster Spec) wird zum Hauptobjekt result.main. Das ursprünglich abgefragte Objekt und alle Zwischenebenen sind über .children_of() abrufbar.

Die Specs werden von außen nach innen übergeben (z.B. zuerst Ordner, dann Register).

  • Sync

  • Async

from ecmind_blue_client.ecm.model import ECMChildSpec, ECMParentSpec

results = (
    ecm.dms.select(InvoiceDocument)
    .where(InvoiceDocument.Status == "Freigegeben")
    .with_parents(ECMParentSpec(InvoiceFolder), ECMParentSpec(InvoiceRegister))
    .limit(50)
    .execute()
)
for r in results:
    folder   = r.main                                          # InvoiceFolder
    register = r.children_of(ECMChildSpec(InvoiceRegister))[0]
    doc      = r.children_of(ECMChildSpec(InvoiceDocument))[0]
    print(f"{folder.Title} → {register.Title} → Dok {doc.system.id}")
from ecmind_blue_client.ecm.model import ECMChildSpec, ECMParentSpec

results = await (
    ecm.dms.select(InvoiceDocument)
    .where(InvoiceDocument.Status == "Freigegeben")
    .with_parents(ECMParentSpec(InvoiceFolder), ECMParentSpec(InvoiceRegister))
    .limit(50)
    .execute()
)
for r in results:
    folder   = r.main
    register = r.children_of(ECMChildSpec(InvoiceRegister))[0]
    doc      = r.children_of(ECMChildSpec(InvoiceDocument))[0]
    print(f"{folder.Title} → {register.Title} → Dok {doc.system.id}")

20. Beispiele

20.1. Einfache Abfrage mit Filter und Sortierung

  • Sync

  • Async

from ecmind_blue_client.ecm.model import ECMFolderModel, ECMField

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

for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year >= 2020)
    .order_by(InvoiceFolder.Year.DESC)
    .stream()
):
    print(folder.system.id, folder.Title)
from ecmind_blue_client.ecm.model import ECMFolderModel, ECMField

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

async for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(InvoiceFolder.Year >= 2020)
    .order_by(InvoiceFolder.Year.DESC)
    .stream()
):
    print(folder.system.id, folder.Title)

20.2. Kombinierte Bedingungen (AND / OR)

  • Sync

  • Async

for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(
        (InvoiceFolder.Year >= 2020) & (InvoiceFolder.Year <= 2024),
        InvoiceFolder.Title == "Rechnung",
    )
    .stream()
):
    print(folder.Title, folder.Year)
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .where(
        (InvoiceFolder.Year >= 2020) & (InvoiceFolder.Year <= 2024),
        InvoiceFolder.Title == "Rechnung",
    )
    .stream()
):
    print(folder.Title, folder.Year)

20.3. Limit, Offset und Seitengröße

  • Sync

  • Async

# Maximal 50 Treffer, ab Position 100, in Paketen von 25 pro Server-Anfrage
for folder in (
    ecm.dms.select(InvoiceFolder)
    .limit(50)
    .offset(100)
    .pagesize(25)
    .stream()
):
    print(folder.system.id)
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .limit(50)
    .offset(100)
    .pagesize(25)
    .stream()
):
    print(folder.system.id)

20.4. Rechte und Audit-Metadaten

  • Sync

  • Async

for folder in (
    ecm.dms.select(InvoiceFolder)
    .rights()
    .base_params()
    .stream()
):
    print(folder.system.rights)
    print(folder.system.base_params)
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .rights()
    .base_params()
    .stream()
):
    print(folder.system.rights)
    print(folder.system.base_params)

20.5. Selektive Felder

Mit .fields() werden nur die angegebenen Felder vom Server geladen. Das reduziert die Datenmenge erheblich, wenn nur einzelne Felder benötigt werden.

  • Sync

  • Async

for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields(InvoiceFolder.Title, InvoiceFolder.Year)
    .stream()
):
    print(folder.Title, folder.Year)
    # folder.OtherField wäre None (nicht geladen)
async for folder in (
    ecm.dms.select(InvoiceFolder)
    .fields(InvoiceFolder.Title, InvoiceFolder.Year)
    .stream()
):
    print(folder.Title, folder.Year)

20.6. Papierkorb abfragen

  • Sync

  • Async

# Nur gelöschte Objekte
for folder in ecm.dms.select(InvoiceFolder).garbage_mode().stream():
    print(folder.system.id, folder.Title)
async for folder in ecm.dms.select(InvoiceFolder).garbage_mode().stream():
    print(folder.system.id, folder.Title)

20.7. Generisches Model (ohne Klassendefinition)

Wenn der Objekttyp erst zur Laufzeit bekannt ist, kann ein Model dynamisch erzeugt werden:

  • Sync

  • Async

from ecmind_blue_client.ecm.model import make_folder_model

InvoiceFolder = make_folder_model("InvoiceFolder")

for folder in ecm.dms.select(InvoiceFolder).stream():
    print(folder.system.id, folder["Title"])
from ecmind_blue_client.ecm.model import make_folder_model

InvoiceFolder = make_folder_model("InvoiceFolder")

async for folder in ecm.dms.select(InvoiceFolder).stream():
    print(folder.system.id, folder["Title"])

20.8. Kombinierte Abfrage über Dokument, Register und Ordner

Nur HOL. select_lol() bietet kein Äquivalent, siehe Modus wählen.
  • Sync

  • Async

from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Register, Unittest_DMS_Document

for doc in (
    ecm.dms.select(Unittest_DMS_Document)
    .where(
        Unittest_DMS_Document.StringField == "Rechnung",
        Unittest_DMS_Register.Name == "Eingangsrechnungen",
        Unittest_DMS.Name == "Lieferant GmbH",
    )
    .stream()
):
    print(doc.system.id, doc.Name)
from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Register, Unittest_DMS_Document

async for doc in (
    ecm.dms.select(Unittest_DMS_Document)
    .where(
        Unittest_DMS_Document.StringField == "Rechnung",
        Unittest_DMS_Register.Name == "Eingangsrechnungen",
        Unittest_DMS.Name == "Lieferant GmbH",
    )
    .stream()
):
    print(doc.system.id, doc.Name)

20.9. Ordner suchen, der ein passendes Unterobjekt enthält

Nur HOL. select_lol() bietet kein Äquivalent, siehe Modus wählen.
  • Sync

  • Async

from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Document

for folder in (
    ecm.dms.select(Unittest_DMS)
    .where(
        Unittest_DMS.Name == "Lieferant GmbH",
        Unittest_DMS_Document.StringField == "Rechnung",
    )
    .stream()
):
    print(folder.system.id, folder.Name)
from tests.models.Unittest_DMS import Unittest_DMS, Unittest_DMS_Document

async for folder in (
    ecm.dms.select(Unittest_DMS)
    .where(
        Unittest_DMS.Name == "Lieferant GmbH",
        Unittest_DMS_Document.StringField == "Rechnung",
    )
    .stream()
):
    print(folder.system.id, folder.Name)

20.10. Eine flache LOL-Abfrage

  • Sync

  • Async

for folder in (
    ecm.dms.select_lol(InvoiceFolder)
    .where(InvoiceFolder.Year >= 2020)
    .order_by(InvoiceFolder.Year.DESC)
    .stream()
):
    print(folder.system.id, folder.Title)
async for folder in (
    ecm.dms.select_lol(InvoiceFolder)
    .where(InvoiceFolder.Year >= 2020)
    .order_by(InvoiceFolder.Year.DESC)
    .stream()
):
    print(folder.system.id, folder.Title)