Sdílet prostřednictvím


Příručka pro vývojáře k trvalým entitě v .NET

V tomto článku podrobně popisujeme dostupná rozhraní pro vývoj trvalých entit pomocí .NET, včetně příkladů a obecných rad.

Funkce entit poskytují vývojářům bezserverových aplikací pohodlný způsob, jak uspořádat stav aplikace jako kolekci jemně odstupňovaných entit. Další podrobnosti o základních konceptech najdete v článku Durable Entities: Koncepty .

V současné době nabízíme dvě rozhraní API pro definování entit:

  • Syntaxe založená na třídách představuje entity a operace jako třídy a metody. Tato syntaxe vytváří snadno čitelný kód a umožňuje vyvolání operací způsobem kontrolovaných typem prostřednictvím rozhraní.

  • Syntaxe založená na funkcích je rozhraní nižší úrovně, které představuje entity jako funkce. Poskytuje přesnou kontrolu nad tím, jak se operace entit odesílají a jak se spravuje stav entity.

Tento článek se zaměřuje především na syntaxi založenou na třídě, protože očekáváme, že bude vhodnější pro většinu aplikací. Syntaxe založená na funkcích ale může být vhodná pro aplikace, které chtějí definovat nebo spravovat vlastní abstrakce pro stav a operace entity. Může být také vhodné pro implementaci knihoven, které vyžadují obecné typy, které syntaxe založená na třídách v současné době nepodporuje.

Poznámka:

Syntaxe založená na třídách je pouze vrstva nad syntaxí založenou na funkci, takže obě varianty lze ve stejné aplikaci zaměnitelně.

Definování tříd entit

Následující příklad je implementace Counter entity, která ukládá jednu hodnotu typu integer, a nabízí čtyři operace Add, Reset, Geta 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>();
}

Funkce Run obsahuje často používané šablony pro použití syntaxe založené na třídě. Musí to být statická funkce Azure Functions. Spustí se jednou pro každou zprávu operace, která je zpracována entitou. Když DispatchAsync<T> je volána a entita ještě není v paměti, vytvoří objekt typu T a naplní jeho pole z posledního trvalého json nalezeného v úložišti (pokud existuje). Pak vyvolá metodu s odpovídajícím názvem.

Funkce EntityTriggerRun v této ukázce nemusí být umístěná v samotné třídě Entity. Může se nacházet v libovolném platném umístění pro funkci Azure Functions: uvnitř oboru názvů nejvyšší úrovně nebo uvnitř třídy nejvyšší úrovně. Pokud je však vnořená hlouběji (například funkce je deklarována uvnitř vnořené třídy), nebude tato funkce rozpoznána nejnovějším modulem runtime.

Poznámka:

Stav entity založené na třídě se vytvoří implicitně před tím, než entita zpracuje operaci a lze ji explicitně odstranit voláním Entity.Current.DeleteState().

Poznámka:

K spouštění entit v izolovaném modelu potřebujete nebo vyšší.

Existují dva způsoby definování entity jako třídy v izolovaném pracovním modelu jazyka C#. Vytvářejí entity s různými strukturami serializace stavu.

Při definování entity se při definování entity serializuje celý objekt.

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>();
}

Implementace TaskEntity<TState>založená na ní, což usnadňuje použití injektáže závislostí. V tomto případě je stav deserializován na State vlastnost a žádná jiná vlastnost není serializována/deserializována.

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

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

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

    public Task 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>();
}

Upozorňující

Při psaní entit, které jsou odvozeny z ITaskEntity nebo TaskEntity<TState>, je důležité nepojmenovat metodu triggeru entity RunAsync. To způsobuje chyby za běhu při vyvolání entity, protože existuje nejednoznačná shoda s názvem metody "RunAsync" vzhledem k tomu, že již existuje definice instance na úrovni "RunAsync".

Odstranění entit v izolovaném modelu

