Share via


Fejlesztői útmutató tartós entitásokhoz a .NET-ben

Ebben a cikkben részletesen ismertetjük a tartós entitások .NET-tel való fejlesztéséhez rendelkezésre álló felületeket, beleértve a példákat és az általános tanácsokat.

Az entitásfüggvények segítségével a kiszolgáló nélküli alkalmazásfejlesztők kényelmesen rendszerezhetik az alkalmazásállapotokat részletes entitások gyűjteményeként. Az alapul szolgáló fogalmakkal kapcsolatos további információkért tekintse meg a Durable Entities: Concepts (Tartós entitások: Fogalmak ) című cikket.

Jelenleg két API-t kínálunk entitások definiáláshoz:

  • Az osztályalapú szintaxis az entitásokat és a műveleteket osztályként és metódusként jelöli. Ez a szintaxis könnyen olvasható kódot hoz létre, és lehetővé teszi a műveletek meghívását típus-ellenőrzött módon az interfészeken keresztül.

  • A függvényalapú szintaxis egy alsó szintű felület, amely függvényként jelöli az entitásokat. Pontos vezérlést biztosít az entitásműveletek feladásának módjáról és az entitás állapotának kezeléséről.

Ez a cikk elsősorban az osztályalapú szintaxisra összpontosít, mivel várhatóan jobban megfelel a legtöbb alkalmazásnak. A függvényalapú szintaxis azonban megfelelő lehet azoknak az alkalmazásoknak, amelyek saját absztrakcióikat szeretnék definiálni vagy kezelni az entitásállapothoz és a műveletekhez. Emellett olyan kódtárak implementálásához is alkalmazható, amelyek általánosságot igényelnek, és amelyeket az osztályalapú szintaxis jelenleg nem támogat.

Megjegyzés:

Az osztályalapú szintaxis csak egy réteg a függvényalapú szintaxis tetején, így mindkét változat felcserélhető ugyanabban az alkalmazásban.

Entitásosztályok definiálása

Az alábbi példa egy Counter olyan entitás implementációja, amely egyetlen egész típusú értéket tárol, és négy műveletet Addkínál , ResetGetés Delete.

[JsonObject(MemberSerialization.OptIn)]
public class Counter
{
    [JsonProperty("value")]
    public int Value { get; set; }

    public void Add(int amount) 
    {
        this.Value += amount;
    }

    public Task Reset() 
    {
        this.Value = 0;
        return Task.CompletedTask;
    }

    public Task<int> Get() 
    {
        return Task.FromResult(this.Value);
    }

    public void Delete() 
    {
        Entity.Current.DeleteState();
    }

    [FunctionName(nameof(Counter))]
    public static Task Run([EntityTrigger] IDurableEntityContext ctx)
        => ctx.DispatchAsync<Counter>();
}

A Run függvény tartalmazza az osztályalapú szintaxis használatához szükséges sablont. Statikus Azure-függvénynek kell lennie. Az entitás által feldolgozott minden műveletüzenethez egyszer hajtja végre. Amikor DispatchAsync<T> a rendszer meghívja, és az entitás még nincs a memóriában, létrehoz egy típusú T objektumot, és feltölti a mezőket a tárolóban található utolsó tartós JSON-ból (ha van ilyen). Ezután meghívja a metódust az egyező névvel.

A EntityTrigger mintában szereplő függvénynek Run nem kell magában az entitásosztályban tartózkodnia. Az Azure-függvények bármely érvényes helyén elhelyezhető: a legfelső szintű névtérben vagy egy legfelső szintű osztályban. Ha azonban mélyebben vannak beágyazva (például a függvény beágyazott osztályban van deklarálva), akkor a legújabb futtatókörnyezet nem fogja felismerni ezt a függvényt.

Megjegyzés:

Az osztályalapú entitás állapota implicit módon jön létre, mielőtt az entitás feldolgozna egy műveletet, és a hívással Entity.Current.DeleteState()explicit módon törölhető egy műveletben.

Megjegyzés:

Az entitások izolált modellben való futtatásához az Azure Functions Core Tools vagy újabb verzióra 4.0.5455 van szüksége.

Az entitásokat kétféleképpen lehet osztályként definiálni a C# izolált feldolgozói modellben. Különböző állapot szerializálási struktúrákkal rendelkező entitásokat hoznak létre.

