chary
chary

Reputation: 57

How to cast an object to any type using object.GetType()

I have an object type variable where I want to cast it to its original data type. Data type could be anything (int, enum, class, etc)

(object.GetType())object is not working

Edited:

The reason I want to cast it again to original, because I'm building a cosmos db query with multiple parameters

I have a method QueryCosmos(Dictionary<string, object> parameters) where key is the field name in cosmos, and value is the value of the field that matches the condition (it could be any data type).

static string QueryCosmos(Dictionary<string, object> parameters)
{
   var sb = new StringBuilder($"SELECT * FROM c WHERE ");
   foreach (var p in parameters)
   {
      if (!(p.Value is string))
      {
         // this handles non-string data types
         var value = p.Value == null ? "null" : p.Value.ToString().ToLower();  
         sb.Append($"c.{p.Key} = {value} and ");
      }
      else
      {
         sb.Append($"c.{p.Key} = '{p.Value}' and ");
      }
   }
   return sb.ToString().Substring(0, sb.ToString().LastIndexOf(" and "));
}

My issue is with this line of code var value = p.Value == null ? "null" : p.Value.ToString().ToLower();

If I pass on an enum type, doesn't match the data in cosmos. Because in cosmos, this is stored as the numeric value of the enum. I would need it to convert to its numeric value. There are multiple possible enum types that can be passed. That's the reason why I wanted a dynamic way to covert to original data type

Hope this clears up the concern

Upvotes: 0

Views: 1356

Answers (2)

ChrisBD
ChrisBD

Reputation: 9209

I've not looked at your new edit, but hopefully my answer is still relevant.

If you wish to cast an instance to its known concrete class type then you could use the as operator.

Here's some example code:

using System.IO;
using System;
using System.Collections.Generic;

class Program
{
    public class BaseClass{
        public string Base="BaseClass";
    }
    public class Class1:BaseClass{
        public string Value="Class 1";
    }
    public class Class2:BaseClass{
        public string Value="Class 2";
        public string AnotherValue="Another value";
    }
    
    static void Main()
    {
        List<BaseClass> list=new List<BaseClass>(){new Class1(), new Class2()};
        
        foreach( var item in list)
        {
            Console.WriteLine($"Item.Base = {item.Base}");
            if(typeof(Class1).IsAssignableFrom( item.GetType() ))
            {
                Console.WriteLine($"Item.Value = {(item as Class1).Value}");
            }
            if(typeof(Class2).IsAssignableFrom( item.GetType() ))
            {
                Console.WriteLine($"Item.Value = {(item as Class2).Value}");
                Console.WriteLine($"Item.Value = {(item as Class2).AnotherValue}");
            }
        }
    }
}

This will give you the output of:

Item.Base = BaseClass
Item.Value = Class 1
Item.Base = BaseClass
Item.Value = Class 2
Item.Value = Another value

Upvotes: 1

Dai
Dai

Reputation: 155708

As you're targeting Azure CosmosDB, you don't need to write your own query-generator, the CosmosDB client library (Microsoft.Azure.Cosmos) has one built-in.

In your case, use QueryDefinition and WithParameter to build a strongly-typed query. The WithParameter method accepts values as object and it handles type-specific logic for you.

As you are using a dynamic-query my code below shows how you can safely build a query's WHERE clause from a dictionary by using named-parameters.

Never embed query parameter values directly in the query SQL itself because doing so opens yourself up to SQL injection which is a very bad thing.


Update for enum:

I'll admit that I'm unfamiliar with the Azure CosmosDB client library, though I did take a quick look at the documentation and poked around the library in ILSpy.

As for handling enum values: the QueryDefinition.WithParameter method should handle enum values correctly as it preserves the boxed enum values in its .Value property.

If it doesn't, then converting any enum to an Int32 can be done like so - albeit with boxing (while it is possible to cast any enum to Int64 without casting that requires runtime IL emit which is beyond the scope of this answer, but I have an implementation in my GitHub Gists if you're interested):

I've updated the code below to use this foreach loop which checks for Enum (which matches any and all enum types, regardless of their underlying-type, though this example will fail if you have any enum : Int64 enums with values outside Int32's range - but that's trivial to fix and so is left as an exercise for the reader).

    foreach( var kvp in parameters )
    {
        if( kvp.Value is Enum e )
        {
            Int32 enumAsInt32 = (Int32)Convert.ChangeType( e, TypeCode.Int32 );
            query = query.WithParameter( kvp.Key, enumAsInt32 );
        }
        else
        {
            query = query.WithParameter( kvp.Key, kvp.Value );
        }
    }

The rest of my original answer:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.Configuration;

using Newtonsoft.Json;

//


public static QueryDefinition BuildCosmosQuery( Dictionary<String,Object> parameters )
{
    String sqlText = "SELECT * FROM foobar AS f WHERE " + GetWhereClauses( "f", parameters.Keys );

    QueryDefinition query = new QueryDefinition( sqlText );
    foreach( var kvp in parameters )
    {
        if( kvp.Value is Enum e )
        {
            Int32 enumAsInt32 = (Int32)Convert.ChangeType( e, TypeCode.Int32 );
            query = query.WithParameter( kvp.Key, enumAsInt32 );
        }
        else
        {
            query = query.WithParameter( kvp.Key, kvp.Value );
        }
    }
    
    return query;
}

private static String GetWhereClauses( String tableAlias, IEnumerable<String> parameterNames )
{
    return parameterNames
        .Select( pn => "{0}.{1} = @{1}".FmtInv( tableAlias, pn ) )
        .StringJoin( separator: " AND " );
}

//

public static class Extensions
{
    public static String StringJoin( this IEnumerable<String> source, String separator )
    {
        return String.Join( values: source, separator: separator ); 
    }

    public static String FmtInv( this String format, params Object[] args )
    {
        return String.Format( CultureInfo.InvariantCulture, format, args: args );
    }
}

See the QueryWithSqlParameters method in this code sample to see how to use QueryDefinition.

Upvotes: 2

Related Questions