Share via


.NET'te dayanıklı varlıklara geliştirici kılavuzu

Bu makalede, örnekler ve genel öneriler de dahil olmak üzere .NET ile dayanıklı varlıklar geliştirmeye yönelik kullanılabilir arabirimler ayrıntılı olarak açıklanmaktadır.

Varlık işlevleri, sunucusuz uygulama geliştiricilerine uygulama durumunu ayrıntılı varlıklar koleksiyonu olarak düzenlemek için kullanışlı bir yol sağlar. Temel kavramlar hakkında daha fazla ayrıntı için Dayanıklı Varlıklar: Kavramlar makalesine bakın.

Şu anda varlıkları tanımlamak için iki API sunuyoruz:

  • Sınıf tabanlı söz dizimi, varlıkları ve işlemleri sınıflar ve yöntemler olarak temsil eder. Bu söz dizimi kolayca okunabilir kod oluşturur ve işlemlerin arabirimler aracılığıyla tür denetimi yapılmış bir şekilde çağrılmasını sağlar.

  • İşlev tabanlı söz dizimi , varlıkları işlev olarak temsil eden alt düzey bir arabirimdir. Varlık işlemlerinin nasıl dağıtıldığında ve varlık durumunun nasıl yönetildiğinde tam denetim sağlar.

Çoğu uygulama için daha uygun olmasını beklediğimiz için bu makale öncelikli olarak sınıf tabanlı söz dizimine odaklanır. Ancak, işlev tabanlı söz dizimi varlık durumu ve işlemleri için kendi soyutlamalarını tanımlamak veya yönetmek isteyen uygulamalar için uygun olabilir. Ayrıca, şu anda sınıf tabanlı söz dizimi tarafından desteklenmeyen genellik gerektiren kitaplıklar uygulamak için de uygun olabilir.

Dekont

Sınıf tabanlı söz dizimi, işlev tabanlı söz diziminin üzerindeki bir katmandır, bu nedenle her iki değişken de aynı uygulamada birbirinin yerine kullanılabilir.

Varlık sınıflarını tanımlama

Aşağıdaki örnek, tamsayı türünde tek bir değeri depolayan ve dört işlem Add( , ResetDeleteGetve ) sunan bir Counter varlığın uygulamasıdır.

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

işlevi, Run sınıf tabanlı söz dizimini kullanmak için gereken ortak değeri içerir. Statik bir Azure İşlevi olmalıdır. Varlık tarafından işlenen her işlem iletisi için bir kez yürütülür. Çağrıldığında DispatchAsync<T> ve varlık henüz bellekte değilse, türünde T bir nesne oluşturur ve alanlarını depolamada bulunan son kalıcı JSON'dan doldurur (varsa). Ardından eşleşen ada sahip yöntemini çağırır.

Bu örnekteki EntityTrigger İşlevin Run Entity sınıfının içinde bulunması gerekmez. Azure İşlevi için geçerli herhangi bir konumda bulunabilir: üst düzey ad alanının içinde veya üst düzey bir sınıfın içinde. Ancak, daha derin iç içe yerleştirilmişse (örneğin, İşlev iç içe bir sınıf içinde bildirilir), bu İşlev en son çalışma zamanı tarafından tanınmaz.

Dekont

Sınıf tabanlı bir varlığın durumu, varlık bir işlemi işlemeden önce örtük olarak oluşturulur ve çağrılarak Entity.Current.DeleteState()bir işlemde açıkça silinebilir.

Dekont

Yalıtılmış modelde varlıkları çalıştırmak için Azure İşlevleri Core Tools sürümü 4.0.5455 veya üzeri gerekir.

C# yalıtılmış çalışan modelinde bir varlığı sınıf olarak tanımlamanın iki yolu vardır. Farklı durum serileştirme yapılarına sahip varlıklar üretirler.

Aşağıdaki yaklaşımla, bir varlık tanımlanırken nesnenin tamamı serileştirilir.

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

TaskEntity<TState>Bağımlılık eklemenin kullanımını kolaylaştıran tabanlı bir uygulama. Bu durumda, durum özelliği için seri durumdan State çıkarılır ve başka hiçbir özellik serileştirilmemiş/seri durumdan çıkarılmamıştır.

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

Uyarı