Az alábbi megközelítéssel a teljes objektum szerializálva lesz egy entitás definiálásakor.

public class Counter
{
    public int Value { get; set; }

    public void Add(int amount) 
    {
        this.Value += amount;
    }

    public Task Reset() 
    {
        this.Value = 0;
        return Task.CompletedTask;
    }

    public Task<int> Get() 
    {
        return Task.FromResult(this.Value);
    }

    // Delete is implicitly defined when defining an entity this way

    [Function(nameof(Counter))]
    public static Task Run([EntityTrigger] TaskEntityDispatcher dispatcher)
        => dispatcher.DispatchAsync<Counter>();
}

Egy TaskEntity<TState>-alapú implementáció, amely megkönnyíti a függőséginjektálás használatát. Ebben az esetben az állapot deszerializálva van a State tulajdonsághoz, és nincs más tulajdonság szerializálva/deszerializálva.

public class Counter : TaskEntity<int>
{
    readonly ILogger logger; 

    public Counter(ILogger<Counter> logger)
    {
        this.logger = logger; 
    }

    public int Add(int amount) 
    {
        this.State += amount;
    }

    public Reset() 
    {
        this.State = 0;
        return Task.CompletedTask;
    }

    public Task<int> Get() 
    {
        return Task.FromResult(this.State);
    }

    // Delete is implicitly defined when defining an entity this way

    [Function(nameof(Counter))]
    public static Task Run([EntityTrigger] TaskEntityDispatcher dispatcher)
        => dispatcher.DispatchAsync<Counter>();
}

Figyelmeztetés:

Ha olyan entitásokat ír, amelyek származnak ITaskEntity vagy TaskEntity<TState>származnak, fontos, hogy ne nevezze el az entitás-eseményindító metódustRunAsync. Ez futásidejű hibákat fog okozni az entitás meghívásakor, mivel a "RunAsync" metódusnévvel nem egyértelmű egyezés van, mert ITaskEntity már definiált egy példányszintű "RunAsync" értéket.

Entitások törlése az izolált modellben

Egy entitás törlése az izolált modellben úgy történik, hogy az entitás állapotát a következőre nullállítja. Ennek végrehajtása attól függ, hogy milyen entitás-megvalósítási útvonalat használ.

  • Ha függvényalapú szintaxisból ITaskEntityszármazik vagy használ, a törlés hívással TaskEntityOperation.State.SetState(null)történik.
  • A származtatáskor TaskEntity<TState>a törlés implicit módon van definiálva. Ezt azonban felül lehet bírálni egy metódus Delete definiálásával az entitáson. Az állapot bármely műveletből törölhető a használatával this.State = null.
    • A null értékre állítással történő törléshez null értékűnek kell TState lennie.
    • Az implicit módon definiált törlési művelet nem null értékű TStateműveletet töröl.
  • Ha poCO-t használ állapotként (nem abból TaskEntity<TState>származik), a törlés implicit módon van definiálva. A törlési művelet felülbírálható egy metódus Delete meghatározásával a POCO-n. A POCO-útvonalon azonban nem lehet állapotot null beállítani, így az implicit módon definiált törlési művelet az egyetlen igaz törlés.

Osztálykövetelmények

Az entitásosztályok olyan POCO-k (egyszerű régi CLR-objektumok), amelyek nem igényelnek speciális szuperosztályokat, interfészeket vagy attribútumokat. Azonban:

Ezenkívül minden olyan metódusnak, amelyet műveletként kíván meghívni, meg kell felelnie az egyéb követelményeknek:

  • Egy műveletnek legfeljebb egy argumentumot kell tartalmaznia, és nem lehetnek túlterhelések vagy általános típusú argumentumok.
  • Egy felület használatával történő vezénylésből meghívandó műveletnek vissza kell térnie vagy Task<T>.Task
  • Az argumentumoknak és a visszatérési értékeknek szerializálható értékeknek vagy objektumoknak kell lenniük.

Mit tehetnek a műveletek?

Minden entitásművelet képes az entitás állapotának olvasására és frissítésére, az állapot módosításai pedig automatikusan megmaradnak a tárolóban. Emellett a műveletek külső I/O- vagy egyéb számításokat is végrehajthatnak az összes Azure Functionsre jellemző általános korlátokon belül.

