Error "The ID `1` has an invalid format" when querying HotChocolate - hotchocolate

Tried to make own project, looking to ChilliCream/graphql-workshop as example.
There is a part, where id parameter of a query marked with IDAttribute.
Description of ID type says following:
The ID scalar type represents a unique identifier, often used to
refetch an object or as key for a cache. The ID type appears in a JSON
response as a String; however, it is not intended to be
human-readable. When expected as an input type, any string (such as
"4") or integer (such as 4) input value will be accepted as an ID.
My C# query source looks like
[ExtendObjectType(Name = GraphqlQueryNames.Query)]
public class EmployeeQuery
{
public async Task<Employee> GetEmployeeByIdAsync(
[ID] int id,
[Service] IEmployeeRepository employeeRepository,
CancellationToken token)
{
return await employeeRepository.GetEmployeeByIdAsync(id, token);
}
}
And in playground:
# 1 passed as value of $id
query getEmployeeById($id: ID!) {
employeeById(id: $id) {
familyName
}
}
Whether value is a string or a number, server throws same error "The ID `1` has an invalid format".
If we remove the [ID] attribute from C# and use it as 'Int!' in GraphQL query, it works fine.
What's wrong with ID and why it exists in example (AttendeeQueries.cs)? HotChocolate 10.5.3

First, the ID scalar type is part of GraphQL standard, and defined as:
In GraphQL the ID scalar type represents a unique identifier, often
used to refetch an object or as the key for a cache. The ID type is
serialized in the same way as a String; however, defining it as an ID
signifies that it is not intended to be human‐readable.
Relay is a GraphQL client JavaScript framework for React applications. Apollo GraphQL is another alternative.
HotChocolate has a few helpers to enable "Relay-style GraphQL API". These helpers will convert the id-field to a base64-encoded hash, serialized as a string. This is a good thing because:
it helps with caching, all entities has a unique id
hide actual id from users (?)
Even though you enable "Relay support" in HotChocolate, you don't have to use Relay, you can still use any GraphQL client (Apollo client is my favourite)
Now, if you just want to use the GraphQL ID scalar type, you can try this as I suggested first:
First remove ID-attribute from query:
[ExtendObjectType(Name = GraphqlQueryNames.Query)]
public class EmployeeQuery
{
public async Task<Employee> GetEmployeeByIdAsync(
int id,
[Service] IEmployeeRepository employeeRepository,
CancellationToken token)
{
return await employeeRepository.GetEmployeeByIdAsync(id, token);
}
}
then specify ID scalar type like this:
public class EmployeeType : ObjectType<Employee>
{
protected override void Configure(IObjectTypeDescriptor<Employee> descriptor)
{
descriptor.Field(r => r.Id).Type<IdType>;
}
}
But if you want to enable "Relay support" in HotChocolate, follow Arsync's answer (I changed this to HotChocolate v11 which has slightly different syntax):
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddGraphQLServer().EnableRelaySupport();
}
}
Update for Hot Chocolate v12: It's now possible to enable global identification, but without other relay related stuff:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddGraphQLServer().AddGlobalObjectIdentification();
}
}
Then in your type definition:
public class EmployeeType : ObjectType<Employee>
{
protected override void Configure(IObjectTypeDescriptor<Employee> descriptor)
{
// Relay ID support. Retailer.Id will be a hash. the real id / int is available below when passed to DataLoader
descriptor
.ImplementsNode()
.IdField(c => c.Id)
.ResolveNode(((context, id) => context.DataLoader<EmployeeByIdDataLoader>().LoadAsync(id, context.RequestAborted)));
}
}
Or even simpler with v12:
public class EmployeeType : ObjectType<Employee>
{
protected override void Configure(IObjectTypeDescriptor<Employee> descriptor)
{
descriptor
.Field(f => f.Id).ID(nameof(Employee));
}
}
If you try to query for employees now you'll see that id is not an integer, but a hash. Something like "TGFuZ3VhZ2UKaTE=". This hash is generated by HotChocolate, see IdSerializer source. If you try to base64 decode this string:
$ echo "TGFuZ3VhZ2UKaTE=" | base64 -d
Employee
i1
The errormessage you received "The ID 1 has invalid format", is because it now expects a hashed string, and not an integer.
This query should work:
query getEmployeeById {
employeeById(id: "TGFuZ3VhZ2UKaTE=") {
familyName
}
}

Found that IDAttribute is for Relay (as it located in HotChocolate.Types.Relay namespace). So need enable and configure Relay support (source):
ISchema schema = SchemaBuilder.New()
.EnableRelaySupport()
...
.Create();
And in ObjectType:
public class MyObjectType
: ObjectType<MyObject>
{
protected override void Configure(IObjectTypeDescriptor<MyObject> descriptor)
{
descriptor.AsNode()
.IdField(t => t.Id)
.NodeResolver((ctx, id) =>
ctx.Service<IMyRepository>().GetMyObjectAsync(id));
...
}
}
Seems the example project graphql-workshop needs more in-place explanation of purpose for these things. Can be found here.

Related

iBatis unable to read property from Map when using isEqual