veya TaskEntity<TState>'den ITaskEntity türetilen varlıklar yazarken varlık tetikleyici yönteminizi RunAsyncadlandırmamak önemlidir. Zaten bir örnek düzeyi "RunAsync" tanımlandığından" "RunAsync" yöntem adıyla belirsiz bir eşleşme olduğundan, bu durum varlığı çağırırken çalışma zamanı hatalarına ITaskEntity neden olur.

Yalıtılmış modeldeki varlıkları silme

Yalıtılmış modeldeki bir varlığı silme işlemi, varlık durumu nullolarak ayarlanarak gerçekleştirilir. Bunun nasıl gerçekleştirildiği, hangi varlık uygulama yolunun kullanıldığına bağlıdır.

  • İşlev tabanlı söz diziminden ITaskEntitytüretilirken veya kullanılırken, silme işlemi çağrılarak TaskEntityOperation.State.SetState(null)gerçekleştirilir.
  • 'den TaskEntity<TState>türetilirken delete örtük olarak tanımlanır. Ancak, varlıkta bir yöntem Delete tanımlanarak geçersiz kılınabilir. Durum, aracılığıyla this.State = nullherhangi bir işlemden de silinebilir.
    • Durumu null olarak ayarlayarak silmek için null atanabilir olması gerekir TState .
    • Örtük olarak tanımlanan silme işlemi null değer atanamayan TStateöğesini siler.
  • Durumunuz olarak bir POCO kullanırken (öğesinden TaskEntity<TState>türetilmez), delete örtük olarak tanımlanır. POCO'da bir yöntem Delete tanımlayarak silme işlemini geçersiz kılmak mümkündür. Ancak POCO yolunda durumu null olarak ayarlamanın bir yolu yoktur, bu nedenle örtük olarak tanımlanan silme işlemi tek doğru silme işlemidir.

Sınıf Gereksinimleri

Varlık sınıfları özel üst sınıflar, arabirimler veya öznitelikler gerektirmeyen POCO'lardır (düz eski CLR nesneleri). Ancak:

Ayrıca, bir işlem olarak çağrılması amaçlanan tüm yöntemlerin diğer gereksinimleri karşılaması gerekir:

  • Bir işlemin en fazla bir bağımsız değişkeni olmalıdır ve aşırı yüklemeler veya genel tür bağımsız değişkenleri olmamalıdır.
  • Bir arabirim kullanılarak bir düzenlemeden çağrılması gereken bir işlem veya Task<T>döndürmelidirTask.
  • Bağımsız değişkenler ve dönüş değerleri serileştirilebilir değerler veya nesneler olmalıdır.

İşlemler ne yapabilir?

Tüm varlık işlemleri varlık durumunu okuyup güncelleştirebilir ve durumdaki değişiklikler otomatik olarak depolamada kalıcı olur. Ayrıca, işlemler tüm Azure İşlevleri ortak genel sınırlar içinde dış G/Ç veya diğer hesaplamalar gerçekleştirebilir.

İşlemler bağlam tarafından Entity.Current sağlanan işlevlere de erişebilir:

  • EntityName: şu anda yürütülmekte olan varlığın adı.
  • EntityKey: şu anda yürütülmekte olan varlığın anahtarı.
  • EntityId: şu anda yürütülmekte olan varlığın kimliği (ad ve anahtar içerir).
  • SignalEntity: bir varlığa tek yönlü ileti gönderir.
  • CreateNewOrchestration: yeni bir düzenleme başlatır.
  • DeleteState: bu varlığın durumunu siler.

Örneğin, sayaç 100'e ulaştığında ve varlık kimliğini giriş bağımsız değişkeni olarak geçirdiğinde düzenleme başlatabilmesi için sayaç varlığını değiştirebiliriz:

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

Varlıklara doğrudan erişme

Sınıf tabanlı varlıklara, varlık ve işlemleri için açık dize adları kullanılarak doğrudan erişilebilir. Bu bölümde örnekler verilmiştir. Temel kavramlar (sinyaller ve çağrılar gibi) hakkında daha ayrıntılı bir açıklama için Access varlıklarındaki tartışmaya bakın.

Dekont

Mümkün olduğunda, daha fazla tür denetimi sağladığından varlıklara arabirimler aracılığıyla erişmeniz gerekir.

Örnek: istemci sinyalleri varlığı