A műveletek a környezet által biztosított funkciókhoz is hozzáférnek Entity.Current :

  • EntityName: a jelenleg végrehajtó entitás neve.
  • EntityKey: a jelenleg végrehajtó entitás kulcsa.
  • EntityId: a jelenleg végrehajtó entitás azonosítója (beleértve a nevet és a kulcsot).
  • SignalEntity: egyirányú üzenetet küld egy entitásnak.
  • CreateNewOrchestration: új vezénylést indít.
  • DeleteState: törli az entitás állapotát.

Módosíthatjuk például a számláló entitást, hogy vezénylést indítson el, amikor a számláló eléri a 100-as értéket, és bemeneti argumentumként adja át az entitásazonosítót:

public void Add(int amount) 
{
    if (this.Value < 100 && this.Value + amount >= 100)
    {
        Entity.Current.StartNewOrchestration("MilestoneReached", Entity.Current.EntityId);
    }
    this.Value += amount;      
}

Entitások közvetlen elérése

Az osztályalapú entitások közvetlenül érhetők el az entitás és műveletei explicit sztringneveivel. Ez a szakasz példákat tartalmaz. Az alapul szolgáló fogalmak (például a jelek és a hívások) részletesebb ismertetését az Access-entitások vitafórumában tekintheti meg.

Megjegyzés:

Ahol lehetséges, az entitásokat interfészeken keresztül kell elérnie, mivel ez több típusellenőrzést biztosít.

Példa: ügyféljelek entitása

Az alábbi Azure Http-függvény REST-konvenciók használatával implementál egy DELETE műveletet. Törlési jelet küld a számláló entitásnak, amelynek kulcsát az URL-elérési úton adja át.

[FunctionName("DeleteCounter")]
public static async Task<HttpResponseMessage> DeleteCounter(
    [HttpTrigger(AuthorizationLevel.Function, "delete", Route = "Counter/{entityKey}")] HttpRequestMessage req,
    [DurableClient] IDurableEntityClient client,
    string entityKey)
{
    var entityId = new EntityId("Counter", entityKey);
    await client.SignalEntityAsync(entityId, "Delete");    
    return req.CreateResponse(HttpStatusCode.Accepted);
}

Példa: az ügyfél beolvassa az entitás állapotát

Az alábbi Azure Http-függvény REST-konvenciók használatával implementál egy GET műveletet. Beolvassa annak a számláló entitásnak az aktuális állapotát, amelynek kulcsát az URL-elérési úton adja át.

[FunctionName("GetCounter")]
public static async Task<HttpResponseMessage> GetCounter(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Counter/{entityKey}")] HttpRequestMessage req,
    [DurableClient] IDurableEntityClient client,
    string entityKey)
{
    var entityId = new EntityId("Counter", entityKey);
    var state = await client.ReadEntityStateAsync<Counter>(entityId); 
    return req.CreateResponse(state);
}

Megjegyzés:

A visszaadott ReadEntityStateAsync objektum csak egy helyi másolat, vagyis egy pillanatkép az entitás állapotáról egy korábbi időpontból. Különösen elavult lehet, és az objektum módosítása nincs hatással a tényleges entitásra.

Példa: az első jelek vezénylése, majd entitás meghívása

Az alábbi vezénylés egy számláló entitást jelez a növeléséhez, majd meghívja ugyanazt az entitást a legújabb érték olvasásához.

[FunctionName("IncrementThenGet")]
public static async Task<int> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var entityId = new EntityId("Counter", "myCounter");

    // One-way signal to the entity - does not await a response
    context.SignalEntity(entityId, "Add", 1);

    // Two-way call to the entity which returns a value - awaits the response
    int currentValue = await context.CallEntityAsync<int>(entityId, "Get");

    return currentValue;
}

Példa: ügyféljelek entitása

Az alábbi Azure Http-függvény REST-konvenciók használatával implementál egy DELETE műveletet. Törlési jelet küld a számláló entitásnak, amelynek kulcsát az URL-elérési úton adja át.

[Function("DeleteCounter")]
public static async Task<HttpResponseData> DeleteCounter(
    [HttpTrigger(AuthorizationLevel.Function, "delete", Route = "Counter/{entityKey}")] HttpRequestData req,
    [DurableClient] DurableTaskClient client, string entityKey)
{
    var entityId = new EntityInstanceId("Counter", entityKey);
    await client.Entities.SignalEntityAsync(entityId, "Delete");
    return req.CreateResponse(HttpStatusCode.Accepted);
}