I'm seeing a very bizarre issue with iBatis when trying to read a property from a Java map using isEqual, but not with other iBatis operators. For example it is able to read the map properties fine when using isNotNull and iterate. The xml:
<isNotNull property="filterCriteria.account">
AND
id
<isEqual property="filterCriteria.account.meetsCriteria" compareValue="false">
NOT
</isEqual>
IN
(SELECT DISTINCT id
FROM account
WHERE some other criteria....
)
</isNotNull>
The 2 java classes we're using here:
public class SearchProfile {
private Map<String, SearchProfileCriteria> filterCriteria;
public SAOSearchProfile() {
filterCriteria = new HashMap<>();
}
public Map<String, SAOSearchProfileCriteria> getFilterCriteria() {
return filterCriteria;
}
public void setFilterCriteria(Map<String, SAOSearchProfileCriteriaBase> filterCriteria) {
this.filterCriteria = filterCriteria;
}
}
Above is the container object that is passed to iBatis for the querying, and below is the criteria object that will be the value of the map. In this example it is keyed with the String "account"
public class SearchProfileCriteria {
boolean meetsCriteria;
public String getCriteriaAsString() {
return StringUtils.getStringValueFromBoolean(meetsCriteria);
}
public boolean isMeetsCriteria() {
return meetsCriteria;
}
public void setMeetsCriteria(boolean meetsCriteria) {
this.meetsCriteria = meetsCriteria;
}
public String getSQLString(){
return meetsCriteria ? "" : "NOT";
}
}
And the exception:
Cause: com.ibatis.common.beans.ProbeException: There is no READABLE property named 'account' in class 'java.util.Map'; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException:
The getSQLString() method was my half baked attempt at a work around, the String gets escaped in the query and throws a syntax error.
When I remove the <isEqual> block the query executes find, which indicates it is able to read the "account" key when checking the to see if it is null. As I mentioned above, we're also able to use the map keys in <iterate> tags without issue. It seems <isEqual> and <isNotEqual> are the only tags causing issues. Does anyone have experience with this or know what may be going on?
Beware: Using isNotNull, isEqual, iterate is iBatis, they don't exist anymore in Mybatis, so referencing to Mybatis indifferently is confusing.
Reference documentation.
For your issue, how does it behave if replacing Map with a class (property will be known at compile time)?
Or try using <isPropertyAvailable>.
The work around could work with right syntax: $ instead of #: $filterCriteria.account.SQLString$ instead of #filterCriteria.account.SQLString#, then the value is just concatenated instead of bound as parameter.

Dapper Extension Get & Update returns errors