Aşağıdaki Azure Http İşlevi, REST kurallarını kullanarak bir DELETE işlemi uygular. Anahtarı URL yolunda geçirilen sayaç varlığına bir silme sinyali gönderir.

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

Örnek: istemci varlık durumunu okur

Aşağıdaki Azure Http İşlevi, REST kurallarını kullanarak bir GET işlemi uygular. Anahtarı URL yolunda geçirilen sayaç varlığının geçerli durumunu okur.

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

Dekont

tarafından ReadEntityStateAsync döndürülen nesne yalnızca yerel bir kopyadır, yani zamanın önceki bir noktasından varlık durumunun anlık görüntüsüdür. Özellikle eski olabilir ve bu nesnenin değiştirilmesinin gerçek varlık üzerinde hiçbir etkisi yoktur.

Örnek: düzenleme önce sinyalleri sonra varlığı çağırır

Aşağıdaki düzenleme, bir sayaç varlığını artırması için sinyal gönderir ve ardından en son değerini okumak için aynı varlığı çağırır.

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

Örnek: istemci sinyalleri varlığı

Aşağıdaki Azure Http İşlevi, REST kurallarını kullanarak bir DELETE işlemi uygular. Anahtarı URL yolunda geçirilen sayaç varlığına bir silme sinyali gönderir.

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

Örnek: istemci varlık durumunu okur

Aşağıdaki Azure Http İşlevi, REST kurallarını kullanarak bir GET işlemi uygular. Anahtarı URL yolunda geçirilen sayaç varlığının geçerli durumunu okur.

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

Örnek: düzenleme önce sinyalleri sonra varlığı çağırır

Aşağıdaki düzenleme, bir sayaç varlığını artırması için sinyal gönderir ve ardından en son değerini okumak için aynı varlığı çağırır.

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

Arabirimler aracılığıyla varlıklara erişme

Arabirimler, oluşturulan ara sunucu nesneleri aracılığıyla varlıklara erişmek için kullanılabilir. Bu yaklaşım, bir işlemin adının ve bağımsız değişken türünün uygulananla eşleşmesini sağlar. Mümkün olduğunda varlıklara erişmek için arabirimleri kullanmanızı öneririz.

Örneğin, sayaç örneğini aşağıdaki gibi değiştirebiliriz:

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

public class Counter : ICounter
{
    ...
}

Varlık sınıfları ve varlık arabirimleri, Orleans tarafından popülerleştirilen taneciklere ve tane arabirimlerine benzer. Dayanıklı Varlıklar ile Orleans arasındaki benzerlikler ve farklar hakkında daha fazla bilgi için bkz . Sanal aktörlerle karşılaştırma.

Arabirimler, tür denetimi sağlamanın yanı sıra, uygulama içindeki endişelerin daha iyi ayrılması için kullanışlıdır. Örneğin, bir varlık birden çok arabirim uygulayabildiğinden, tek bir varlık birden çok rol sağlayabilir. Ayrıca, bir arabirim birden çok varlık tarafından uygulanabildiğinden, genel iletişim desenleri yeniden kullanılabilir kitaplıklar olarak uygulanabilir.

Örnek: istemci arabirim aracılığıyla varlığı sinyaller

İstemci kodu, uygulayan TEntityInterfacevarlıklara sinyal göndermek için kullanabilirSignalEntityAsync<TEntityInterface>. Örneğin:

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

Bu örnekte parametresi, proxy çağrısının ICounterDelete dahili olarak sinyale çevrildiği dinamik olarak oluşturulmuş bir örneğidir.

Dekont

API'ler SignalEntityAsync yalnızca tek yönlü işlemler için kullanılabilir. bir işlem döndürse Task<T>bile, parametrenin T değeri her zaman null olur veya defaultgerçek sonuç olmaz. Örneğin, hiçbir değer döndürülmedikçe işlemin sinyalini Get vermek mantıklı değildir. Bunun yerine, istemciler sayaç durumuna doğrudan erişmek için kullanabilir ReadStateAsync veya işlemi çağıran Get bir düzenleyici işlevi başlatabilir.

Örnek: Düzenleme önce sinyalleri sonra ara sunucu aracılığıyla varlığı çağırır

Bir düzenlemenin içinden bir varlığı çağırmak veya sinyal vermek için, CreateEntityProxy arabirim türüyle birlikte varlık için bir ara sunucu oluşturmak üzere kullanılabilir. Bu ara sunucu daha sonra çağrı veya sinyal işlemleri için kullanılabilir:

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