Példa: az ügyfél beolvassa az entitás állapotát

Az alábbi Azure Http-függvény REST-konvenciók használatával implementál egy GET műveletet. Beolvassa annak a számláló entitásnak az aktuális állapotát, amelynek kulcsát az URL-elérési úton adja át.

[Function("GetCounter")]
public static async Task<HttpResponseData> GetCounter(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Counter/{entityKey}")] HttpRequestData req,
    [DurableClient] DurableTaskClient client, string entityKey)
{
    var entityId = new EntityInstanceId("Counter", entityKey);
    EntityMetadata<int>? entity = await client.Entities.GetEntityAsync<int>(entityId);
    HttpResponseData response = request.CreateResponse(HttpStatusCode.OK);
    await response.WriteAsJsonAsync(entity.State);

    return response;
}

Példa: az első jelek vezénylése, majd entitás meghívása

Az alábbi vezénylés egy számláló entitást jelez a növeléséhez, majd meghívja ugyanazt az entitást a legújabb érték olvasásához.

[Function("IncrementThenGet")]
public static async Task<int> Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
    var entityId = new EntityInstanceId("Counter", "myCounter");

    // One-way signal to the entity - does not await a response
    await context.Entities.SignalEntityAsync(entityId, "Add", 1);

    // Two-way call to the entity which returns a value - awaits the response
    int currentValue = await context.Entities.CallEntityAsync<int>(entityId, "Get");

    return currentValue; 
}

Entitások elérése interfészeken keresztül

Az interfészek az entitások generált proxyobjektumokon keresztüli elérésére használhatók. Ez a megközelítés biztosítja, hogy a művelet neve és argumentumtípusa megegyezik a implementált művelettel. Javasoljuk, hogy minden lehetséges esetben használjon interfészeket az entitásokhoz való hozzáféréshez.

A számláló példáját például az alábbiak szerint módosíthatjuk:

public interface ICounter
{
    void Add(int amount);
    Task Reset();
    Task<int> Get();
    void Delete();
}

public class Counter : ICounter
{
    ...
}

Az entitásosztályok és az entitás-interfészek hasonlóak az Orleans által népszerűvé vált szemcsékhez és szemcsés felületekhez. A Durable Entities és Orleans közötti hasonlóságokról és különbségekről további információt a virtuális szereplőkkel való összehasonlítása című témakörben talál.

A típusellenőrzés mellett az interfészek az alkalmazáson belüli problémák jobb elkülönítéséhez is hasznosak. Mivel például egy entitás több illesztőt is implementálhat, egyetlen entitás több szerepkört is kiszolgálhat. Mivel egy interfészt több entitás is implementálhat, az általános kommunikációs minták újrafelhasználható kódtárakként is implementálhatók.

Példa: ügyféljelek entitása interfészen keresztül

Az ügyfélkód képes SignalEntityAsync<TEntityInterface> jeleket küldeni azokat az entitásoknak, amelyek implementálják TEntityInterface. Például:

[FunctionName("DeleteCounter")]
public static async Task<HttpResponseMessage> DeleteCounter(
    [HttpTrigger(AuthorizationLevel.Function, "delete", Route = "Counter/{entityKey}")] HttpRequestMessage req,
    [DurableClient] IDurableEntityClient client,
    string entityKey)
{
    var entityId = new EntityId("Counter", entityKey);
    await client.SignalEntityAsync<ICounter>(entityId, proxy => proxy.Delete());    
    return req.CreateResponse(HttpStatusCode.Accepted);
}

Ebben a példában a proxy paraméter egy dinamikusan generált példány ICounter, amely belsőleg lefordítja a hívást Delete egy jelre.

Megjegyzés:

Az SignalEntityAsync API-k csak egyirányú műveletekhez használhatók. Még ha egy művelet is ad vissza Task<T>, a T paraméter értéke mindig null defaultvagy nem a tényleges eredmény lesz. Például nincs értelme jelezni a Get műveletet, mivel a rendszer nem ad vissza értéket. Ehelyett az ügyfelek ReadStateAsync közvetlenül hozzáférhetnek a számláló állapotához, vagy elindíthatnak egy vezénylő függvényt, amely meghívja a Get műveletet.