Odstranění entity v izolovaném modelu se provádí nastavením stavu entity na nulla tento proces závisí na použité cestě implementace entity:

  • Při odvození nebo použití ITaskEntity založené na funkcích se odstranění provádí voláním .TaskEntityOperation.State.SetState(null)
  • Při odvození z TaskEntity<TState>objektu delete je implicitně definováno. Lze ho však přepsat definováním metody Delete entity. Stav lze také odstranit z jakékoli operace prostřednictvím this.State = null.
    • Chcete-li odstranit nastavením stavu na hodnotu null, musí TState být nullable.
    • Implicitně definovaná operace odstranění odstraní non-nullable TState.
  • Při použití POCO jako vašeho stavu (neodvozuje z TaskEntity<TState>), odstranění je implicitně definováno. Operaci odstranění je možné přepsat definováním metody Delete pro POCO. Nelze však nastavit stav na null v trase POCO, takže implicitně definovaná operace mazání je jediným skutečným odstraněním.

Požadavky na třídu

Třídy entit jsou objekty POCOs (prosté staré objekty CLR), které nevyžadují speciální supertřídy, rozhraní nebo atributy. Mějte však na paměti následující:

  • Třída musí být konstruktovatelná (viz Konstrukce entity).
  • Třída musí být serializovatelná json (viz serializace entity).

Také každá metoda vyvolaná jako operace musí splňovat další požadavky:

  • Operace musí mít maximálně jeden argument, ale nesmí obsahovat žádná přetížení ani argumenty obecného typu.
  • Operace, která se má volat z orchestrace pomocí rozhraní, musí vrátit Task nebo Task<T>.
  • Argumenty a návratové hodnoty musí být serializovatelné hodnoty nebo objekty.

Co můžou operace dělat?

Všechny operace entit mohou číst a aktualizovat stav entity a změny stavu se automaticky uchovávají v úložišti. Kromě toho můžou operace provádět externí vstupně-výstupní operace nebo jiné výpočty v rámci obecných limitů, které jsou společné pro všechny služby Azure Functions.

Operace mají také přístup k funkcím poskytovaným kontextem Entity.Current :

  • EntityName: název aktuálně spuštěné entity.
  • EntityKey: klíč aktuálně spuštěné entity.
  • EntityId: ID aktuálně spuštěné entity (zahrnuje název a klíč).
  • SignalEntity: Odešle jednosměrnou zprávu do entity.
  • CreateNewOrchestration: spustí novou orchestraci.
  • DeleteState: Odstraní stav této entity.

Můžeme například upravit entitu čítače, aby spustila orchestraci, když čítač dosáhne 100 a předá ID entity jako vstupní argument:

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

Přímý přístup k entitě

K entitě založeným na třídách je možné přistupovat přímo pomocí explicitních názvů řetězců pro entitu a její operace. Tato část obsahuje příklady. Podrobnější vysvětlení základních konceptů (například signálů a volání) najdete v diskuzi v entitách Accessu.

Poznámka:

Pokud je to možné, měli byste přistupovat k entitám prostřednictvím rozhraní, protože poskytuje více kontroly typů.

Příklad: Entita klientských signálů

Následující funkce Azure Http implementuje operaci DELETE pomocí konvencí REST. Odešle signál pro odstranění do entity čítače, jejíž klíč se předává v cestě URL.

[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říklad: Klient čte stav entity.

Následující funkce Azure HTTP implementuje operaci GET pomocí konvencí REST. Přečte aktuální stav entity čítače, jejíž klíč se předává v cestě URL.

[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);
}

Poznámka:

Objekt vrácený ReadEntityStateAsync pouze místní kopií, tj. snímek stavu entity z nějakého dřívějšího bodu v čase. Konkrétně může být zastaralá a úprava tohoto objektu nemá žádný vliv na skutečnou entitu.

Příklad: Orchestrace nejprve signalizuje, potom volá entitu

Následující orchestrace signalizuje entitu čítače, která ji zvýší, a pak zavolá stejnou entitu, aby si přečetla její nejnovější hodnotu.