Örtük olarak, döndüren void tüm işlemler işaretlenir ve döndüren Task veya Task<T> çağrılan tüm işlemler. Bu varsayılan davranışı değiştirebilir ve yöntemi açıkça kullanarak SignalEntity<IInterfaceType> Görev döndürseler bile sinyal işlemleri yapılabilir.

Hedefi belirtmek için daha kısa seçenek

Arabirim kullanarak bir varlığı çağırırken veya sinyal gönderirken, ilk bağımsız değişkenin hedef varlığı belirtmesi gerekir. Hedef, varlık kimliği belirtilerek veya varlığı uygulayan tek bir sınıfın olduğu durumlarda yalnızca varlık anahtarı belirtilebilir:

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

Yalnızca varlık anahtarı belirtilirse ve çalışma zamanında benzersiz bir uygulama bulunamazsa, InvalidOperationException oluşturulur.

Varlık arabirimleri üzerindeki kısıtlamalar

Her zamanki gibi, tüm parametre ve dönüş türleri JSON serileştirilebilir olmalıdır. Aksi takdirde, serileştirme özel durumları çalışma zamanında oluşturulur.

Ayrıca bazı daha fazla kural uygularız:

  • Varlık arabirimleri, varlık sınıfıyla aynı derlemede tanımlanmalıdır.
  • Varlık arabirimleri yalnızca yöntemleri tanımlamalıdır.
  • Varlık arabirimleri genel parametreler içermemelidir.
  • Varlık arabirimi yöntemlerinin birden fazla parametresi olmamalıdır.
  • Varlık arabirimi yöntemleri , Taskveya Task<T>döndürmelidirvoid.

Bu kurallardan herhangi biri ihlal edilirse, arabirim , SignalEntityAsyncveya CreateEntityProxyiçin tür bağımsız değişkeni SignalEntityolarak kullanıldığında çalışma zamanında bir InvalidOperationException oluşturulur. Özel durum iletisinde hangi kuralın bozuk olduğu açıklanır.

Dekont

Döndürülen void arabirim yöntemleri yalnızca işaretlenebilir (tek yönlü), çağrılmaz (iki yönlü). Döndüren Task veya çağrılabilen veya Task<T> sinyal verilen arabirim yöntemleri. Çağrılırsa, işlemin sonucunu döndürür veya işlem tarafından oluşan özel durumları yeniden oluştururlar. Ancak sinyal gönderildiğinde işlemden gerçek sonucu veya özel durumu döndürmez, yalnızca varsayılan değeri döndürür.

Bu, şu anda .NET yalıtılmış çalışanda desteklenmiyor.

Varlık serileştirme

Bir varlığın durumu kalıcı olarak kalıcı olduğundan, varlık sınıfının serileştirilebilir olması gerekir. Dayanıklı İşlevler çalışma zamanı, serileştirme ve seri durumdan çıkarma işlemini denetlemek için ilkeleri ve öznitelikleri destekleyen bu amaçla Json.NET kitaplığını kullanır. En yaygın kullanılan C# veri türleri (diziler ve koleksiyon türleri dahil) zaten serileştirilebilir ve dayanıklı varlıkların durumunu tanımlamak için kolayca kullanılabilir.

Örneğin, Json.NET aşağıdaki sınıfı kolayca seri hale getirip seri durumdan çıkarabilirsiniz:

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

    ...
}

Serileştirme Öznitelikleri

Yukarıdaki örnekte, temel serileştirmeyi daha görünür hale getirmek için birkaç öznitelik eklemeyi seçtik:

  • Sınıfının serileştirilebilir olması gerektiğini anımsatmak ve yalnızca açıkça JSON özellikleri olarak işaretlenmiş üyeleri kalıcı hale getirmek için ile [JsonObject(MemberSerialization.OptIn)] sınıfına açıklama ekleriz.
  • Bir alanın kalıcı [JsonProperty("name")] varlık durumunun bir parçası olduğunu anımsatmak ve JSON gösteriminde kullanılacak özellik adını belirtmek için kalıcı olacak alanlara açıklama ekleriz.