Példa: első jelek vezénylése, majd entitás meghívása proxyn keresztül

Az entitások vezénylésen CreateEntityProxy belüli meghívásához vagy jelzéséhez a felület típusával együtt proxyt is létrehozhat az entitáshoz. Ezt a proxyt ezután a következő műveletek hívására vagy jelzésére használhatja:

[FunctionName("IncrementThenGet")]
public static async Task<int> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var entityId = new EntityId("Counter", "myCounter");
    var proxy = context.CreateEntityProxy<ICounter>(entityId);

    // One-way signal to the entity - does not await a response
    proxy.Add(1);

    // Two-way call to the entity which returns a value - awaits the response
    int currentValue = await proxy.Get();

    return currentValue;
}

Implicit módon a rendszer minden visszaadott void műveletet jelez, és minden olyan műveletet, amely visszaadja Task vagy Task<T> meghívja őket. Ezt az alapértelmezett viselkedést és jelműveletet akkor is módosíthatja, SignalEntity<IInterfaceType> ha a feladatot explicit módon adja vissza.

Rövidebb beállítás a cél megadásához

Ha egy entitást egy interfész használatával hív vagy jelez, az első argumentumnak meg kell adnia a cél entitást. A cél megadható az entitásazonosító megadásával, vagy abban az esetben, ha csak egy osztály implementálja az entitást, csak az entitáskulcs:

context.SignalEntity<ICounter>(new EntityId(nameof(Counter), "myCounter"), ...);
context.SignalEntity<ICounter>("myCounter", ...);

Ha csak az entitáskulcs van megadva, és egy egyedi implementáció nem található futásidőben, InvalidOperationException a rendszer eldobja.

Az entitás-interfészek korlátozásai

A szokásos módon minden paraméter- és visszatérési típusnak JSON-szerializálhatónak kell lennie. Ellenkező esetben a szerializálási kivételek futásidőben jelennek meg.

További szabályokat is kikényszerítettünk:

  • Az entitásillesztőket ugyanabban a szerelvényben kell definiálni, mint az entitásosztályt.
  • Az entitásillesztőknek csak metódusokat kell meghatározniuk.
  • Az entitásillesztők nem tartalmazhatnak általános paramétereket.
  • Az entitás-illesztési metódusoknak nem lehet több paraméterük.
  • Az entitásillesztő metódusokat vissza kell adnia void, Taskvagy Task<T>.

Ha ezen szabályok bármelyikét megsértik, a rendszer futásidőben ad vissza egy InvalidOperationException hibát, amikor a felületet típusargumentumként SignalEntityhasználják az , SignalEntityAsyncvagy CreateEntityProxy. A kivételüzenet ismerteti, hogy melyik szabály volt hibás.

Megjegyzés:

A visszaadott void felületi metódusok csak egyirányúak, nem hívhatók (kétirányúak). A visszaadott TaskTask<T> interfészmetenek hívhatók vagy jelezhetők. Meghívás esetén visszaadják a művelet eredményét, vagy újradobják a művelet által kidobott kivételeket. A jelzéskor azonban nem a művelet tényleges eredményét vagy kivételét, hanem csak az alapértelmezett értéket adja vissza.

Ez jelenleg nem támogatott a .NET izolált feldolgozójában.

Entitás szerializálása

Mivel egy entitás állapota tartósan megmarad, az entitásosztálynak szerializálhatónak kell lennie. A Durable Functions-futtatókörnyezet ehhez a Json.NET kódtárat használja, amely támogatja a szabályzatokat és attribútumokat a szerializálási és deszerializálási folyamat szabályozásához. A leggyakrabban használt C#-adattípusok (beleértve a tömböket és a gyűjteménytípusokat) már szerializálhatók, és könnyen használhatók tartós entitások állapotának meghatározására.

A Json.NET például egyszerűen szerializálhatja és deszerializálhatja a következő osztályt:

[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class User
{
    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("yearOfBirth")]
    public int YearOfBirth { get; set; }

    [JsonProperty("timestamp")]
    public DateTime Timestamp { get; set; }

    [JsonProperty("contacts")]
    public Dictionary<Guid, Contact> Contacts { get; set; } = new Dictionary<Guid, Contact>();

    [JsonObject(MemberSerialization = MemberSerialization.OptOut)]
    public struct Contact
    {
        public string Name;
        public string Number;
    }

    ...
}

