Ontwikkelaars handleiding voor duurzame entiteiten in .NETDeveloper's guide to durable entities in .NET

In dit artikel beschrijven we de beschik bare interfaces voor het ontwikkelen van duurzame entiteiten met .NET in detail, inclusief voor beelden en algemeen advies.In this article, we describe the available interfaces for developing durable entities with .NET in detail, including examples and general advice.

Entiteits functies bieden ontwikkel aars van serverloze toepassingen een handige manier om de toepassings status te organiseren als een verzameling van verfijnde entiteiten.Entity functions provide serverless application developers with a convenient way to organize application state as a collection of fine-grained entities. Zie het artikel over duurzame entiteiten: concepten voor meer informatie over de onderliggende concepten.For more detail about the underlying concepts, see the Durable Entities: Concepts article.

Momenteel bieden we twee Api's voor het definiëren van entiteiten:We currently offer two APIs for defining entities:

  • De op klassen gebaseerde syntaxis vertegenwoordigt entiteiten en bewerkingen als klassen en methoden.The class-based syntax represents entities and operations as classes and methods. Deze syntaxis produceert gemakkelijk Lees bare code en Hiermee kunnen bewerkingen worden aangeroepen door middel van een type ingeschakelde interface via interfaces.This syntax produces easily readable code and allows operations to be invoked in a type-checked manner through interfaces.

  • De syntaxis op basis van functies is een interface op een lager niveau die entiteiten als functies vertegenwoordigt.The function-based syntax is a lower-level interface that represents entities as functions. Het biedt nauw keurige controle over hoe de entiteits bewerkingen worden verzonden en hoe de status van de entiteit wordt beheerd.It provides precise control over how the entity operations are dispatched, and how the entity state is managed.

Dit artikel richt zich voornamelijk op de op klassen gebaseerde syntaxis, aangezien we verwachten dat deze beter geschikt zijn voor de meeste toepassingen.This article focuses primarily on the class-based syntax, as we expect it to be better suited for most applications. De syntaxis op basis van functies kan echter geschikt zijn voor toepassingen die hun eigen abstracties voor entiteits status en-bewerkingen willen definiëren of beheren.However, the function-based syntax may be appropriate for applications that wish to define or manage their own abstractions for entity state and operations. Het kan ook geschikt zijn voor het implementeren van bibliotheken die op dit moment niet worden ondersteund door de op klassen gebaseerde syntaxis.Also, it may be appropriate for implementing libraries that require genericity not currently supported by the class-based syntax.

Notitie

De op klassen gebaseerde syntaxis is slechts een laag bovenop de syntaxis op basis van functies, zodat beide varianten door elkaar kunnen worden gebruikt in dezelfde toepassing.The class-based syntax is just a layer on top of the function-based syntax, so both variants can be used interchangeably in the same application.

Entiteits klassen definiërenDefining entity classes

Het volgende voor beeld is een implementatie van een Counter-entiteit die één waarde van het type integer opslaat en vier bewerkingen Add, Reset, Geten Deletebiedt.The following example is an implementation of a Counter entity that stores a single value of type integer, and offers four operations Add, Reset, Get, and 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>();
}

De functie Run bevat de standaard die is vereist voor het gebruik van de op klassen gebaseerde syntaxis.The Run function contains the boilerplate required for using the class-based syntax. Dit moet een statische Azure-functie zijn.It must be a static Azure Function. Deze wordt eenmaal uitgevoerd voor elk bewerkings bericht dat door de entiteit wordt verwerkt.It executes once for each operation message that is processed by the entity. Als DispatchAsync<T> wordt aangeroepen en de entiteit nog niet in het geheugen is, wordt een object van het type T gemaakt en worden de bijbehorende velden gevuld van de laatste persistente JSON die in de opslag is gevonden (indien van toepassing).When DispatchAsync<T> is called and the entity isn't already in memory, it constructs an object of type T and populates its fields from the last persisted JSON found in storage (if any). Vervolgens wordt de-methode aangeroepen met de overeenkomende naam.Then it invokes the method with the matching name.