Ancak, bu öznitelikler gerekli değildir; Json.NET ile çalıştıkları sürece diğer kurallar veya özniteliklere izin verilir. Örneğin, öznitelikleri kullanabilir veya hiç öznitelik kullanamaz [DataContract] :

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

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

Varsayılan olarak, sınıfın adı* JSON gösteriminin bir parçası olarak depolanmaz; yani varsayılan ayar olarak kullanırız TypeNameHandling.None . Bu varsayılan davranış veya JsonProperty öznitelikleri kullanılarak JsonObject geçersiz kılınabilir.

Sınıf tanımlarında değişiklik yapma

Depolanan JSON nesnesi artık yeni sınıf tanımıyla eşleşmediğinden, bir uygulama çalıştırıldıktan sonra bir sınıf tanımında değişiklik yaparken bazı özen gösterilmesi gerekir. Yine de, tarafından JsonConvert.PopulateObjectkullanılan seri durumdan çıkarma işlemini anladığı sürece veri biçimlerini değiştirmekle doğru başa çıkmak mümkündür.

Örneğin, değişikliklere ve bunların etkisine ilişkin bazı örnekler aşağıda verilmiştir:

  • Depolanan JSON'da bulunmayan yeni bir özellik eklendiğinde, varsayılan değerini varsayar.
  • Depolanan JSON'da bulunan bir özellik kaldırıldığında, önceki içerik kaybolur.
  • Bir özellik yeniden adlandırıldığında, efekt eskisini kaldırıp yenisini ekler gibi olur.
  • Bir özelliğin türü artık depolanan JSON'dan seri durumdan çıkarılamayacak şekilde değiştirildiğinde, bir özel durum oluşturulur.
  • Bir özelliğin türü değiştirildiğinde ancak depolanmış JSON'dan seri durumdan çıkarılabildiğinde, bunu yapar.

Json.NET davranışını özelleştirmek için birçok seçenek vardır. Örneğin, depolanan JSON sınıfında bulunmayan bir alan içeriyorsa özel durumu zorlamak için özniteliğini JsonObject(MissingMemberHandling = MissingMemberHandling.Error)belirtin. İsteğe bağlı biçimlerde depolanan JSON'ı okuyabilen seri durumdan çıkarma için özel kod yazmak da mümkündür.

Serileştirme varsayılan davranışı olarak Newtonsoft.Json System.Text.Jsondeğiştirildi. Daha fazla bilgi için buraya bakın.

Varlık oluşturma

Bazen varlık nesnelerinin nasıl oluşturulurken daha fazla denetim sahibi olmak istiyoruz. Şimdi varlık nesneleri oluştururken varsayılan davranışı değiştirmek için çeşitli seçenekleri açıklıyoruz.

İlk erişimde özel başlatma

Bazen, hiç erişilmemiş veya silinmiş bir varlığa bir işlem göndermeden önce bazı özel başlatmalar yapmamız gerekir. Bu davranışı belirtmek için, önce DispatchAsyncbir koşullu ekleyebilirsiniz:

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

Varlık sınıflarındaki bağlamalar

Normal işlevlerden farklı olarak, varlık sınıfı yöntemlerinin giriş ve çıkış bağlamalarına doğrudan erişimi yoktur. Bunun yerine, bağlama verilerinin giriş noktası işlev bildiriminde yakalanması ve ardından yöntemine DispatchAsync<T> geçirilmesi gerekir. öğesine geçirilen DispatchAsync<T> tüm nesneler, bağımsız değişken olarak varlık sınıfı oluşturucusunun otomatik olarak iletilir.

Aşağıdaki örnek, blob giriş bağlamasından bir CloudBlobContainer başvurunun sınıf tabanlı bir varlık için nasıl kullanılabilir hale getirilebileceğini gösterir.

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

Azure İşlevleri bağlamaları hakkında daha fazla bilgi için Azure İşlevleri Tetikleyiciler ve Bağlamalar belgelerine bakın.

Varlık sınıflarında bağımlılık ekleme

Varlık sınıfları Azure İşlevleri Bağımlılık Eklemeyi destekler. Aşağıdaki örnekte, bir IHttpClientFactory hizmetin sınıf tabanlı bir varlığa nasıl kaydedilecekleri gösterilmektedir.

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

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

Aşağıdaki kod parçacığı, eklenen hizmetin varlık sınıfınıza nasıl eklendiğini gösterir.

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

İlk erişimde özel başlatma

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