[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říklad: Entita klientských signálů

Následující funkce AZURE HTTP implementuje operaci DELETE pomocí konvencí REST. Odešle signál pro odstranění do entity čítače, jejíž klíč se předává v cestě URL.

[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říklad: Klient čte stav entity.

Následující funkce Azure HTTP implementuje operaci GET pomocí konvencí REST. Přečte aktuální stav entity čítače, jejíž klíč se předává v cestě URL.

[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říklad: Orchestrace nejprve signalizuje, potom volá entitu

Následující orchestrace signalizuje entitu čítače, která ji zvýší, a pak zavolá stejnou entitu, aby si přečetla její nejnovější hodnotu.

[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; 
}

Přístup k entitám prostřednictvím rozhraní

Rozhraní se dají použít pro přístup k entitám prostřednictvím vygenerovaných objektů proxy. Tento přístup zajišťuje, že název a typ argumentu operace odpovídají implementovanému objektu. Pokud je to možné pro přístup k entitám, doporučujeme používat rozhraní.

Můžeme například upravit příklad čítače:

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

public class Counter : ICounter
{
    ...
}

Třídy entit a rozhraní entit se podobají rozhraním zrn a zrn, která jsou oblíbená společností Orleans. Další informace o podobnostech a rozdílech mezi Durable Entities a Orleans naleznete v tématu Porovnání s virtuálními aktéry.

Kromě poskytování kontroly typů jsou rozhraní užitečná pro lepší oddělení obav v aplikaci. Například vzhledem k tomu, že entita může implementovat více rozhraní, může jedna entita sloužit více rolím. Vzhledem k tomu, že rozhraní může implementovat více entit, je možné implementovat obecné komunikační vzory jako opakovaně použitelné knihovny.

Příklad: Entita klientských signálů prostřednictvím rozhraní

Klientský kód může použít SignalEntityAsync<TEntityInterface> k odesílání signálů entit, které implementují TEntityInterface. Příklad:

[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);
}

V tomto příkladu proxy je parametr dynamicky generovanou instancí ICounter, která interně překládá volání na Delete signál.

Poznámka:

Rozhraní SignalEntityAsync API je možné použít pouze pro jednosměrné operace. I když operace vrátí Task<T>hodnotu , hodnota parametru T je vždy null nebo default místo skutečného výsledku. Například nemá smysl signalizovat Get operaci, protože nevrací hodnotu. Místo toho můžou klienti buď použít ReadStateAsync pro přímý přístup ke stavu čítače, nebo spustit funkci orchestrátoru Get , která tuto operaci volá.

Příklad: Orchestrace nejprve vyšle signál, potom zavolá entitu přes proxy

K volání nebo signalizaci entity z orchestrace CreateEntityProxy je možné použít spolu s typem rozhraní k vygenerování proxy serveru pro entitu. Tento proxy server pak lze použít k volání nebo signalizačním operacím:

[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;
}

Implicitně se všechny operace, které vrací, signalizují a všechny operace, které vrací voidTask nebo Task<T> se volají. Jedno může změnit toto výchozí chování a operace signálu, i když vrací úlohu pomocí SignalEntity<IInterfaceType> metody explicitně.

Kratší možnost pro určení cíle

Při volání nebo signalizaci entity pomocí rozhraní musí první argument zadat cílovou entitu. Cíl lze zadat definováním ID entity nebo pouze klíčem entity, pokud existuje pouze jedna třída, která implementuje entitu:

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

Pokud je zadán pouze klíč entity a za běhu nelze najít jedinečnou implementaci, InvalidOperationException vyvolá se.

Omezení rozhraní entit

Jako obvykle musí být všechny typy parametrů a návratových typů serializovatelné ve formátu JSON. V opačném případě jsou výjimky serializace vyvolány za běhu. Platí také následující pravidla:

  • Rozhraní entit musí být definována ve stejném sestavení jako třída entity.
  • Rozhraní entit musí definovat pouze metody.
  • Rozhraní entit nesmí obsahovat obecné parametry.
  • Metody rozhraní entity nesmí mít více než jeden parametr.
  • Metody rozhraní entity musí vracet void, Tasknebo Task<T>.

Pokud jsou některá z těchto pravidel porušena, je vyvolán za běhu, InvalidOperationException pokud je rozhraní použito jako typ argumentu , SignalEntitySignalEntityAsyncnebo CreateEntityProxy. Zpráva o výjimce vysvětluje, které pravidlo bylo přerušeno.

Poznámka:

Návratové void metody rozhraní mohou být signalizovat pouze jednosměrně, nevolané (obousměrné). Metody rozhraní vracející Task nebo Task<T> se dají volat nebo signalizovat. Pokud jsou volány, vrátí výsledek operace nebo znovu vyvolají výjimky vyvolané operací. V případě signálu vrátí výchozí hodnotu, nikoli skutečný výsledek nebo výjimku z operace.

V izolovaném pracovním procesu .NET se to v současné době nepodporuje.

Serializace entit

Vzhledem k tomu, že stav entity je trvale trvalý, musí být třída entity serializovatelná. Modul runtime Durable Functions používá pro tento účel knihovnu Json.NET , která podporuje zásady a atributy k řízení procesu serializace a deserializace. Nejčastěji používané datové typy jazyka C# (včetně polí a typů kolekcí) jsou již serializovatelné a lze je snadno použít k definování stavu trvalých entit.

Například Json.NET může snadno serializovat a deserializovat následující třídu:

[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;
    }

    ...
}

Atributy serializace

V předchozím příkladu jsme zahrnuli několik atributů, které zviditelní základní serializaci:

  • Třídu označíme poznámkami [JsonObject(MemberSerialization.OptIn)] , abychom si připomněli, že třída musí být serializovatelná a zachovat pouze členy, které jsou explicitně označeny jako vlastnosti JSON.
  • Pole, která se mají zachovat, [JsonProperty("name")] označujeme, abychom si připomněli, že pole je součástí trvalého stavu entity a aby bylo možné zadat název vlastnosti, který se má použít v reprezentaci JSON.

Tyto atributy však nejsou povinné; jiné konvence nebo atributy jsou povoleny, pokud pracují s Json.NET. Můžete například použít [DataContract] atributy nebo vůbec žádné atributy:

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

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

Ve výchozím nastavení není název třídy * uložen jako součást reprezentace JSON: to znamená, že používáme TypeNameHandling.None jako výchozí nastavení. Toto výchozí chování lze přepsat pomocí JsonObject atributů nebo JsonProperty atributů.

Provádění změn definic tříd

Při provádění změn definice třídy po spuštění aplikace se vyžaduje určitá opatrnost, protože uložený objekt JSON už nemůže odpovídat nové definici třídy. Často je možné správně pracovat s měnícími se formáty dat, pokud jeden rozumí procesu deserializace používaného JsonConvert.PopulateObject. Tady jsou příklady změn a jejich dopadu:

  • Pokud se přidá nová vlastnost, která není v uloženém formátu JSON, předpokládá se její výchozí hodnota.
  • Když se odebere vlastnost uložená ve formátu JSON, předchozí obsah se ztratí.
  • Když je vlastnost přejmenována, efekt je odebrání starého a přidání nového.
  • Když se typ vlastnosti změní, aby se nedá deserializovat z uloženého JSON, vyvolá se výjimka.
  • Když se typ vlastnosti změní, aby se stále mohl deserializovat z uloženého JSON, provede to.

Pro přizpůsobení chování Json.NET je k dispozici mnoho možností. Chcete-li například vynutit výjimku, pokud uložený JSON obsahuje pole, které není přítomné ve třídě, zadejte atribut JsonObject(MissingMemberHandling = MissingMemberHandling.Error). Je také možné napsat vlastní kód pro deserializaci, který může číst JSON uložený v libovolných formátech.

Výchozí chování serializace se změnilo z Newtonsoft.Json na System.Text.Json. Další informace naleznete v tématu Přizpůsobení serializace a deserializace.

Konstrukce entit

Někdy chceme mít větší kontrolu nad vytvářením objektů entit. Nyní popisujeme několik možností pro změnu výchozího chování při vytváření objektů entity.

Vlastní inicializace při prvním přístupu

Občas potřebujeme provést nějakou speciální inicializaci před odesláním operace do entity, která nebyla nikdy přístupná nebo která byla odstraněna. Chcete-li zadat toto chování, můžete přidat podmínku před DispatchAsync:

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

Vazby ve třídách entit

Na rozdíl od běžných funkcí nemají metody tříd entit přímý přístup ke vstupním a výstupním vazbám. Místo toho musí být data vazby zachycena v deklaraci funkce vstupního bodu a pak předána metodě DispatchAsync<T> . Jakékoliv objekty předané DispatchAsync<T> se automaticky předávají jako argument konstruktoru třídy entity.

Následující příklad ukazuje, jak CloudBlobContainer lze zpřístupnit odkaz ze vstupní vazby objektu blob pro entitu založenou na třídě.

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);
    }
}