I tried to play with Dapper Extension & MS Access and succeeded up to certain extent. My code is listed below. All the functions works properly (Insert/Count/GetList/Delete) except Get & Update. I have summarised the code below. If anybody wants I can paste all the code here
My Product class
public class Products
{
public string ProductNumber { get; set; }
public string Description { get; set; }
}
And in my main class. I tried to get the product and update it as below. con.Get<Products> function returns an exception with "Sequence contains more than one element" message and con.Update<Products> returns an exception with "At least one Key column must be defined".
using (var con = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb"))
{
string ProductNumber = "12";
var product4 = con.Get<Products>(ProductNumber);
product4.ProductNumber = "Baz";
con.Update<Products>(product4);
Console.ReadLine();
}
Even though con.Get<Products> fails con.GetList<Products>(predicate) works perfectly. I did follow this link for setup
If DapperExtensions can't infer a key property called ID from your class, you'll need to explicitly specify one via a class mapper. Assuming the ProductNumber is the primary key in the database, the following example class mapper should set the ProductNumber to be the primary key for DapperExtensions.
using Dapper;
using DapperExtensions;
using DapperExtensions.Mapper;
public class ProductsMapper : ClassMapper<Products>
{
public ProductsMapper()
{
Map(x => x.ProductNumber).Key(KeyType.Assigned);
AutoMap();
}
}
This class can sit somewhere within the same assembly as the rest of your code. Dapper Extensions will automatically pick it up. If you have your classes and Dapper code in separate assemblies, you can point it to your mapper with the following line:
DapperExtensions.DapperExtensions.SetMappingAssemblies({ typeof(ProductsMapper).Assembly })

What is the design pattern name that used to create an object by passing an engine object

I have a class called DatabaseModel.
And an interface called DatabaseEngineInterface that have a methods such as:
insert
update
delete
select
So I can on running time determine which engine to use mysql or oracle which are a classes that implements the DatabaseEngineInterface
EngineDatabase engine = new MySQLEngine();
DatabaseModel db = new DatabaseModel(engine);
What is this design pattern called?
Specifically, this is the Constructor Injection pattern (described in my book), which is a special case of the Strategy pattern.
Isn't it an implementation of the strategy pattern? Wikipedia states that the strategy pattern is:
>a software design pattern that enables an algorithm's behavior to be selected at runtime
It also says that the strategy pattern:
defines a family of algorithms,
encapsulates each algorithm, and
makes the algorithms interchangeable within that family.
You are allowing the database which will be used (and so the behaviour) to be selected at run time. You have defined a family of algorithms (your interface), encapsulated each algorithm (by creating a class per provider) and they can be used interchangeably as the DatabaseModel depends only on the interface.
DaoFactory design pattern fits well for your implementation.
Interface
public interface DatabaseEngineInterface {
public void insert(User user);
public void update(User user);
public void delete(int userId);
}
Class which implements the above methods:
public class DatabaseModel implements DatabaseEngineInterface {
#Override
public void delete(int userId) {
// delete user from user table
}
#Override
public User[] findAll() {
// get a list of all users from user table
return null;
}
#Override
public User findByKey(int userId) {
// get a user information if we supply unique userid
return null;
}
#Override
public void insert(User user) {
// insert user into user table
}
#Override
public void update(User user) {
// update user information in user table
}
}
Factory
public class DatabaseModelDAOFactory {
public static UserDAO getUserDAO(String type) {
if (type.equalsIgnoreCase("mysql")) {
return new UserDAOMySQLImpl();
} else {
return new UserDAOORACLEImpl();
}
}
}
Client side code:
Then, in the client side instead of a hardcoded line like:
DatabaseModel userDAO=DatabaseModelDAOFactory.getUserDAODatabaseEngineInterface("jdbc");
You could have a properties file to be able to switch between DAOs dynamically, having retrieved that string from the properties file you can simply do:
DatabaseModel userDAO=DatabaseModelDAOFactory.getUserDaoDatabaseEngineInterface(myStringFromPropertiesFile);
myStringFromPropertiesFile would contain "mysql" or "oracle" according to the definition in your properties file.

Parameter must be an entity type exposed by the DomainService?

Trying to implement a domain service in a SL app and getting the following error:
Parameter 'spFolderCreate' of domain method 'CreateSharePointFolder' must be an entity type exposed by the DomainService.
[EnableClientAccess()]
public class FileUploadService : DomainService
{
public void CreateSharePointFolder(SharePointFolderCreate spFolderCreate)
{
SharePointFolder spf = new SharePointFolder();
spf.CreateFolder_ClientOM(spFolderCreate.listName, spFolderCreate.fileName);
}
[OperationContract]
void CreateSharePointFolder(SharePointFolderCreate spFolderCreate);
[DataContract]
public class SharePointFolderCreate
{
private string m_listName;
private string m_fileName;
[DataMember]
public string listName
{
get { return m_listName; }
set { m_listName = value; }
}
[DataMember]
public string fileName
{
get { return m_fileName; }
set { m_fileName = value; }
}
}
So am I missing something simple here to make this all work?
It may be that the framework is inferring the intended operation because you have the word "Create" prefixing the function name (CreateSharePointFolder). Details of this behaviour can be found here
Although that is all fine for DomainServices and EntityFramework, following the information in that article, it can be inferred that methods beginning "Delete" will be performing a delete of an entity, so must accept an entity as a parameter. The same is true for "Create" or "Insert" prefixed methods. Only "Get" or "Select" methods can take non-entity parameters, making it possible to pass a numeric id (for example) to a "Get" method.
Try changing your method name temporarily to "BlahSharePointFolder" to see if it is this convention of inferrance that's causing your problem.
Also, as there is no metadata defined for your SharePointFolderCreate DC, you might need to decorate the class (in addition to the [DataContract] attribute) with the [MetadataType] attribute. You will see how to implement this if you used the DomainServiceClass wizard and point to an EF model. There is a checkbox at the bottom for generating metadata. Somewhere in your solution.Web project you should find a domainservice.metadata.cs file. In this file, you will find examples of how to use the [MetadataType] attribute.
For the RIA WCF service to work correctly with your own methods, you need to ensure that all entities existing on the parameter list have at least one member with a [Key] attribute defined in their metadata class, and that the entity is returned somewhere on your DomainService in a "Get" method.
HTH
Lee

Manually assign value to a hibernate UUID

As we know, in hibernate, configure the generator of a id to "uuid" , then hibernate will auto generate a UUID value to the id field when saving a new object.If configuring the generator to "assigned", the id must be assigned a value before saving a object.
And I found that if configuring the generator to uuid and assigning the id a value manually, the hibernate will change the value to a new UUID one.
My question is, when the generator is configured as uuid, how to manually assign a value to it?
PS: I use spring HibernateDaoSupport to save.
org.springframework.orm.hibernate3.support.HibernateDaoSupport.save(Ojbect obj)
Thanks!
If you need it only in rare special cases, the simpliest way is to issue INSERT queries in native SQL instead of using save().
Alternatively, you can customize generator to achieve the desired behaviour:
public class FallbackUUIDHexGenerator extends UUIDHexGenerator {
private String entityName;
#Override
public void configure(Type type, Properties params, Dialect d)
throws MappingException {
entityName = params.getProperty(ENTITY_NAME);
super.configure(type, params, d);
}
#Override
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
Serializable id = session
.getEntityPersister(entityName, object)
.getIdentifier(object, session);
if (id == null)
return super.generate(session, object);
else
return id;
}
}
and configure Hibernate to use it by setting its fully qualified name as strategy.

Resources