Notitie

De status van een entiteit op basis van een klasse wordt impliciet gemaakt voordat de entiteit een bewerking verwerkt en kan expliciet in een bewerking worden verwijderd door Entity.Current.DeleteState()aan te roepen.The state of a class-based entity is created implicitly before the entity processes an operation, and can be deleted explicitly in an operation by calling Entity.Current.DeleteState().

Klasse-vereistenClass Requirements

Entiteits klassen zijn POCOs (gewone, verouderde CLR-objecten) waarvoor geen speciale superklassen, interfaces of kenmerken zijn vereist.Entity classes are POCOs (plain old CLR objects) that require no special superclasses, interfaces, or attributes. IndienHowever:

Daarnaast moet de methode die is bedoeld om te worden aangeroepen als een bewerking, voldoen aan aanvullende vereisten:Also, any method that is intended to be invoked as an operation must satisfy additional requirements:

  • Een bewerking mag Maxi maal één argument hebben en mag geen Overloads of algemene type argumenten hebben.An operation must have at most one argument, and must not have any overloads or generic type arguments.
  • Een bewerking die moet worden aangeroepen vanuit een indeling met behulp van een interface, moet Task of Task<T>retour neren.An operation meant to be called from an orchestration using an interface must return Task or Task<T>.
  • Argumenten en retour waarden moeten serialiseerbare waarden of objecten zijn.Arguments and return values must be serializable values or objects.

Wat kunnen bewerkingen doen?What can operations do?

Alle entiteits bewerkingen kunnen de status van de entiteit lezen en bijwerken, en wijzigingen in de status worden automatisch opgeslagen in de opslag.All entity operations can read and update the entity state, and changes to the state are automatically persisted to storage. Bovendien kunnen bewerkingen externe I/O-of andere berekeningen uitvoeren, binnen de algemene limieten die gelden voor alle Azure Functions.Moreover, operations can perform external I/O or other computations, within the general limits common to all Azure Functions.

Bewerkingen hebben ook toegang tot de functionaliteit van de Entity.Current context:Operations also have access to functionality provided by the Entity.Current context:

  • EntityName: de naam van de entiteit die momenteel wordt uitgevoerd.EntityName: the name of the currently executing entity.
  • EntityKey: de sleutel van de entiteit die momenteel wordt uitgevoerd.EntityKey: the key of the currently executing entity.
  • EntityId: de ID van de entiteit die momenteel wordt uitgevoerd (inclusief naam en sleutel).EntityId: the ID of the currently executing entity (includes name and key).
  • SignalEntity: verzendt een eenrichtings bericht naar een entiteit.SignalEntity: sends a one-way message to an entity.
  • CreateNewOrchestration: er wordt een nieuwe indeling gestart.CreateNewOrchestration: starts a new orchestration.
  • DeleteState: de status van deze entiteit wordt verwijderd.DeleteState: deletes the state of this entity.

We kunnen de entiteit teller bijvoorbeeld zodanig wijzigen dat een indeling wordt gestart wanneer de teller 100 bereikt en de entiteit-ID als invoer argument wordt door gegeven:For example, we can modify the counter entity so it starts an orchestration when the counter reaches 100 and passes the entity ID as an input argument:

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

Rechtstreeks toegang tot entiteitenAccessing entities directly

Op klassen gebaseerde entiteiten kunnen rechtstreeks worden geopend, met behulp van expliciete teken reeks namen voor de entiteit en de bijbehorende bewerkingen.Class-based entities can be accessed directly, using explicit string names for the entity and its operations. Hieronder vindt u enkele voor beelden. Zie de discussie in Access entitiesvoor een diep gaande uitleg van de onderliggende concepten (zoals signalen versus aanroepen).We provide some examples below; for a deeper explanation of the underlying concepts (such as signals vs. calls) see the discussion in Access entities.

