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.
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 Add
kínál , Reset
Get
é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.
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
ITaskEntity
származik vagy használ, a törlés hívássalTaskEntityOperation.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ódusDelete
definiálásával az entitáson. Az állapot bármely műveletből törölhető a használatávalthis.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ű
TState
műveletet töröl.
- A null értékre állítással történő törléshez null értékűnek kell
- 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ódusDelete
meghatározásával a POCO-n. A POCO-útvonalon azonban nem lehet állapototnull
beállítani, így az implicit módon definiált törlési művelet az egyetlen igaz törlés.
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:
- Az osztálynak konstruktornak kell lennie (lásd az entitások felépítését).
- Az osztálynak JSON-szerializálhatónak kell lennie (lásd : Entitásszerializálás).
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.
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;
}
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.
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);
}
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.
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;
}
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);
}
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;
}
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;
}
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.
Az ügyfélkód képes SignalEntityAsync<TEntityInterface>
jeleket küldeni azokat az entitásoknak, amelyek implementálják TEntityInterface
. Példa:
[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 default
vagy 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.
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.
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.
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
,Task
vagyTask<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 SignalEntity
használják az , SignalEntityAsync
vagy 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 Task
Task<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.
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;
}
...
}
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 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.PopulateObject
deszerializá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.
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.
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 DispatchAsync
következő elé:
[FunctionName(nameof(Counter))]
public static Task Run([EntityTrigger] IDurableEntityContext ctx)
{
if (!ctx.HasState)
{
ctx.SetState(...);
}
return ctx.DispatchAsync<Counter>();
}
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 CloudBlobContainer
blobbemeneti 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 .
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>();
}
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;
}
}
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 .
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 static
kell. 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.
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ásspecifikus funkciók egy típusú IDurableEntityContext
kö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.Current
keresztü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;
});
}