Szerializálási attribútumok

A fenti példában több attribútumot is belefoglaltunk a mögöttes szerializálás láthatóbbá tételéhez:

  • Megjegyzést [JsonObject(MemberSerialization.OptIn)] fűzünk az osztályhoz, hogy emlékeztessen minket arra, hogy az osztálynak szerializálhatónak kell lennie, és csak a kifejezetten JSON-tulajdonságokként megjelölt tagokat kell megőriznünk.
  • A megőrzendő mezők megjegyzésével [JsonProperty("name")] emlékeztetünk arra, hogy egy mező a megőrzött entitásállapot része, és megadhatja a JSON-ábrázolásban használandó tulajdonságnevet.

Ezek az attribútumok azonban nem kötelezőek; más konvenciók vagy attribútumok mindaddig engedélyezettek, amíg azok Json.NET működnek. Használhat például attribútumokat, vagy egyáltalán nem használhat [DataContract] attribútumokat:

[DataContract]
public class Counter
{
    [DataMember]
    public int Value { get; set; }
    ...
}

public class Counter
{
    public int Value;
    ...
}

Alapértelmezés szerint az osztály neve nem* a JSON-ábrázolás részeként van tárolva, azaz alapértelmezett beállításként használjuk TypeNameHandling.None . Ez az alapértelmezett viselkedés felülírható attribútumok vagy JsonProperty attribútumok használatávalJsonObject.

Az osztálydefiníciók módosítása

Az alkalmazás futtatása után az osztálydefiníciók módosításakor bizonyos óvatosságra van szükség, mivel a tárolt JSON-objektum már nem felel meg az új osztálydefiníciónak. Mégis gyakran lehet helyesen kezelni az adatformátumok módosítását, amíg az ember ismeri a használt JsonConvert.PopulateObjectdeszerializálási folyamatot.

Íme például néhány példa a változásokra és azok hatására:

  • Ha új tulajdonságot ad hozzá, amely nem szerepel a tárolt JSON-ban, az alapértelmezett értéket feltételezi.
  • Ha eltávolít egy tulajdonságot, amely a tárolt JSON-ban található, az előző tartalom elveszik.
  • Ha egy tulajdonságot átneveznek, az olyan, mintha eltávolítaná a régit, és újat ad hozzá.
  • Ha egy tulajdonság típusa megváltozik, hogy a továbbiakban ne lehessen deszerializálni a tárolt JSON-ból, a rendszer kivételt jelez.
  • Ha egy tulajdonság típusa megváltozik, de továbbra is deszerializálható a tárolt JSON-ból, ezt teszi.

Számos lehetőség áll rendelkezésre a Json.NET viselkedésének testreszabására. Ha például kivételt szeretne kényszeríteni, ha a tárolt JSON olyan mezőt tartalmaz, amely nem szerepel az osztályban, adja meg az attribútumot JsonObject(MissingMemberHandling = MissingMemberHandling.Error). A deszerializáláshoz egyéni kódot is írhat, amely tetszőleges formátumban tárolt JSON-t tud olvasni.

A szerializálás alapértelmezett viselkedése megváltozottNewtonsoft.Json System.Text.Json. További információt itt talál.

Entitások felépítése

Néha jobban szeretnénk szabályozni az entitásobjektumok létrehozásának módját. Most már számos lehetőséget ismertetünk az entitásobjektumok létrehozásakor az alapértelmezett viselkedés módosítására.

Egyéni inicializálás az első hozzáféréskor

Időnként speciális inicializálást kell végrehajtanunk, mielőtt elküldünk egy műveletet egy olyan entitásnak, amely soha nem volt elérhető vagy törölve lett. Ennek a viselkedésnek a megadásához hozzáadhat egy feltételes beállítást a DispatchAsynckövetkező elé:

[FunctionName(nameof(Counter))]
public static Task Run([EntityTrigger] IDurableEntityContext ctx)
{
    if (!ctx.HasState)
    {
        ctx.SetState(...);
    }
    return ctx.DispatchAsync<Counter>();
}

Kötések entitásosztályokban