Notitie

Waar mogelijk wordt u aangeraden om toegang te krijgen tot entiteiten via interfaces, omdat deze meer type controle biedt.Where possible, we recommend Accessing entities through interfaces, because it provides more type checking.

Voor beeld: entiteit client signalenExample: client signals entity

De volgende Azure http-functie implementeert een Verwijder bewerking met behulp van REST-conventies.The following Azure Http Function implements a DELETE operation using REST conventions. Er wordt een delete-signaal verzonden naar de item entiteit waarvan de sleutel wordt door gegeven in het URL-pad.It sends a delete signal to the counter entity whose key is passed in the URL path.

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

Voor beeld: client leest entiteits statusExample: client reads entity state

De volgende Azure http-functie implementeert een GET-bewerking met behulp van REST-conventies.The following Azure Http Function implements a GET operation using REST conventions. Hiermee wordt de huidige status van de item entiteit gelezen waarvan de sleutel wordt door gegeven in het URL-pad.It reads the current state of the counter entity whose key is passed in the URL path.

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

Notitie

Het object dat door ReadEntityStateAsync wordt geretourneerd, is slechts een lokale kopie, dat wil zeggen, een moment opname van de entiteits status van een eerder tijdstip.The object returned by ReadEntityStateAsync is just a local copy, that is, a snapshot of the entity state from some earlier point in time. Dit kan met name verlopen en het wijzigen van dit object heeft geen invloed op de werkelijke entiteit.In particular, it may be stale, and modifying this object has no effect on the actual entity.

Voor beeld: Orchestration-eerste signalen en vervolgens entiteit aanroepenExample: orchestration first signals, then calls entity

In de volgende indeling wordt een item entiteit gesignaleerd om deze te verhogen en vervolgens wordt dezelfde entiteit aangeroepen om de meest recente waarde te lezen.The following orchestration signals a counter entity to increment it, and then calls the same entity to read its latest value.

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

Toegang tot entiteiten via interfacesAccessing entities through interfaces

Interfaces kunnen worden gebruikt voor toegang tot entiteiten via gegenereerde proxy-objecten.Interfaces can be used for accessing entities via generated proxy objects. Deze aanpak zorgt ervoor dat de naam en het argument type van een bewerking overeenkomt met wat er wordt geïmplementeerd.This approach ensures that the name and argument type of an operation matches what is implemented. U wordt aangeraden interfaces te gebruiken voor het verkrijgen van toegang tot entiteiten wanneer dat mogelijk is.We recommend using interfaces for accessing entities whenever possible.

Zo kunnen we het voor beeld van het item als volgt wijzigen:For example, we can modify the counter example as follows:

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

public class Counter : ICounter
{
    ...
}

Entiteits klassen en entiteits interfaces zijn vergelijkbaar met die van de korrels en korrels die worden gepopulaird door Orleans.Entity classes and entity interfaces are similar to the grains and grain interfaces popularized by Orleans. Zie vergelijking met virtuele actorsvoor meer informatie over overeenkomsten en verschillen tussen duurzame entiteiten en Orleans.For a more information about similarities and differences between Durable Entities and Orleans, see Comparison with virtual actors.

Naast het leveren van type controle zijn interfaces handig voor een betere schei ding van de problemen binnen de toepassing.Besides providing type checking, interfaces are useful for a better separation of concerns within the application. Omdat een entiteit bijvoorbeeld meerdere interfaces kan implementeren, kan één entiteit meerdere rollen hebben.For example, since an entity may implement multiple interfaces, a single entity can serve multiple roles. Omdat een interface mogelijk door meerdere entiteiten kan worden geïmplementeerd, kunnen algemene communicatie patronen worden geïmplementeerd als herbruikbare bibliotheken.Also, since an interface may be implemented by multiple entities, general communication patterns can be implemented as reusable libraries.

Voor beeld: client signaleert entiteit via interfaceExample: client signals entity through interface

