ChilliCream / ChilliCream/graphql-platform
ObjectType<T> classes which implements interface or abstract class are not respecting mongo conventions
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 5.8k
- Forks
- 810
- Avg merge
- 15h 39m
- Merged PRs (30d)
- 98
Description
Is there an existing issue for this?
- I have searched the existing issues
Product
Hot Chocolate
Describe the bug
Hi,
I'm trying to use HotChocolate with MongoDB and I'm using paging, filtering, sorting features. My query Type contains a list of base types which can be resolved with multiple types. I have a set of mongo conventions registered and one of them is CamelCaseElementNameConvention. I managed to overcome problems with deserializing mongo documents by forcing HC to project mongo discriminator _t (without that it is trying to create an instance of abstract class). But unfortunately the query which is generated for mongo has uppercase fields in projection (only for derived classes) and because of that those fields are not returned from DB (please see logs) .
Steps to reproduce
- I have following code:
//Domain classes
public class Person
{
public string Id { get; set; }
public int PersonProp1 { get; set; }
public IEnumerable<Animal> PetList { get; set; }
}
public abstract class Animal
{
public int AnimalProp1 { get; set; }
public string AnimalProp2 { get; set; }
}
public class Cat : Animal
{
public int CatProp1 { get; set; }
public string CatProp2 { get; set; }
}
public class Dog : Animal
{
public int DogProp1 { get; set; }
public string DogProp2 { get; set; }
}
//Program.cs
var builder = WebApplication.CreateBuilder(args);
var pack = new ConventionPack
{
new CamelCaseElementNameConvention(),
new StringIdStoredAsObjectIdConvention(),
new IgnoreIfNullConvention(true),
new IgnoreExtraElementsConvention(true)
};
ConventionRegistry.Register("custom", pack, t => true);
BsonClassMap.RegisterClassMap<Animal>(cm => {
cm.AutoMap();
cm.AddKnownType(typeof(Cat));
cm.AddKnownType(typeof(Dog));
});
BsonClassMap.RegisterClassMap<Cat>(cm => {
cm.AutoMap();
});
BsonClassMap.RegisterClassMap<Dog>(cm => {
cm.AutoMap();
});
var connectionString = builder.Configuration.GetConnectionString("MongoDb");
var url = new MongoUrl(connectionString);
var settings = MongoClientSettings.FromUrl(url);
var client = new MongoClient(settings);
var database = client.GetDatabase(url.DatabaseName);
builder.Services.AddSingleton(client);
builder.Services.AddSingleton(database);
builder.Services.AddSingleton(database.GetCollection<Person>("person"));
builder.Services.AddControllers();
builder.Services.AddGraphQLServer()
.AddQueryType<PersonLookup>()
.AddType<CatType>()
.AddType<DogType>()
.ModifyOptions(opt => opt.RemoveUnreachableTypes = true)
.AddMongoDbPagingProviders()
.AddMongoDbProjections()
.AddMongoDbFiltering()
.AddMongoDbSorting()
.InitializeOnStartup();
var app = builder.Build();
app.MapGraphQL("/graphql");
app.Run();
//Schema definition
public class PersonLookup
{
private const int _maxPageSize = 1000;
[UseOffsetPaging(IncludeTotalCount = true, MaxPageSize = _maxPageSize)]
[UseProjection]
[UseFiltering]
[UseSorting]
public async Task<IExecutable<Person>> UserProfiles([Service] IMongoCollection<Person> collection)
{
return collection.AsExecutable();
}
}
public class AnimalType : InterfaceType<Animal>
{
protected override void Configure(IInterfaceTypeDescriptor<Animal> descriptor)
{
descriptor.Name(nameof(Animal));
}
}
public class CatType : ObjectType<Cat>
{
protected override void Configure(IObjectTypeDescriptor<Cat> descriptor)
{
descriptor.Name(nameof(Cat));
descriptor.Implements<AnimalType>();
descriptor.Field("_t").Type<StringType>().IsProjected().Resolve((context, ct) =>
{
//code won't run without resolver even if it is not used
return nameof(Cat);
});
}
}
public class DogType : ObjectType<Dog>
{
protected override void Configure(IObjectTypeDescriptor<Dog> descriptor)
{
descriptor.Name(nameof(Dog));
descriptor.Implements<AnimalType>();
descriptor.Field("_t").Type<StringType>().IsProjected().Resolve((context, ct) =>
{
return nameof(Dog);
});
}
}
- My sample data in DB looks like this - please note all the fields are in camel case
{
"_id" : ObjectId("6495c776a56246502b2515f2"),
"personProp1" : 6,
"petList" : [
{
"_t" : "Dog",
"animalProp1" : 5,
"animalProp2" : "Animal Prop 8",
"dogProp1" : 1,
"dogProp2" : "Dog Prop 4"
},
{
"_t" : "Dog",
"animalProp1" : 5,
"animalProp2" : "Animal Prop 8",
"dogProp1" : 1,
"dogProp2" : "Dog Prop 5"
},
{
"_t" : "Cat",
"animalProp1" : 6,
"animalProp2" : "Animal Prop 5",
"catProp1" : 3,
"catProp2" : "Cat Prop 1"
}
]
}
- Query which use to get data
query MyQuery {
userProfiles(where: {id: {eq: "6495c776a56246502b2515f2"}}) {
items {
petList {
... on Cat {
catProp2
animalProp2
catProp1
animalProp1
}
... on Dog {
dogProp2
animalProp2
dogProp1
animalProp1
}
animalProp2
animalProp1
}
id
personProp1
}
}
}
Relevant log output
Requested document:
{
"find" : "person",
"filter" : {
"_id" : {
"$eq" : ObjectId("6495c776a56246502b2515f2")
}
},
"projection" : {
"personProp1" : 1,
"_id" : 1,
"petList.DogProp1" : 1, <-- should be: petList.dogProp1
"petList.DogProp2" : 1, <-- should be: petList.dogProp2
"petList._t" : 1,
"petList.animalProp1" : 1,
"petList.CatProp1" : 1, <-- should be: petList.catProp1
"petList.animalProp2" : 1,
"petList.CatProp2" : 1 <-- should be: petList.catProp2
},
"limit" : 11,
"$db" : "testHC",
"lsid" : {
"id" : CSUUID("6c719ba1-9ca9-412b-9b03-f8a80d50fc66")
},
"$clusterTime" : {
"clusterTime" : Timestamp(1687614162, 1),
"signature" : {
"hash" : new BinData(0, "AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
Returned document:
{
"cursor" : {
"firstBatch" : [{
"_id" : ObjectId("6495c776a56246502b2515f2"),
"personProp1" : 6,
"petList" : [{
"_t" : "Dog",
"animalProp1" : 5,
"animalProp2" : "Animal Prop 8"
}, {
"_t" : "Dog",
"animalProp1" : 5,
"animalProp2" : "Animal Prop 8"
}, {
"_t" : "Cat",
"animalProp1" : 6,
"animalProp2" : "Animal Prop 5"
}]
}],
"id" : NumberLong(0),
"ns" : "testHC.person"
},
"ok" : 1.0,
"$clusterTime" : {
"clusterTime" : Timestamp(1687614162, 1),
"signature" : {
"hash" : new BinData(0, "AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1687614162, 1)
}
Additional Context?
No response
Version
13.2.1
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No source file or test is named. Start by reproducing the sample with AddMongoDbProjections and inspect the generated MongoDB projection for ObjectType implementations of the Animal interface. Done means derived Cat and Dog fields use the registered camelCase element names, while the existing discriminator and base fields still work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, graphql, mongodb
- Domain
- api, backend, database
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100