diff --git a/CHANGELOG.md b/CHANGELOG.md
index e7772486..8730a50c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## Unreleased
- Change the default large payload externalization threshold (`LargePayloadStorageOptions.ThresholdBytes`) from 900,000 bytes to 256 KiB (262,144 bytes)
+- **BREAKING**: `EntityInstanceId` now throws if its string form exceeds 100 characters (backend limit). Migration: shorten entity name/key; if entities already exist with overlong IDs, use the truncated 100-character value when addressing them.
## v1.25.0-preview.2
diff --git a/src/Abstractions/Entities/EntityInstanceId.cs b/src/Abstractions/Entities/EntityInstanceId.cs
index fceaaaee..0b7836d4 100644
--- a/src/Abstractions/Entities/EntityInstanceId.cs
+++ b/src/Abstractions/Entities/EntityInstanceId.cs
@@ -12,6 +12,8 @@ namespace Microsoft.DurableTask.Entities;
[JsonConverter(typeof(EntityInstanceId.JsonConverter))]
public readonly record struct EntityInstanceId
{
+ const int MaxInstanceIdLength = 100;
+
///
/// Initializes a new instance of the struct.
///
@@ -28,6 +30,12 @@ public EntityInstanceId(string name, string key)
Check.NotNull(key);
this.Name = name.ToLowerInvariant();
this.Key = key;
+
+ int length = this.ToString().Length;
+ if (length > MaxInstanceIdLength)
+ {
+ throw new ArgumentException($"entity instance ids may not exceed 100 characters in length, actual length {length}.");
+ }
}
///
diff --git a/test/Abstractions.Tests/Entities/EntityInstanceIdTests.cs b/test/Abstractions.Tests/Entities/EntityInstanceIdTests.cs
new file mode 100644
index 00000000..b5f4c24b
--- /dev/null
+++ b/test/Abstractions.Tests/Entities/EntityInstanceIdTests.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.DurableTask.Entities;
+
+namespace Microsoft.DurableTask.Abstractions.Tests.Entities;
+
+public class EntityInstanceIdTests
+{
+ [Fact]
+ public void TestValidEntityInstanceId()
+ {
+ var entityId = new EntityInstanceId("entity", "key1");
+ Assert.Equal("@entity@key1", entityId.ToString());
+ }
+
+ [Fact]
+ public void TestEntityNameLowercased()
+ {
+ var entityId = new EntityInstanceId("Entity", "key1");
+ Assert.Equal("entity", entityId.Name);
+ Assert.Equal("@entity@key1", entityId.ToString());
+ }
+
+ [Fact]
+ public void TestEntityNameWithAtSymbolThrows()
+ {
+ Assert.Throws(() => new EntityInstanceId("entity@name", "key1"));
+ }
+
+ [Fact]
+ public void TestEntityInstanceIdExceedsMaxLengthThrows()
+ {
+ string longName = new string('a', 50);
+ string longKey = new string('b', 51);
+ Assert.Throws(() => new EntityInstanceId(longName, longKey));
+ }
+
+ [Fact]
+ public void TestEntityInstanceIdEqualsMaxLength()
+ {
+ string longName = new string('a', 49);
+ string longKey = new string('b', 49);
+ var entityId = new EntityInstanceId(longName, longKey);
+ Assert.Equal($"@{longName}@{longKey}", entityId.ToString());
+ }
+
+ [Fact]
+ public void TestFromStringValid()
+ {
+ var entityId = EntityInstanceId.FromString("@entity@key1");
+ Assert.Equal("entity", entityId.Name);
+ Assert.Equal("key1", entityId.Key);
+ }
+
+ [Fact]
+ public void TestFromStringInvalidThrows()
+ {
+ Assert.Throws(() => EntityInstanceId.FromString("invalid"));
+ Assert.Throws(() => EntityInstanceId.FromString("@entitykey1"));
+ Assert.Throws(() => EntityInstanceId.FromString("@@key1"));
+ Assert.Throws(() => EntityInstanceId.FromString($"@entity@{new string('a', 100)}"));
+ }
+}