Client code kan SignalEntityAsync<TEntityInterface> gebruiken om signalen te verzenden naar entiteiten die TEntityInterfaceimplementeren.Client code can use SignalEntityAsync<TEntityInterface> to send signals to entities that implement TEntityInterface. Bijvoorbeeld:For example:

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

In dit voor beeld is de para meter proxy een dynamisch gegenereerd exemplaar van ICounter, dat intern de aanroep naar Delete in een signaal zet.In this example, the proxy parameter is a dynamically generated instance of ICounter, which internally translates the call to Delete into a signal.

Notitie

De SignalEntityAsync-Api's kunnen alleen worden gebruikt voor eenrichtings bewerkingen.The SignalEntityAsync APIs can be used only for one-way operations. Zelfs als een bewerking Task<T>retourneert, is de waarde van de para meter T altijd Null of default, niet het werkelijke resultaat.Even if an operation returns Task<T>, the value of the T parameter will always be null or default, not the actual result. Het is bijvoorbeeld niet zinvol om de Get bewerking te Signa leren, omdat er geen waarde wordt geretourneerd.For example, it doesn't make sense to signal the Get operation, as no value is returned. In plaats daarvan kunnen clients ReadStateAsync gebruiken om rechtstreeks toegang te krijgen tot de status van het item, of kan een Orchestrator-functie worden gestart die de Get-bewerking aanroept.Instead, clients can use either ReadStateAsync to access the counter state directly, or can start an orchestrator function that calls the Get operation.

Voor beeld: Orchestration-eerste signalen en vervolgens entiteit aanroepen via proxyExample: orchestration first signals, then calls entity through proxy

Als u een entiteit vanuit een indeling wilt aanroepen of als u een signaal wilt ontvangen, kunt u CreateEntityProxy gebruiken, samen met het interface type, om een proxy voor de entiteit te genereren.To call or signal an entity from within an orchestration, CreateEntityProxy can be used, along with the interface type, to generate a proxy for the entity. Deze proxy kan vervolgens worden gebruikt om bewerkingen uit te voeren of te Signa leren:This proxy can then be used to call or signal operations:

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

Alle bewerkingen die void retour neren, worden impliciet gesignaleerd en alle bewerkingen die Task of Task<T> retour neren worden genoemd.Implicitly, any operations that return void are signaled, and any operations that return Task or Task<T> are called. Eén kan dit standaard gedrag wijzigen en signaal bewerkingen, zelfs als ze een taak retour neren met behulp van de methode SignalEntity<IInterfaceType> expliciet.One can change this default behavior, and signal operations even if they return Task, by using the SignalEntity<IInterfaceType> method explicitly.

Kortere optie voor het opgeven van het doelShorter option for specifying the target

Wanneer u een entiteit aanroept of signaleert met behulp van een interface, moet in het eerste argument de doel entiteit worden opgegeven.When calling or signaling an entity using an interface, the first argument must specify the target entity. U kunt het doel opgeven door de entiteit-ID op te geven, of, in gevallen waarin er slechts één klasse is die de entiteit implementeert, alleen de entiteits sleutel:The target can be specified either by specifying the entity ID, or, in cases where there's just one class that implements the entity, just the entity key:

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

Als alleen de entiteits sleutel is opgegeven en tijdens runtime geen unieke implementatie wordt gevonden, wordt InvalidOperationException gegenereerd.If only the entity key is specified and a unique implementation can't be found at runtime, InvalidOperationException is thrown.

Beperkingen voor entiteits interfacesRestrictions on entity interfaces

Zoals gebruikelijk, moeten alle para meters en retour typen JSON-serialiseerbaar zijn.As usual, all parameter and return types must be JSON-serializable. Anders worden er uitzonde ringen voor serialisatie gegenereerd tijdens runtime.Otherwise, serialization exceptions are thrown at runtime.