Pro více informací o vazbách v Azure Functions se podívejte na koncepce spouštění a vazeb Azure Functions.

Injektáž závislostí ve třídách entit

Třídy entit podporují injektáž závislostí Azure Functions. Následující příklad ukazuje, jak zaregistrovat IHttpClientFactory službu do entity založené na třídě.

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

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

Následující fragment kódu ukazuje, jak začlenit vloženou službu do třídy entity:

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>();
}

Vlastní inicializace při prvním přístupu

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;
    }
}

Vazby ve třídách entit

Následující příklad ukazuje, jak použít vstupní vazbu objektu blob v entitě založené na třídě.

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);
    }
}

Další informace o vazbách ve službě Azure Functions najdete v dokumentaci k triggerům a vazbám Azure Functions.

Injektáž závislostí ve třídách entit

Třídy entit podporují injektáž závislostí Azure Functions.

Následující příklad ukazuje, jak nakonfigurovat HttpClient v program.cs souboru, který se má importovat později ve třídě entity:

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();
    }
}

Následující příklad ukazuje, jak začlenit vloženou službu do vaší třídy entity:

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>();
}

Poznámka:

Abyste se vyhnuli problémům se serializací, nezapomeňte vyloučit pole, která ukládají vložené hodnoty ze serializace.