A normál függvényekkel ellentétben az entitásosztály-metódusok nem rendelkeznek közvetlen hozzáféréssel a bemeneti és kimeneti kötésekhez. Ehelyett a kötési adatokat rögzíteni kell a belépési pont függvény deklarációjában, majd át kell adni a DispatchAsync<T> metódusnak. A rendszer automatikusan átadja az DispatchAsync<T> átadott objektumokat az entitásosztály konstruktorának argumentumként.

Az alábbi példa bemutatja, hogyan tehető elérhetővé egy CloudBlobContainerblobbemeneti kötésből származó hivatkozás egy osztályalapú entitás számára.

public class BlobBackedEntity
{
    [JsonIgnore]
    private readonly CloudBlobContainer container;

    public BlobBackedEntity(CloudBlobContainer container)
    {
        this.container = container;
    }

    // ... entity methods can use this.container in their implementations ...

    [FunctionName(nameof(BlobBackedEntity))]
    public static Task Run(
        [EntityTrigger] IDurableEntityContext context,
        [Blob("my-container", FileAccess.Read)] CloudBlobContainer container)
    {
        // passing the binding object as a parameter makes it available to the
        // entity class constructor
        return context.DispatchAsync<BlobBackedEntity>(container);
    }
}

Az Azure Functions kötéseivel kapcsolatos további információkért tekintse meg az Azure Functions-eseményindítók és kötések dokumentációját .

Függőséginjektálás entitásosztályokban

Az entitásosztályok támogatják az Azure Functions függőséginjektálást. Az alábbi példa bemutatja, hogyan regisztrálhat egy IHttpClientFactory szolgáltatást egy osztályalapú entitásban.

[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]

namespace MyNamespace
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddHttpClient();
        }
    }
}

Az alábbi kódrészlet bemutatja, hogyan építheti be az injektált szolgáltatást az entitásosztályba.

public class HttpEntity
{
    [JsonIgnore]
    private readonly HttpClient client;

    public HttpEntity(IHttpClientFactory factory)
    {
        this.client = factory.CreateClient();
    }

    public Task<int> GetAsync(string url)
    {
        using (var response = await this.client.GetAsync(url))
        {
            return (int)response.StatusCode;
        }
    }

    [FunctionName(nameof(HttpEntity))]
    public static Task Run([EntityTrigger] IDurableEntityContext ctx)
        => ctx.DispatchAsync<HttpEntity>();
}

Egyéni inicializálás az első hozzáféréskor

public class Counter : TaskEntity<int>
{
    protected override int InitializeState(TaskEntityOperation operation)
    {
        // This is called when state is null, giving a chance to customize first-access of entity.
        return 10;
    }
}

Kötések entitásosztályokban

Az alábbi példa bemutatja, hogyan használható blobbemeneti kötés egy osztályalapú entitásban.

public class BlobBackedEntity : TaskEntity<object?>
{
    private BlobContainerClient Container { get; set; }

    [Function(nameof(BlobBackedEntity))]
    public Task DispatchAsync(
        [EntityTrigger] TaskEntityDispatcher dispatcher, 
        [BlobInput("my-container")] BlobContainerClient container)
    {
        this.Container = container;
        return dispatcher.DispatchAsync(this);
    }
}

Az Azure Functions kötéseivel kapcsolatos további információkért tekintse meg az Azure Functions-eseményindítók és kötések dokumentációját .

Függőséginjektálás entitásosztályokban

Az entitásosztályok támogatják az Azure Functions függőséginjektálást.

Az alábbiakban bemutatjuk, hogyan konfigurálhat egy HttpClient fájlt az program.cs entitásosztály későbbi részében importálandó fájlban.

public class Program
{
    public static void Main()
    {
        IHost host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults((IFunctionsWorkerApplicationBuilder workerApplication) =>
            {
                workerApplication.Services.AddHttpClient<HttpEntity>()
                    .ConfigureHttpClient(client => {/* configure http client here */});
             })
            .Build();

        host.Run();
    }
}

Az alábbiakban bemutatjuk, hogyan építheti be az injektált szolgáltatást az entitásosztályba.

public class HttpEntity : TaskEntity<object?>
{
    private readonly HttpClient client;

     public HttpEntity(HttpClient client)
    {
        this.client = client;
    }

    public async Task<int> GetAsync(string url)
    {
        using var response = await this.client.GetAsync(url);
        return (int)response.StatusCode;
    }