Varlık sınıflarındaki bağlamalar

Aşağıdaki örnekte, sınıf tabanlı bir varlıkta blob giriş bağlamasının nasıl kullanılacağı gösterilmektedir.

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

Azure İşlevleri bağlamaları hakkında daha fazla bilgi için Azure İşlevleri Tetikleyiciler ve Bağlamalar belgelerine bakın.

Varlık sınıflarında bağımlılık ekleme

Varlık sınıfları Azure İşlevleri Bağımlılık Eklemeyi destekler.

Aşağıda, varlık sınıfında daha sonra içeri aktarılacak dosyada bir HttpClientprogram.cs öğesinin nasıl yapılandırılacağı gösterilmektedir.

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

Eklenen hizmeti varlık sınıfınıza ekleme burada anlatılır.

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

Dekont

Serileştirmeyle ilgili sorunları önlemek için, eklenen değerleri serileştirmenin dışında tutmak için kullanılan alanları dışlamamaya özen gösterin.

Dekont

Normal .NET Azure İşlevleri oluşturucu ekleme kullanıldığından farklı olarak, sınıf tabanlı varlıklar için işlev giriş noktası yöntemi bildirilmelidirstatic. Statik olmayan bir işlev giriş noktası bildirilmesi, normal Azure İşlevleri nesne başlatıcısı ile Dayanıklı Varlıklar nesne başlatıcısı arasında çakışmalara neden olabilir.

İşlev tabanlı söz dizimi

Şimdiye kadar çoğu uygulama için daha uygun olmasını beklediğimiz için sınıf tabanlı söz dizimine odaklandık. Ancak, işlev tabanlı söz dizimi varlık durumu ve işlemleri için kendi soyutlamalarını tanımlamak veya yönetmek isteyen uygulamalar için uygun olabilir. Ayrıca, şu anda sınıf tabanlı söz dizimi tarafından desteklenmeyen genellik gerektiren kitaplıklar uygulanırken de uygun olabilir.

İşlev tabanlı söz dizimi ile Entity İşlevi, işlem gönderimini açıkça işler ve varlığın durumunu açıkça yönetir. Örneğin aşağıdaki kod, işlev tabanlı söz dizimi kullanılarak uygulanan Counter varlığını gösterir.

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

Varlık bağlam nesnesi

Varlığa özgü işlevlere türündeki IDurableEntityContextbir bağlam nesnesi aracılığıyla erişilebilir. Bu bağlam nesnesi, varlık işlevi için bir parametre olarak ve async-local özelliği Entity.Currentaracılığıyla kullanılabilir.

Aşağıdaki üyeler geçerli işlem hakkında bilgi sağlar ve bir dönüş değeri belirtmemize izin verir.

  • EntityName: şu anda yürütülmekte olan varlığın adı.
  • EntityKey: şu anda yürütülmekte olan varlığın anahtarı.
  • EntityId: şu anda yürütülmekte olan varlığın kimliği (ad ve anahtar içerir).
  • OperationName: geçerli işlemin adı.
  • GetInput<TInput>(): geçerli işlemin girişini alır.
  • Return(arg): düzenlemeye işlemi çağıran bir değer döndürür.

Aşağıdaki üyeler varlığın durumunu yönetir (oluşturma, okuma, güncelleştirme, silme).

  • HasState: varlığın mevcut olup olmadığı, yani bazı durumlara sahip olup olmadığı.
  • GetState<TState>(): varlığın geçerli durumunu alır. Henüz yoksa oluşturulur.
  • SetState(arg): varlığın durumunu oluşturur veya güncelleştirir.
  • DeleteState(): varsa varlığın durumunu siler.

tarafından GetState döndürülen durum bir nesneyse, uygulama kodu tarafından doğrudan değiştirilebilir. Sonunda yeniden aramaya SetState gerek yoktur (ancak zarar da yoktur). Birden çok kez çağrılırsa GetState<TState> , aynı tür kullanılmalıdır.

Son olarak, aşağıdaki üyeler diğer varlıklara sinyal vermek veya yeni düzenleme başlatmak için kullanılır:

  • SignalEntity(EntityId, operation, input): bir varlığa tek yönlü ileti gönderir.
  • CreateNewOrchestration(orchestratorFunctionName, input): yeni bir düzenleme başlatır.
[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;
    });
}

Sonraki adımlar