Poznámka:

Na rozdíl od použití injektáže konstruktoru v běžném rozhraní .NET Azure Functions musí. Deklarace vstupního bodu funkce, který není statický, může způsobit konflikty mezi normálním inicializátorem objektů Azure Functions a inicializátorem objektů Durable Entities.

Syntaxe založená na funkcích

Zatím jsme se zaměřili na syntaxi založenou na třídě, protože očekáváme, že bude vhodnější pro většinu aplikací. Syntaxe založená na funkcích ale může být vhodná pro aplikace, které chtějí definovat nebo spravovat vlastní abstrakce pro stav a operace entity. Může být také vhodné při implementaci knihoven, které vyžadují genericitu, která není aktuálně podporována syntaxí založenou na třídě.

Pomocí syntaxe založené na funkcích funkce funkce entita explicitně zpracovává odesílání operací a explicitně spravuje stav entity. Například následující kód ukazuje entitu Counter implementovanou pomocí syntaxe založené na funkcích.

[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;
    }
}

Objekt kontextu entity

K funkcím specifickým pro entity lze přistupovat prostřednictvím kontextového objektu typu IDurableEntityContext. Tento kontextový objekt je k dispozici jako parametr pro funkci entity a prostřednictvím async-local vlastnosti Entity.Current.

Následující členové poskytují informace o aktuální operaci a pomáhají určit návratovou hodnotu:

  • EntityName: Název aktuálně spuštěné entity
  • EntityKey: Klíč aktuálně spuštěné entity
  • EntityId: ID aktuálně spuštěné entity (včetně názvu a klíče)
  • OperationName: Název aktuální operace
  • GetInput<TInput>(): Získá vstup pro aktuální operaci.
  • Return(arg): Vrátí hodnotu do orchestrace, která vyvolala operaci.

Následující členové spravují stav entity (vytvoření, čtení, aktualizace, odstranění):

  • HasState: Pokud entita existuje; tj. má nějaký stav.
  • GetState<TState>(): Získá aktuální stav entity a vytvoří ji, pokud neexistuje.
  • SetState(arg): Vytvoří nebo aktualizuje stav entity.
  • DeleteState(): Odstraní stav entity, pokud existuje.

Pokud je stav vrácený objektem GetState , může ho kód aplikace upravit. Na konci už nemusíte volat SetState znovu (ale ani žádné škody). Pokud GetState<TState> se volá vícekrát, musí být použit stejný typ.

Nakonec následující členové signalizují jiné entity nebo spouštějí nové orchestrace:

  • SignalEntity(EntityId, operation, input): Odešle jednosměrnou zprávu entitě.
  • CreateNewOrchestration(orchestratorFunctionName, input): Spustí novou orchestraci.
[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;
    });
}

Další kroky