    [Function(nameof(HttpEntity))]
    public static Task Run([EntityTrigger] TaskEntityDispatcher dispatcher)
        => dispatcher.DispatchAsync<HttpEntity>();
}

Megjegyzés:

A szerializálással kapcsolatos problémák elkerülése érdekében zárja ki a szerializálásból az injektált értékek tárolására szánt mezőket.

Megjegyzés:

A hagyományos .NET Azure Functions konstruktorinjektálásával ellentétben az osztályalapú entitások függvénybeviteli pont metódusát deklarálni statickell. A nem statikus függvény belépési pontjának deklarálása ütközéseket okozhat a normál Azure Functions-objektum inicializálója és a Durable Entities objektum inicializálója között.

Függvényalapú szintaxis

Eddig az osztályalapú szintaxisra összpontosítottunk, mivel azt várjuk, hogy jobban illeszkedjen a legtöbb alkalmazáshoz. A függvényalapú szintaxis azonban megfelelő lehet azoknak az alkalmazásoknak, amelyek saját absztrakcióikat szeretnék definiálni vagy kezelni az entitásállapothoz és a műveletekhez. Az osztályalapú szintaxis által jelenleg nem támogatott általánosságot igénylő kódtárak implementálásakor is célszerű lehet.

A függvényalapú szintaxissal az Entitásfüggvény explicit módon kezeli a műveletküldést, és explicit módon kezeli az entitás állapotát. Az alábbi kód például a függvényalapú szintaxissal implementált Számláló entitást mutatja be.

[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            ctx.SetState(ctx.GetState<int>() + ctx.GetInput<int>());
            break;
        case "reset":
            ctx.SetState(0);
            break;
        case "get":
            ctx.Return(ctx.GetState<int>());
            break;
        case "delete":
            ctx.DeleteState();
            break;
    }
}

Az entitás környezeti objektuma

Az entitásspecifikus funkciók egy típusú IDurableEntityContextkörnyezeti objektumon keresztül érhetők el. Ez a környezeti objektum paraméterként érhető el az entitásfüggvény számára, és az aszinkron helyi tulajdonságon Entity.Currentkeresztül.

A következő tagok információkat nyújtanak az aktuális műveletről, és lehetővé teszik számunkra a visszatérési érték megadását.

  • EntityName: a jelenleg végrehajtó entitás neve.
  • EntityKey: a jelenleg végrehajtó entitás kulcsa.
  • EntityId: a jelenleg végrehajtó entitás azonosítója (beleértve a nevet és a kulcsot).
  • OperationName: az aktuális művelet neve.
  • GetInput<TInput>(): lekéri az aktuális művelet bemenetét.
  • Return(arg): egy értéket ad vissza a műveletnek nevezett vezénylésnek.

A következő tagok kezelik az entitás állapotát (létrehozás, olvasás, frissítés, törlés).

  • HasState: hogy az entitás létezik-e, vagyis van-e valamilyen állapota.
  • GetState<TState>(): lekéri az entitás aktuális állapotát. Ha még nem létezik, létrejön.
  • SetState(arg): létrehozza vagy frissíti az entitás állapotát.
  • DeleteState(): törli az entitás állapotát, ha létezik.

Ha a visszaadott GetState állapot egy objektum, azt közvetlenül módosíthatja az alkalmazáskód. A végén nem kell újra hívni SetState (de nem is árthat). Ha GetState<TState> többször is meghívják, ugyanazt a típust kell használni.

Végül a következő tagok jelzik a többi entitást, vagy új vezényléseket indítanak el:

  • SignalEntity(EntityId, operation, input): egyirányú üzenetet küld egy entitásnak.
  • CreateNewOrchestration(orchestratorFunctionName, input): új vezénylést indít.
[Function(nameof(Counter))]
public static Task DispatchAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
{
    return dispatcher.DispatchAsync(operation =>
    {
        if (operation.State.GetState(typeof(int)) is null)
        {
            operation.State.SetState(0);
        }

        switch (operation.Name.ToLowerInvariant())
        {
            case "add":
                int state = operation.State.GetState<int>();
                state += operation.GetInput<int>();
                operation.State.SetState(state);
                return new(state);
            case "reset":
                operation.State.SetState(0);
                break;
            case "get":
                return new(operation.State.GetState<int>());
            case "delete": 
                operation.State.SetState(null);
                break; 
        }

        return default;
    });
}

Következő lépések