Daarnaast worden enkele extra regels afgedwongen:We also enforce some additional rules:

  • Entiteits interfaces moeten alleen methoden definiëren.Entity interfaces must only define methods.
  • Entiteits interfaces mogen geen generieke para meters bevatten.Entity interfaces must not contain generic parameters.
  • De methoden van de entity-interface mogen niet meer dan één para meter hebben.Entity interface methods must not have more than one parameter.
  • De methoden van de entity-interface moeten void, Taskof Task<T> retour nerenEntity interface methods must return void, Task, or Task<T>

Als een van deze regels wordt geschonden, wordt tijdens runtime een InvalidOperationException gegenereerd wanneer de interface wordt gebruikt als een type argument voor SignalEntity of CreateProxy.If any of these rules are violated, an InvalidOperationException is thrown at runtime when the interface is used as a type argument to SignalEntity or CreateProxy. In het uitzonderings bericht wordt uitgelegd welke regel is verbroken.The exception message explains which rule was broken.

Notitie

Interface methoden die void retour neren, kunnen alleen worden gesignaleerd (eenrichtings), niet aangeroepen (twee richtingen).Interface methods returning void can only be signaled (one-way), not called (two-way). Interface methoden die Task of Task<T> retour neren, kunnen worden aangeroepen of gedeponeerd zijn.Interface methods returning Task or Task<T> can be either called or signalled. Als deze wordt aangeroepen, retour neren ze het resultaat van de bewerking of genereren ze uitzonde ringen die door de bewerking zijn gegenereerd.If called, they return the result of the operation, or re-throw exceptions thrown by the operation. Als ze echter worden geresulteerd, retour neren ze niet het werkelijke resultaat of de uitzonde ring van de bewerking, maar alleen de standaard waarde.However, when signalled, they do not return the actual result or exception from the operation, but just the default value.

Serialisatie van entiteitEntity serialization

Omdat de status van een entiteit blijvend persistent is, moet de entiteits klasse serialiseerbaar zijn.Since the state of an entity is durably persisted, the entity class must be serializable. De Durable Functions runtime maakt gebruik van de JSON.net -bibliotheek voor dit doel, die een aantal beleids regels en kenmerken ondersteunt om het serialisatie-en deserialisatie proces te beheren.The Durable Functions runtime uses the Json.NET library for this purpose, which supports a number of policies and attributes to control the serialization and deserialization process. Meest gebruikte C# gegevens typen (inclusief matrices en verzamelings typen) zijn al serialiseerbaar en kunnen eenvoudig worden gebruikt voor het definiëren van de status van duurzame entiteiten.Most commonly used C# data types (including arrays and collection types) are already serializable, and can easily be used for defining the state of durable entities.

Json.NET kan bijvoorbeeld eenvoudig de volgende klasse serialiseren en deserialiseren:For example, Json.NET can easily serialize and deserialize the following class:

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

    ...
}

Serialisatie kenmerkenSerialization Attributes

In het bovenstaande voor beeld hebben we gekozen om verschillende kenmerken op te nemen om de onderliggende serialisatie meer zichtbaar te maken:In the example above, we chose to include several attributes to make the underlying serialization more visible:

  • We maken aantekeningen op de klasse met [JsonObject(MemberSerialization.OptIn)] om ons eraan te herinneren dat de klasse serialiseerbaar moet zijn en om alleen leden te blijven die expliciet zijn gemarkeerd als JSON-eigenschappen.We annotate the class with [JsonObject(MemberSerialization.OptIn)] to remind us that the class must be serializable, and to persist only members that are explicitly marked as JSON properties.
  • We geven aantekeningen aan de velden die moeten worden vastgehouden met [JsonProperty("name")] om eraan te herinneren dat een veld deel uitmaakt van de persistente entiteits status en om de naam van de eigenschap die in de JSON-weer gave moet worden gebruikt te specificeren.We annotate the fields to be persisted with [JsonProperty("name")] to remind us that a field is part of the persisted entity state, and to specify the property name to be used in the JSON representation.

