Not
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
I den här artikeln beskriver vi de tillgängliga gränssnitten för att utveckla hållbara entiteter med .NET i detalj, inklusive exempel och allmänna råd.
Entitetsfunktioner ger serverlösa programutvecklare ett bekvämt sätt att organisera programtillståndet som en samling detaljerade entiteter. Mer information om de underliggande begreppen finns i artikeln Durable Entities: Concepts (Varaktiga entiteter: Begrepp ).
Vi erbjuder för närvarande två API:er för att definiera entiteter:
Den klassbaserade syntaxen representerar entiteter och åtgärder som klasser och metoder. Den här syntaxen ger lättläst kod och gör att åtgärder kan anropas på ett typkontrollerat sätt via gränssnitt.
Den funktionsbaserade syntaxen är ett gränssnitt på lägre nivå som representerar entiteter som funktioner. Den ger exakt kontroll över hur entitetsåtgärderna skickas och hur entitetstillståndet hanteras.
Den här artikeln fokuserar främst på den klassbaserade syntaxen, eftersom vi förväntar oss att den passar bättre för de flesta program. Den funktionsbaserade syntaxen kan dock vara lämplig för program som vill definiera eller hantera sina egna abstraktioner för entitetstillstånd och åtgärder. Det kan också vara lämpligt att implementera bibliotek som kräver generiskhet som för närvarande inte stöds av den klassbaserade syntaxen.
Kommentar
Den klassbaserade syntaxen är bara ett lager ovanpå den funktionsbaserade syntaxen, så båda varianterna kan användas omväxlande i samma program.
Definiera entitetsklasser
Följande exempel är en implementering av en entitet Counter som lagrar ett enda värde av typen heltal och erbjuder fyra åtgärder Add, Reset, Getoch 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>();
}
Funktionen Run innehåller den pannplåt som krävs för att använda den klassbaserade syntaxen. Det måste vara en statisk Azure-funktion. Den körs en gång för varje åtgärdsmeddelande som bearbetas av entiteten. När DispatchAsync<T> anropas och entiteten inte redan finns i minnet konstruerar den ett objekt av typen T och fyller i fälten från den senast bevarade JSON som hittades i lagringen (om någon). Sedan anropas metoden med matchande namn.
Funktionen EntityTriggerRun i det här exemplet behöver inte finnas i själva entitetsklassen. Den kan finnas på valfri giltig plats för en Azure-funktion: i namnområdet på den översta nivån eller i en toppnivåklass. Men om den kapslas djupare (t.ex. att funktionen deklareras i en kapslad klass) identifieras inte den här funktionen av den senaste körningen.
Kommentar
Tillståndet för en klassbaserad entitet skapas implicit innan entiteten bearbetar en åtgärd och kan tas bort explicit i en åtgärd genom att anropa Entity.Current.DeleteState().
Kommentar
Du behöver Azure Functions Core Tools-versionen4.0.5455 eller senare för att köra entiteter i den isolerade modellen.
Det finns två sätt att definiera en entitet som en klass i den isolerade C#-arbetsmodellen. De skapar entiteter med olika tillståndsserialiseringsstrukturer.
Med följande metod serialiseras hela objektet när en entitet definieras.
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>();
}
En TaskEntity<TState>-baserad implementering som gör det enkelt att använda beroendeinmatning. I det här fallet deserialiseras tillståndet till State egenskapen och ingen annan egenskap serialiseras/deserialiseras.
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>();
}
Varning
När du skriver entiteter som härleds från ITaskEntity eller TaskEntity<TState>är det viktigt att inte namnge entitetsutlösarmetoden RunAsync. Detta orsakar körningsfel när entiteten anropas eftersom det finns en tvetydig matchning med metodnamnet "RunAsync", då ITaskEntity redan definierar en "RunAsync" på instansnivå.
Ta bort entiteter i den isolerade modellen
Du kan ta bort en entitet i den isolerade modellen genom att ange entitetstillståndet till null, och den här processen beror på den entitetsimplementeringssökväg som används:
- När du härleder från
ITaskEntityeller använder funktionsbaserad syntax utförs borttagningen genom att anropaTaskEntityOperation.State.SetState(null). - När du härleder från
TaskEntity<TState>definieras borttagning implicit. Det kan dock åsidosättas genom att definiera en metodDeleteför entiteten. Tillstånd kan också tas bort från valfri åtgärd viathis.State = null.- Om du vill ta bort genom att ange värdet null måste
TStatedet vara null. - Den implicit definierade borttagningsåtgärden tar bort icke-nullbar
TState.
- Om du vill ta bort genom att ange värdet null måste
- När du använder en POCO som ditt tillstånd (som inte härleds från
TaskEntity<TState>), definieras borttagning implicit. Det går att åsidosätta borttagningsåtgärden genom att definiera en metodDeleteför POCO. Det finns dock inget sätt att ange tillstånd tillnulli POCO-vägen, så den implicit definierade borttagningsåtgärden är den enda sanna borttagningen.
Klasskrav
Entitetsklasser är POCO:er (vanliga gamla CLR-objekt) som inte kräver särskilda superklasser, gränssnitt eller attribut. Observera följande:
- Klassen måste vara konstruktionsbar (se Entitetskonstruktion).
- Klassen måste vara JSON-serializable (se Entitets serialisering).
Dessutom måste alla metoder som anropas som en åtgärd uppfylla andra krav:
- En åtgärd måste ha högst ett argument men inte ha några överlagringar eller allmänna typargument.
- En åtgärd som är avsedd att anropas från en orkestrering med hjälp av ett gränssnitt måste returnera
TaskellerTask<T>. - Argument och returvärden måste vara serialiserbara värden eller objekt.
Vad kan åtgärder göra?
Alla entitetsåtgärder kan läsa och uppdatera entitetstillståndet, och ändringar i tillståndet sparas automatiskt till lagring. Dessutom kan åtgärder utföra externa I/O eller andra beräkningar inom de allmänna gränser som är gemensamma för alla Azure Functions.
Åtgärder har också åtkomst till funktioner som tillhandahålls av kontexten Entity.Current :
-
EntityName: namnet på den entitet som körs just nu. -
EntityKey: nyckeln för den entitet som körs just nu. -
EntityId: ID:t för den entitet som körs (innehåller namn och nyckel). -
SignalEntity: skickar ett enkelriktad meddelande till en entitet. -
CreateNewOrchestration: startar en ny orkestrering. -
DeleteState: tar bort tillståndet för den här entiteten.
Vi kan till exempel ändra räknarentiteten så att den startar en orkestrering när räknaren når 100 och skickar entitets-ID:t som ett indataargument:
public void Add(int amount)
{
if (this.Value < 100 && this.Value + amount >= 100)
{
Entity.Current.StartNewOrchestration("MilestoneReached", Entity.Current.EntityId);
}
this.Value += amount;
}
Åtkomst till entiteter direkt
Klassbaserade entiteter kan nås direkt med explicita strängnamn för entiteten och dess åtgärder. Det här avsnittet innehåller exempel. En djupare förklaring av de underliggande begreppen (till exempel signaler kontra anrop) finns i diskussionen i Åtkomstentiteter.
Kommentar
Där det är möjligt bör du komma åt entiteter via gränssnitt, eftersom det ger mer typkontroll.
Exempel: entiteten klientsignaler
Följande Azure Http-funktion implementerar en DELETE-åtgärd med hjälp av REST-konventioner. Den skickar en borttagningssignal till räknarentiteten vars nyckel skickas i URL-sökvägen.
[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);
}
Exempel: klienten läser entitetstillstånd
Följande Azure HTTP-funktion implementerar en GET-åtgärd med hjälp av REST-konventioner. Den läser det aktuella tillståndet för räknarentiteten vars nyckel skickas i URL-sökvägen.
[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);
}
Kommentar
Objektet som returneras av ReadEntityStateAsync är bara en lokal kopia, det vill säga en ögonblicksbild av entitetstillståndet från en tidigare tidpunkt. I synnerhet kan det vara inaktuellt, och att ändra det här objektet har ingen effekt på den faktiska entiteten.
Exempel: Orkestrering signalerar först och anropar sedan en entitet
Följande orkestrering signalerar en räknarentitet för att öka den och anropar sedan samma entitet för att läsa dess senaste värde.
[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;
}
Exempel: entiteten klientsignaler
Följande Azure HTTP-funktion implementerar en DELETE-åtgärd med hjälp av REST-konventioner. Den skickar en borttagningssignal till räknarentiteten vars nyckel skickas i URL-sökvägen.
[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);
}
Exempel: klienten läser entitetstillstånd
Följande Azure HTTP-funktion implementerar en GET-åtgärd med hjälp av REST-konventioner. Den läser det aktuella tillståndet för räknarentiteten vars nyckel skickas i URL-sökvägen.
[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;
}
Exempel: Orkestrering signalerar först och anropar sedan en entitet
Följande orkestrering signalerar en räknarentitet för att öka den och anropar sedan samma entitet för att läsa dess senaste värde.
[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;
}
Åtkomst till entiteter via gränssnitt
Gränssnitt kan användas för åtkomst till entiteter via genererade proxyobjekt. Den här metoden säkerställer att namnet och argumenttypen för en åtgärd matchar det som implementeras. Vi rekommenderar att du använder gränssnitt när det är möjligt för åtkomst till entiteter.
Vi kan till exempel ändra räknarexemplet:
public interface ICounter
{
void Add(int amount);
Task Reset();
Task<int> Get();
void Delete();
}
public class Counter : ICounter
{
...
}
Entitetsklasser och entitetsgränssnitt liknar de korn- och korniga gränssnitt som populariserats av Orleans. Mer information om likheter och skillnader mellan durable entiteter och Orleans finns i Jämförelse med virtuella aktörer.
Förutom att tillhandahålla typkontroll är gränssnitt användbara för en bättre uppdelning av problem i programmet. Eftersom en entitet till exempel kan implementera flera gränssnitt kan en enda entitet hantera flera roller. Eftersom flera entiteter kan implementera ett gränssnitt kan allmänna kommunikationsmönster implementeras som återanvändbara bibliotek.
Exempel: klientsignaler entitet via gränssnitt
Klientkod kan använda SignalEntityAsync<TEntityInterface> för att skicka signaler till entiteter som implementerar TEntityInterface. Till exempel:
[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);
}
I det här exemplet är parametern proxy en dynamiskt genererad instans av ICounter, som internt översätter anropet till Delete till en signal.
Kommentar
SignalEntityAsync API:erna kan endast användas för enkelriktade åtgärder. Även om en åtgärd returnerar Task<T>är värdet för parametern T alltid null eller default i stället för det faktiska resultatet. Det är till exempel inte meningsfullt att signalera Get åtgärden eftersom den inte returnerar något värde. I stället kan klienter antingen använda ReadStateAsync för att komma åt räknartillståndet direkt eller starta en orkestreringsfunktion som anropar åtgärden Get .
Exempel: Orkestreringen signalerar först och anropar sedan en entitet via en proxy.
Om du vill anropa eller signalera en entitet inifrån en orkestrering CreateEntityProxy kan du, tillsammans med gränssnittstypen, generera en proxy för entiteten. Den här proxyn kan sedan användas för att anropa eller signalera åtgärder:
[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 signaleras alla åtgärder som returneras void och alla åtgärder som returneras Task eller Task<T> anropas. Man kan ändra det här standardbeteendet och signalåtgärder även om de returnerar Aktivitet med hjälp SignalEntity<IInterfaceType> av metoden explicit.
Kortare alternativ för att ange målet
När du anropar eller signalerar en entitet med ett gränssnitt måste det första argumentet ange målentiteten. Målet kan anges genom att definiera entitets-ID:t eller bara entitetsnyckeln när det bara finns en klass som implementerar entiteten:
context.SignalEntity<ICounter>(new EntityId(nameof(Counter), "myCounter"), ...);
context.SignalEntity<ICounter>("myCounter", ...);
Om endast entitetsnyckeln har angetts och en unik implementering inte kan hittas vid körningen genereras InvalidOperationException .
Begränsningar för entitetsgränssnitt
Som vanligt måste alla parameter- och returtyper vara JSON-serialiserbara. Annars genereras serialiseringsundundentag vid körning. Följande regler gäller även:
- Entitetsgränssnitt måste definieras i samma sammansättning som entitetsklassen.
- Entitetsgränssnitt får endast definiera metoder.
- Entitetsgränssnitt får inte innehålla generiska parametrar.
- Entitetsgränssnittsmetoder får inte ha fler än en parameter.
- Entitetsgränssnittsmetoder måste returnera
void,TaskellerTask<T>.
Om någon av dessa regler överträds genereras en InvalidOperationException vid körning när gränssnittet används som ett typargument till SignalEntity, SignalEntityAsynceller CreateEntityProxy. Undantagsmeddelandet förklarar vilken regel som bröts.
Kommentar
Gränssnittsmetoder som returneras void kan bara signaleras (enkelriktade), inte anropas (tvåvägs). Gränssnittsmetoder som returnerar Task eller Task<T> kan anropas eller signaleras. Om det anropas returnerar de resultatet av åtgärden eller återgenererar undantag som genereras av åtgärden. Om de signaleras returnerar de standardvärdet och inte det faktiska resultatet eller undantaget från åtgärden.
Detta stöds i nuläget inte i .NET-isolerad arbetsprocess.
Entitets serialisering
Eftersom tillståndet för en entitet är varaktigt beständiga måste entitetsklassen vara serialiserbar. Durable Functions-körningen använder Json.NET-biblioteket för detta ändamål, som stöder principer och attribut för att styra serialiserings- och deserialiseringsprocessen. De vanligaste C#-datatyperna (inklusive matriser och samlingstyper) är redan serialiserbara och kan enkelt användas för att definiera tillståndet för varaktiga entiteter.
Till exempel kan Json.NET enkelt serialisera och deserialisera följande klass:
[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;
}
...
}
Serialiseringsattribut
I exemplet ovan innehåller vi flera attribut för att göra den underliggande serialiseringen mer synlig:
- Vi kommenterar klassen med
[JsonObject(MemberSerialization.OptIn)]för att påminna oss om att klassen måste vara serialiserbar och att endast bevara medlemmar som uttryckligen har markerats som JSON-egenskaper. - Vi kommenterar de fält som ska sparas med
[JsonProperty("name")]för att påminna oss om att ett fält är en del av det bevarade entitetstillståndet och för att ange det egenskapsnamn som ska användas i JSON-representationen.
Dessa attribut krävs dock inte. andra konventioner eller attribut tillåts så länge de fungerar med Json.NET. Till exempel kan man använda [DataContract] attribut eller inga attribut alls:
[DataContract]
public class Counter
{
[DataMember]
public int Value { get; set; }
...
}
public class Counter
{
public int Value;
...
}
Som standard lagras inte namnet på klassen* som en del av JSON-representationen: det vill säga vi använder TypeNameHandling.None som standardinställning. Det här standardbeteendet kan åsidosättas med hjälp av JsonObject eller JsonProperty attribut.
Göra ändringar i klassdefinitioner
Viss försiktighet krävs när du gör ändringar i en klassdefinition efter att ett program har körts eftersom det lagrade JSON-objektet inte längre kan matcha den nya klassdefinitionen. Det är ofta möjligt att hantera ändrade dataformat korrekt så länge man förstår deserialiseringsprocessen som används av JsonConvert.PopulateObject. Följande är exempel på ändringar och deras inverkan:
- När en ny egenskap som inte finns i den lagrade JSON läggs till, förutsätter den dess standardvärde.
- När en egenskap som finns i den lagrade JSON tas bort går det tidigare innehållet förlorat.
- När en egenskap byter namn blir effekten att den gamla tas bort och en ny läggs till.
- När en egenskapstyp ändras så att den inte kan deserialiseras från den lagrade JSON:en genereras ett undantag.
- När en egenskapstyp ändras så att den fortfarande kan deserialiseras från den lagrade JSON gör den det.
Det finns många tillgängliga alternativ för att anpassa beteendet för Json.NET. Om du till exempel vill framtvinga ett undantag om den lagrade JSON-filen innehåller ett fält som inte finns i klassen anger du attributet JsonObject(MissingMemberHandling = MissingMemberHandling.Error). Det går också att skriva anpassad kod för deserialisering som kan läsa JSON som lagras i godtyckliga format.
Standardbeteendet för serialisering har ändrats från Newtonsoft.Json till System.Text.Json. Mer information finns i Anpassa serialisering och deserialisering.
Entitetskonstruktion
Ibland vill vi utöva mer kontroll över hur entitetsobjekt skapas. Vi beskriver nu flera alternativ för att ändra standardbeteendet när du skapar entitetsobjekt.
Anpassad initiering vid första åtkomsten
Ibland behöver vi utföra en särskild initiering innan vi skickar en åtgärd till en entitet som aldrig har använts eller som har tagits bort. Om du vill ange det här beteendet kan du lägga till en villkorsstyrd före DispatchAsync:
[FunctionName(nameof(Counter))]
public static Task Run([EntityTrigger] IDurableEntityContext ctx)
{
if (!ctx.HasState)
{
ctx.SetState(...);
}
return ctx.DispatchAsync<Counter>();
}
Bindningar i entitetsklasser
Till skillnad från vanliga funktioner har entitetsklassmetoder inte direkt åtkomst till indata- och utdatabindningar. I stället måste bindningsdata samlas in i funktionsdeklarationen för startpunkt och skickas sedan till DispatchAsync<T> metoden. Alla objekt som skickas till DispatchAsync<T> skickas automatiskt till entitetsklasskonstruktorn som ett argument.
I följande exempel visas hur en CloudBlobContainer referens från blobindatabindningen kan göras tillgänglig för en klassbaserad entitet.
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);
}
}
Mer information om bindningar i Azure Functions finns i Azure Functions-utlösare och bindningar.
Beroendeinmatning i entitetsklasser
Entitetsklasser stöder Azure Functions-beroendeinmatning. I följande exempel visas hur du registrerar en IHttpClientFactory tjänst i en klassbaserad entitet.
[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]
namespace MyNamespace
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
}
}
}
Följande kodfragment visar hur du införlivar den inmatade tjänsten i din entitetsklass:
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>();
}
Anpassad initiering vid första åtkomsten
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;
}
}
Bindningar i entitetsklasser
I följande exempel visas hur du använder en blobindatabindning i en klassbaserad entitet.
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);
}
}
Mer information om bindningar i Azure Functions finns i dokumentationen om Azure Functions-utlösare och bindningar .
Beroendeinmatning i entitetsklasser
Entitetsklasser stöder Azure Functions-beroendeinmatning.
I följande exempel visas hur du konfigurerar en HttpClient i program.cs filen som ska importeras senare i entitetsklassen:
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();
}
}
I följande exempel visas hur du införlivar den inmatade tjänsten i din entitetsklass:
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>();
}
Kommentar
Undvik problem med serialisering genom att undanta fält som lagrar inmatade värden från serialiseringen.
Kommentar
Till skillnad från när du använder konstruktorinmatning i vanliga .NET Azure Functions måste funktionens startpunktsmetod för klassbaserade entiteter deklareras static. Om du deklarerar en funktionsstartpunkt som inte är statisk kan det orsaka konflikter mellan den normala Azure Functions-objektinitieraren och objektinitieraren Durable Entities.
Funktionsbaserad syntax
Hittills har vi fokuserat på den klassbaserade syntaxen, eftersom vi förväntar oss att den passar bättre för de flesta program. Den funktionsbaserade syntaxen kan dock vara lämplig för program som vill definiera eller hantera sina egna abstraktioner för entitetstillstånd och åtgärder. Det kan också vara lämpligt när du implementerar bibliotek som kräver generiskhet som för närvarande inte stöds av den klassbaserade syntaxen.
Med den funktionsbaserade syntaxen hanterar entitetsfunktionen uttryckligen åtgärdens sändning och hanterar uttryckligen tillståndet för entiteten. Följande kod visar till exempel entiteten Counter implementerad med hjälp av den funktionsbaserade syntaxen.
[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 för entitetskontext
Entitetsspecifika funktioner kan nås via ett kontextobjekt av typen IDurableEntityContext. Det här kontextobjektet är tillgängligt som en parameter för entitetsfunktionen och via egenskapen async-local Entity.Current.
Följande medlemmar ger information om den aktuella åtgärden och hjälp med att ange ett returvärde:
-
EntityName: Namnet på den entitet som körs just nu -
EntityKey: Nyckeln för den entitet som körs just nu -
EntityId: ID:t för den entitet som körs (innehåller namn och nyckel) -
OperationName: Namnet på den aktuella åtgärden -
GetInput<TInput>(): Hämtar indata för den aktuella åtgärden -
Return(arg): Returnerar ett värde till orkestreringen som anropade åtgärden
Följande medlemmar hanterar tillståndet för entiteten (skapa, läsa, uppdatera, ta bort):
-
HasState: Om entiteten finns; d.v.s. har ett visst tillstånd -
GetState<TState>(): Hämtar den aktuella statusen för entiteten och skapar en om den inte finns -
SetState(arg): Skapar eller uppdaterar tillståndet för entiteten -
DeleteState(): Tar bort tillståndet för entiteten om den finns
Om tillståndet som returneras av GetState är ett objekt kan programkoden ändra det. Det finns ingen anledning att ringa SetState igen i slutet (men inte heller någon skada). Om GetState<TState> anropas flera gånger måste samma typ användas.
Slutligen signalerar följande medlemmar andra entiteter eller startar nya orkestreringar:
-
SignalEntity(EntityId, operation, input): Skickar ett enkelriktad meddelande till en entitet -
CreateNewOrchestration(orchestratorFunctionName, input): Startar en ny orkestrering
[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;
});
}