Skip to content

Commit 20ff770

Browse files
committed
Cosmos: Allow PK with just the partition key and using id as the partition key
Fixes #21396
1 parent 5dd73d5 commit 20ff770

File tree

3 files changed

+140
-21
lines changed

3 files changed

+140
-21
lines changed

src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ protected override object NextValue(EntityEntry entry)
4848
var partitionKey = entityType.GetPartitionKeyPropertyName();
4949
foreach (var property in primaryKey.Properties)
5050
{
51-
if (property.Name == partitionKey)
51+
if (property.Name == partitionKey
52+
&& primaryKey.Properties.Count > 1)
5253
{
5354
continue;
5455
}

test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs

+121-16
Original file line numberDiff line numberDiff line change
@@ -478,10 +478,13 @@ public async Task Can_read_with_find_with_resource_id_async()
478478
PartitionKey = pk1
479479
};
480480

481-
await using (var context = new PartitionKeyContext_WithResourceId(options))
481+
await using (var context = new PartitionKeyContextWithResourceId(options))
482482
{
483483
await context.Database.EnsureCreatedAsync();
484484

485+
Assert.Null(context.Model.FindEntityType(typeof(Customer_WithResourceId))
486+
.FindProperty(StoreKeyConvention.DefaultIdPropertyName));
487+
485488
context.Add(customer);
486489
context.Add(
487490
new Customer_WithResourceId
@@ -494,7 +497,7 @@ public async Task Can_read_with_find_with_resource_id_async()
494497
await context.SaveChangesAsync();
495498
}
496499

497-
await using (var context = new PartitionKeyContext_WithResourceId(options))
500+
await using (var context = new PartitionKeyContextWithResourceId(options))
498501
{
499502
var customerFromStore = await context.Set<Customer_WithResourceId>()
500503
.FindAsync(pk1, "42");
@@ -509,7 +512,7 @@ public async Task Can_read_with_find_with_resource_id_async()
509512
await context.SaveChangesAsync();
510513
}
511514

512-
await using (var context = new PartitionKeyContext_WithResourceId(options))
515+
await using (var context = new PartitionKeyContextWithResourceId(options))
513516
{
514517
var customerFromStore = await context.Set<Customer_WithResourceId>()
515518
.WithPartitionKey(partitionKey: pk1.ToString())
@@ -535,7 +538,7 @@ public void Can_read_with_find_with_resource_id()
535538
PartitionKey = pk1
536539
};
537540

538-
using (var context = new PartitionKeyContext_WithResourceId(options))
541+
using (var context = new PartitionKeyContextWithResourceId(options))
539542
{
540543
context.Database.EnsureCreated();
541544

@@ -551,7 +554,7 @@ public void Can_read_with_find_with_resource_id()
551554
context.SaveChanges();
552555
}
553556

554-
using (var context = new PartitionKeyContext_WithResourceId(options))
557+
using (var context = new PartitionKeyContextWithResourceId(options))
555558
{
556559
var customerFromStore = context.Set<Customer_WithResourceId>()
557560
.Find(pk1, "42");
@@ -566,7 +569,7 @@ public void Can_read_with_find_with_resource_id()
566569
context.SaveChanges();
567570
}
568571

569-
using (var context = new PartitionKeyContext_WithResourceId(options))
572+
using (var context = new PartitionKeyContextWithResourceId(options))
570573
{
571574
var customerFromStore = context.Set<Customer_WithResourceId>()
572575
.WithPartitionKey(partitionKey: pk1.ToString())
@@ -582,7 +585,7 @@ public void Can_read_with_find_with_resource_id()
582585
public void Find_with_empty_resource_id_throws()
583586
{
584587
var options = Fixture.CreateOptions();
585-
using (var context = new PartitionKeyContext_WithResourceId(options))
588+
using (var context = new PartitionKeyContextWithResourceId(options))
586589
{
587590
context.Database.EnsureCreated();
588591

@@ -802,7 +805,7 @@ public async Task Can_read_with_find_without_partition_key()
802805
Name = "Theon"
803806
};
804807

805-
await using (var context = new PartitionKeyContext_EntityWithNoPartitionKey(options))
808+
await using (var context = new PartitionKeyContextEntityWithNoPartitionKey(options))
806809
{
807810
await context.Database.EnsureCreatedAsync();
808811

@@ -811,7 +814,7 @@ public async Task Can_read_with_find_without_partition_key()
811814
await context.SaveChangesAsync();
812815
}
813816

814-
await using (var context = new PartitionKeyContext_EntityWithNoPartitionKey(options))
817+
await using (var context = new PartitionKeyContextEntityWithNoPartitionKey(options))
815818
{
816819
var customerFromStore = context.Set<Customer_NoPartitionKey>().Find(42);
817820

@@ -821,6 +824,71 @@ public async Task Can_read_with_find_without_partition_key()
821824
}
822825
}
823826

827+
[ConditionalFact]
828+
public async Task Can_read_with_find_with_PK_partition_key()
829+
{
830+
var options = Fixture.CreateOptions();
831+
832+
var customer = new Customer
833+
{
834+
Id = 42,
835+
Name = "Theon"
836+
};
837+
838+
await using (var context = new PartitionKeyContextPrimaryKey(options))
839+
{
840+
await context.Database.EnsureCreatedAsync();
841+
842+
context.Add(customer);
843+
844+
await context.SaveChangesAsync();
845+
}
846+
847+
await using (var context = new PartitionKeyContextPrimaryKey(options))
848+
{
849+
var customerFromStore = context.Set<Customer>().Find(42);
850+
851+
Assert.Equal(42, customerFromStore.Id);
852+
Assert.Equal("Theon", customerFromStore.Name);
853+
AssertSql(context, @"ReadItem(42, 42)");
854+
}
855+
}
856+
857+
[ConditionalFact]
858+
public async Task Can_read_with_find_with_PK_resource_id()
859+
{
860+
var options = Fixture.CreateOptions();
861+
862+
var customer = new Customer_WithResourceId
863+
{
864+
id = "42",
865+
Name = "Theon"
866+
};
867+
868+
await using (var context = new PartitionKeyContextWithPrimaryKeyResourceId(options))
869+
{
870+
await context.Database.EnsureCreatedAsync();
871+
872+
context.Add(customer);
873+
874+
await context.SaveChangesAsync();
875+
}
876+
877+
await using (var context = new PartitionKeyContextWithPrimaryKeyResourceId(options))
878+
{
879+
var customerFromStore = context.Set<Customer_WithResourceId>().Find("42");
880+
881+
Assert.Equal("42", customerFromStore.id);
882+
Assert.Equal("Theon", customerFromStore.Name);
883+
AssertSql(context, @"@__p_0='42'
884+
885+
SELECT c
886+
FROM root c
887+
WHERE ((c[""Discriminator""] = ""Customer_WithResourceId"") AND (c[""id""] = @__p_0))
888+
OFFSET 0 LIMIT 1");
889+
}
890+
}
891+
824892
private class PartitionKeyContext : DbContext
825893
{
826894
public PartitionKeyContext(DbContextOptions dbContextOptions)
@@ -840,9 +908,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
840908
}
841909
}
842910

843-
private class PartitionKeyContext_EntityWithNoPartitionKey : DbContext
911+
private class PartitionKeyContextEntityWithNoPartitionKey : DbContext
844912
{
845-
public PartitionKeyContext_EntityWithNoPartitionKey(DbContextOptions dbContextOptions)
913+
public PartitionKeyContextEntityWithNoPartitionKey(DbContextOptions dbContextOptions)
846914
: base(dbContextOptions)
847915
{
848916
}
@@ -870,7 +938,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
870938
cb.Property(StoreKeyConvention.DefaultIdPropertyName)
871939
.HasValueGenerator((p, e) => valueGeneratorFactory.Create(p));
872940

873-
cb.Property(c => c.Id);
874941
cb.Property(c => c.PartitionKey).HasConversion<string>();
875942

876943
cb.HasPartitionKey(c => c.PartitionKey);
@@ -895,7 +962,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
895962

896963
cb.Property(StoreKeyConvention.DefaultIdPropertyName).HasValueGenerator((Type)null);
897964

898-
cb.Property(c => c.Id);
899965
cb.Property(c => c.PartitionKey).HasConversion<string>();
900966

901967
cb.HasPartitionKey(c => c.PartitionKey);
@@ -911,22 +977,61 @@ public PartitionKeyContextNonPrimaryKey(DbContextOptions dbContextOptions)
911977
{
912978
}
913979

980+
protected override void OnModelCreating(ModelBuilder modelBuilder)
981+
{
982+
modelBuilder.Entity<Customer>(
983+
cb =>
984+
{
985+
cb.HasKey(c => new { c.Id });
986+
});
987+
}
988+
}
989+
990+
private class PartitionKeyContextPrimaryKey : DbContext
991+
{
992+
public PartitionKeyContextPrimaryKey(DbContextOptions dbContextOptions)
993+
: base(dbContextOptions)
994+
{
995+
}
996+
914997
protected override void OnModelCreating(ModelBuilder modelBuilder)
915998
{
916999
modelBuilder.Entity<Customer>(
9171000
cb =>
9181001
{
9191002
var valueGeneratorFactory = new CustomPartitionKeyIdValueGeneratorFactory();
9201003

921-
cb.Property(c => c.Id);
1004+
cb.HasNoDiscriminator();
1005+
cb.Property(c => c.Id).HasConversion<string>();
1006+
cb.HasPartitionKey(c => c.Id);
9221007
cb.HasKey(c => new { c.Id });
9231008
});
9241009
}
9251010
}
9261011

927-
private class PartitionKeyContext_WithResourceId : DbContext
1012+
private class PartitionKeyContextWithPrimaryKeyResourceId : DbContext
1013+
{
1014+
public PartitionKeyContextWithPrimaryKeyResourceId(DbContextOptions dbContextOptions)
1015+
: base(dbContextOptions)
1016+
{
1017+
}
1018+
1019+
protected override void OnModelCreating(ModelBuilder modelBuilder)
1020+
{
1021+
modelBuilder.Entity<Customer_WithResourceId>(
1022+
cb =>
1023+
{
1024+
cb.HasPartitionKey(c => c.PartitionKey);
1025+
cb.Property(c => c.PartitionKey).HasConversion<string>();
1026+
cb.Property(c => c.id).HasConversion<string>();
1027+
cb.HasKey(c => new { c.id });
1028+
});
1029+
}
1030+
}
1031+
1032+
private class PartitionKeyContextWithResourceId : DbContext
9281033
{
929-
public PartitionKeyContext_WithResourceId(DbContextOptions dbContextOptions)
1034+
public PartitionKeyContextWithResourceId(DbContextOptions dbContextOptions)
9301035
: base(dbContextOptions)
9311036
{
9321037
}

test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs

+17-4
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,22 @@ public virtual void Passes_on_valid_partition_keys()
9595
modelBuilder.Entity<Order>().ToContainer("Orders").HasPartitionKey(o => o.PartitionId)
9696
.Property(o => o.PartitionId).HasConversion<string>();
9797

98-
var model = modelBuilder.Model;
99-
Validate(model);
98+
Validate(modelBuilder.Model);
99+
}
100+
101+
[ConditionalFact]
102+
public virtual void Passes_PK_partition_key()
103+
{
104+
var modelBuilder = CreateConventionalModelBuilder();
105+
modelBuilder.Entity<Order>(b =>
106+
{
107+
b.HasKey(o => o.PartitionId);
108+
b.Ignore(o => o.Customer);
109+
b.Ignore(o => o.OrderDetails);
110+
b.Ignore(o => o.Products);
111+
});
112+
113+
Validate(modelBuilder.Model);
100114
}
101115

102116
[ConditionalFact]
@@ -115,8 +129,7 @@ public virtual void Detects_non_key_partition_key_property()
115129
b.Ignore(o => o.Products);
116130
});
117131

118-
var model = modelBuilder.Model;
119-
VerifyError(CosmosStrings.NoPartitionKeyKey(typeof(Order).Name, nameof(Order.PartitionId), "id"), model);
132+
VerifyError(CosmosStrings.NoPartitionKeyKey(typeof(Order).Name, nameof(Order.PartitionId), "id"), modelBuilder.Model);
120133
}
121134

122135
[ConditionalFact]

0 commit comments

Comments
 (0)