Deze kenmerken zijn echter niet vereist. andere conventies of kenmerken zijn toegestaan zolang ze met Json.NET werken.However, these attributes aren't required; other conventions or attributes are permitted as long as they work with Json.NET. Een voor beeld: een kan [DataContract] kenmerken of helemaal geen kenmerken gebruiken.For example, one may use [DataContract] attributes, or no attributes at all:

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

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

De naam van de klasse wordt standaard niet opgeslagen als onderdeel van de JSON-weer gave. dat wil zeggen dat we TypeNameHandling.None als standaard instelling gebruiken.By default, the name of the class is not stored as part of the JSON representation: that is, we use TypeNameHandling.None as the default setting. Dit standaard gedrag kan worden overschreven met JsonObject-of JsonProperty-kenmerken.This default behavior can be overridden using JsonObject or JsonProperty attributes.

Wijzigingen aanbrengen in klassen definitiesMaking changes to class definitions

Let op wanneer u wijzigingen aanbrengt in een klassen definitie nadat een toepassing is uitgevoerd, omdat het opgeslagen JSON-object mogelijk niet meer overeenkomt met de nieuwe klassedefinitie.Some care is required when making changes to a class definition after an application has been run, because the stored JSON object may no longer match the new class definition. Het is echter vaak mogelijk om goed te kunnen omgaan met veranderende gegevens indelingen, zolang het deserialisatie proces dat wordt gebruikt door JsonConvert.PopulateObjectwordt begrepen.Still, it is often possible to deal correctly with changing data formats as long as one understands the deserialization process used by JsonConvert.PopulateObject.

Hier volgen enkele voor beelden van wijzigingen en hun effect:For example, here are some examples of changes and their effect:

  1. Als er een nieuwe eigenschap wordt toegevoegd, die niet aanwezig is in de opgeslagen JSON, wordt ervan uitgegaan dat deze de standaard waarde heeft.If a new property is added, which is not present in the stored JSON, it assumes its default value.
  2. Als een eigenschap wordt verwijderd, die aanwezig is in de opgeslagen JSON, gaat de vorige inhoud verloren.If a property is removed, which is present in the stored JSON, the previous content is lost.
  3. Als de naam van een eigenschap wordt gewijzigd, is het effect alsof het oude wordt verwijderd en een nieuw item wordt toegevoegd.If a property is renamed, the effect is as if removing the old one and adding a new one.
  4. Als het type van een eigenschap wordt gewijzigd zodat deze niet meer kan worden gedeserialiseerd van de opgeslagen JSON, wordt een uitzonde ring gegenereerd.If the type of a property is changed so it can no longer be deserialized from the stored JSON, an exception is thrown.
  5. Als het type van een eigenschap wordt gewijzigd, maar het kan nog steeds worden gedeserialiseerd van de opgeslagen JSON, wordt dit gedaan.If the type of a property is changed, but it can still be deserialized from the stored JSON, it will do so.

Er zijn veel opties beschikbaar voor het aanpassen van het gedrag van Json.NET.There are many options available for customizing the behavior of Json.NET. Als u bijvoorbeeld een uitzonde ring wilt afdwingen als de opgeslagen JSON een veld bevat dat niet aanwezig is in de-klasse, geeft u het kenmerk JsonObject(MissingMemberHandling = MissingMemberHandling.Error)op.For example, to force an exception if the stored JSON contains a field that is not present in the class, specify the attribute JsonObject(MissingMemberHandling = MissingMemberHandling.Error). Het is ook mogelijk om aangepaste code te schrijven voor deserialisatie, waardoor JSON kan worden gelezen die in wille keurige indelingen is opgeslagen.It is also possible to write custom code for deserialization that can read JSON stored in arbitrary formats.

Entiteits constructieEntity construction

Soms willen we meer controle uitoefenen over hoe entiteits objecten worden samengesteld.Sometimes we want to exert more control over how entity objects are constructed. We beschrijven nu diverse opties voor het wijzigen van het standaard gedrag bij het maken van entiteits objecten.We now describe several options for changing the default behavior when constructing entity objects.

Aangepaste initialisatie bij eerste toegangCustom initialization on first access

Af en toe moeten we enige speciale initialisatie uitvoeren voordat een bewerking wordt verzonden naar een entiteit die nooit is geopend of die is verwijderd.Occasionally we need to perform some special initialization before dispatching an operation to an entity that has never been accessed, or that has been deleted. Als u dit gedrag wilt opgeven, kunt u een voorwaardelijke actie toevoegen vóór de DispatchAsync:To specify this behavior, one can add a conditional before the DispatchAsync:

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

Bindingen in entiteits klassenBindings in entity classes

In tegens telling tot reguliere functies hebben entity class-methoden geen directe toegang tot invoer-en uitvoer bindingen.Unlike regular functions, entity class methods don't have direct access to input and output bindings. In plaats daarvan moeten bindings gegevens worden vastgelegd in de declaratie van de invoer punt functie en vervolgens worden door gegeven aan de DispatchAsync<T> methode.Instead, binding data must be captured in the entry-point function declaration and then passed to the DispatchAsync<T> method. Objecten die aan DispatchAsync<T> worden door gegeven, worden automatisch door gegeven aan de constructor van de entiteits klasse als een argument.Any objects passed to DispatchAsync<T> will be automatically passed into the entity class constructor as an argument.

In het volgende voor beeld ziet u hoe een CloudBlobContainer referentie van de BLOB-invoer binding beschikbaar kan worden gemaakt voor een entiteit op basis van een klasse.The following example shows how a CloudBlobContainer reference from the blob input binding can be made available to a class-based entity.

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

Zie de documentatie voor Azure functions triggers en bindingen voor meer informatie over bindingen in azure functions.For more information on bindings in Azure Functions, see the Azure Functions Triggers and Bindings documentation.

Afhankelijkheids injectie in entiteits klassenDependency injection in entity classes

Entiteits klassen ondersteunen Azure functions afhankelijkheids injectie.Entity classes support Azure Functions Dependency Injection. In het volgende voor beeld ziet u hoe u een IHttpClientFactory-service registreert bij een entiteit op basis van een klasse.The following example demonstrates how to register an IHttpClientFactory service into a class-based entity.

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

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

Het volgende code fragment laat zien hoe u de geïnjecteerde service kunt opnemen in uw entiteits klasse.The following snippet demonstrates how to incorporate the injected service into your entity class.

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

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

Notitie

Als u problemen met serialisatie wilt voor komen, moet u ervoor zorgen dat velden die bedoeld zijn voor het opslaan van geïnjecteerde waarden uit de serialisatie uitsluiten.To avoid issues with serialization, make sure to exclude fields meant to store injected values from the serialization.

Notitie

In tegens telling tot het gebruik van constructor-injectie in reguliere .NET Azure Functions, moeten de toegangs punt methode functions worden gedeclareerd static.Unlike when using constructor injection in regular .NET Azure Functions, the functions entry point method for class-based entities must be declared static. Het declareren van een niet-statisch functie-ingangs punt kan conflicten veroorzaken tussen de normale Azure Functions voor object initialisatie en de initialisatie functie voor het object van duurzame entiteiten.Declaring a non-static function entry point may cause conflicts between the normal Azure Functions object initializer and the Durable Entities object initializer.

Syntaxis op basis van functiesFunction-based syntax

Tot nu toe hebben we gefocust op de op klassen gebaseerde syntaxis, aangezien we verwachten dat deze beter geschikt zijn voor de meeste toepassingen.So far we have focused on the class-based syntax, as we expect it to be better suited for most applications. De syntaxis op basis van functies kan echter geschikt zijn voor toepassingen die hun eigen abstracties voor entiteits status en-bewerkingen willen definiëren of beheren.However, the function-based syntax can be appropriate for applications that wish to define or manage their own abstractions for entity state and operations. Het kan ook geschikt zijn bij het implementeren van bibliotheken die op dit moment niet worden ondersteund door de op klassen gebaseerde syntaxis.Also, it may be appropriate when implementing libraries that require genericity not currently supported by the class-based syntax.

Met de functie-gebaseerde syntaxis wordt de bewerking door de entiteits functie expliciet verwerkt en wordt de status van de entiteit expliciet beheerd.With the function-based syntax, the Entity Function explicitly handles the operation dispatch, and explicitly manages the state of the entity. De volgende code toont bijvoorbeeld de item entiteit die is geïmplementeerd met behulp van de syntaxis op basis van de functie.For example, the following code shows the Counter entity implemented using the function-based syntax.

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

Het context object entiteitThe entity context object

U kunt toegang krijgen tot entiteit-specifieke functionaliteit via een context object van het type IDurableEntityContext.Entity-specific functionality can be accessed via a context object of type IDurableEntityContext. Dit context object is beschikbaar als een para meter voor de functie entiteit en via de eigenschap async-Local Entity.Current.This context object is available as a parameter to the entity function, and via the async-local property Entity.Current.

De volgende leden bieden informatie over de huidige bewerking en kunnen we een retour waarde opgeven.The following members provide information about the current operation, and allow us to specify a return value.

  • EntityName: de naam van de entiteit die momenteel wordt uitgevoerd.EntityName: the name of the currently executing entity.
  • EntityKey: de sleutel van de entiteit die momenteel wordt uitgevoerd.EntityKey: the key of the currently executing entity.
  • EntityId: de ID van de entiteit die momenteel wordt uitgevoerd (inclusief naam en sleutel).EntityId: the ID of the currently executing entity (includes name and key).
  • OperationName: de naam van de huidige bewerking.OperationName: the name of the current operation.
  • GetInput<TInput>(): Hiermee wordt de invoer voor de huidige bewerking opgehaald.GetInput<TInput>(): gets the input for the current operation.
  • Return(arg): retourneert een waarde voor de indeling die de bewerking aanroept.Return(arg): returns a value to the orchestration that called the operation.

De volgende leden beheren de status van de entiteit (maken, lezen, bijwerken, verwijderen).The following members manage the state of the entity (create, read, update, delete).

  • HasState: of de entiteit bestaat, dat wil zeggen een bepaalde status heeft.HasState: whether the entity exists, that is, has some state.
  • GetState<TState>(): Hiermee wordt de huidige status van de entiteit opgehaald.GetState<TState>(): gets the current state of the entity. Als deze nog niet bestaat, wordt deze gemaakt.If it does not already exist, it is created.
  • SetState(arg): Hiermee wordt de status van de entiteit gemaakt of bijgewerkt.SetState(arg): creates or updates the state of the entity.
  • DeleteState(): de status van de entiteit wordt verwijderd als deze bestaat.DeleteState(): deletes the state of the entity, if it exists.

Als de status die is geretourneerd door GetState een object is, kan het rechtstreeks worden gewijzigd door de toepassings code.If the state returned by GetState is an object, it can be directly modified by the application code. Het is niet nodig om SetState aan het einde aan te roepen (maar ook geen schade).There is no need to call SetState again at the end (but also no harm). Als GetState<TState> meerdere keren wordt aangeroepen, moet hetzelfde type worden gebruikt.If GetState<TState> is called multiple times, the same type must be used.

Ten slotte worden de volgende leden gebruikt om andere entiteiten te Signa leren, of om nieuwe integraties te starten:Finally, the following members are used to signal other entities, or start new orchestrations:

  • SignalEntity(EntityId, operation, input): verzendt een eenrichtings bericht naar een entiteit.SignalEntity(EntityId, operation, input): sends a one-way message to an entity.
  • CreateNewOrchestration(orchestratorFunctionName, input): er wordt een nieuwe indeling gestart.CreateNewOrchestration(orchestratorFunctionName, input): starts a new orchestration.

Volgende stappenNext steps