Dynamically pass Type to Method<T> - linq

I've a method , that retrieves to me some data according to some type I passed in parameter, like this :
protected void FillList<TEntity>()
{
doWorkForTEntity();
}
I Need to dynamically call this method :
Type[] entities = System.Reflection.Assembly.GetAssembly(typeof(User)).GetTypes();
Type currentEntity = (from entity in entities
where entity.Name.Equals(this.targetEntity)
select entity).FirstOrDefault();
FillList<currentEntity>();
I got this error :
The type or namespace name 'currentEntity' could not be found (are you missing a using directive or an assembly reference?)
I've tried an intermediate object type, no success
Any Idea please ?

Since there is no information about entity type in compile time, you need to construct and call appropriate method by reflection:
Type[] entities = System.Reflection.Assembly.GetAssembly(typeof(User)).GetTypes();
Type currentEntity = (from entity in entities
where entity.Name.Equals(this.targetEntity)
select entity).FirstOrDefault();
var method = this.GetType().GetMethod("FillList", BindingFlags.Instance | BindingFlags.NonPublic)
.MakeGenericMethod(currentEntity);
method.Invoke(this, new object[0]);

You need to do that with reflection as well, so it won't fail in compile time (compiler checks):
Generic class:
Type[] entities = System.Reflection.Assembly.GetAssembly(typeof(User)).GetTypes();
Type currentEntity = (from entity in entities
where entity.Name.Equals(this.targetEntity)
select entity).FirstOrDefault();
Type fillListType= typeof(FillList<>);
Type constructedGenericClass = fillListType.MakeGenericType(currentEntity);
object myList = Activator.CreateInstance(constructedGenericClass );
Generic Method:
Type[] entities = System.Reflection.Assembly.GetAssembly(typeof(User)).GetTypes();
Type currentEntity = (from entity in entities
where entity.Name.Equals(this.targetEntity)
select entity).FirstOrDefault();
MethodInfo methodinfo = this.GetType().GetMethod("FillList");
MethodInfo genericMethod = method.MakeGenericMethod(currentEntity);
genericMethod.Invoke(this, null);

Type parameters must be specified at compile time and can´t be assigned at runtime like in your example. You get the error message because there´s no Type called currentEntiry since it´s just a variable.

Change your method to take an instance of the Type TEntity:
protected void FillList<TEntity>(TEntity instance)
{
doWorkForTEntity();
}
Create a dynamic instance from the Type name and then call the modified method:
dynamic instance = Activator.CreateInstance(this.targetEntity);
FillList(instance);
The dynamic type is basically doing what the other answers have shown you - but IMHO this code is neater and clearer in its intent.

Related

How to convert ODataEnumValue into its CLR enum type?

While traversing the expression tree from a FilterQueryOption, I have an instance of a ODataEnumValue. I'm wondering how to convert that into the corresponding CLR enum type value in generic way (i.e. without having to look-up the actual CLR type myself).
Maybe you can refer to ODataEnumDeserializer's ReadInline method, which convert ODataEnumvalue to CLR enum type if it is in EdmModel.
The enum type can be retrieved by getting the ClrTypeAnnotation of the TypeReference as follows. First get the model from the FilterQueryOption instance:
IEdmModel _model = filterQueryOption.Context.Model;
Then, later when parsing its FilterClause, for example for a ConstantNode:
private object GetClrValue(ConstantNode constantNode)
{
ODataEnumValue enumValue;
...
else if ((enumValue = constantNode.Value as ODataEnumValue) != null)
{
var annotation = _model.GetAnnotationValue<ClrTypeAnnotation>(constantNode.TypeReference.Definition);
Type enumType = annotation.ClrType;
parameterValue = Enum.Parse(enumType, enumValue.Value);
}
...
}

MapODataRoute and ODataQueryOptions

Im building a WebAPI OData solution that handles untyped entity objects, as described in this excellent post. Like that post, I define my EdmModel upfront, and use the MapODataRoute method and pass in the model to use:
config.Routes.MapODataRoute("odata", "odata", ModelBuilder.GetEdmModel());
However, this does not seem to work with ODataQueryOptions parameter in my methods:
Get(ODataQueryOptions query)
{
}
It gives the following error: The given model does not contain the type 'System.Web.Http.OData.IEdmEntityObject'. Parameter name: elementClrType
Is there any way to get ODataQueryOptions to work with MapODataRoute?
You should build the ODataQueryOptions manually in your controller action in untyped mode. Sample code follows,
ODataPath path = Request.GetODataPath();
IEdmType edmType = path.EdmType;
IEdmType elementType = edmType.TypeKind == EdmTypeKind.Collection
? (edmType as IEdmCollectionType).ElementType.Definition
: edmType;
// build the typeless query options using the element type.
ODataQueryContext queryContext = new ODataQueryContext(Request.GetEdmModel(), elementType);
ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, Request);
Ive managed to do it as follows:
ODataPath path = Request.GetODataPath();
IEdmType edmType = path.EdmType;
private ODataQueryOptions GetODataQueryOptions(IEdmType edmType)
{
IEdmModel model = Models.ModelBuilder.GetEdmModel();
ODataQueryContext queryContext = new ODataQueryContext(model, edmType);
ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, this.Request);
return queryOptions;
}
This works for most query options, but crashes with $select and $expand: The type 'Collection([Org.Microsoft.Product Nullable=False])' is not an entity type. Only entity types support $select and $expand. Is there a way to avoid this exception gracefully when a client tries to filter with $select and $expand, or should i just write something like
if (Request.RequestUri.Query.Contains("select")) { return errormessage }
Also, and more importantly, how would one apply these query options to an EdmEntityObjectCollection which gets returned in the first method?
queryOptions.ApplyTo(collectionProduct.AsQueryable()); // wont work...
(perhaps it is better practice to dynamically build your collection in regards to the query options anyway)

Cannot map raw SQL query to DataRow

I am trying to get IEnumerable from linq query below. What am I doing wrong?
IEnumerable<DataRow> results =
context.Database.SqlQuery<DataRow>("SELECT * FROM Customer").AsEnumerable();
DataRow class does not have default (parameterless) constructor, so you can't use it as query parameter type. There is no generic constraints on type parameter, and nothing mentioned on MSDN(!), but column map factory will throw exception if parameter type does not have default constructor:
The result type 'System.Data.DataRow' may not be abstract and must
include a default constructor.
Here is a code which throws this exception:
internal static CollectionColumnMap CreateColumnMapFromReaderAndClrType(
DbDataReader reader, Type type, MetadataWorkspace workspace)
{
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
ConstructorInfo constructor = type.GetConstructor(flags, (Binder) null, Type.EmptyTypes, (ParameterModifier[]) null);
if (type.IsAbstract || (ConstructorInfo) null == constructor && !type.IsValueType)
throw EntityUtil.InvalidOperation(InvalidTypeForStoreQuery((object) type));
// ...
}
BTW Mapping to DataRow makes no sense, even if it would have default public constructor. Because it is not simple primitive type and it does not have properties which match the names of columns returned from the query (yes, mapping uses properties only).
Correct usage of Linq will be
IEnumerable<Customer> results = context.Customers;
That will generate SELECT * FROM Customer query, and map query results to customer entities. If you really want to use raw SQL:
IEnumerable<Customer> results =
context.Database.SqlQuery<Customer>("SELECT * FROM Customers");
I think we were trying to solve the same problem (Google led me here, anyway). I am executing a raw SQL command through SqlQuery<TElement>(string sql, params object[] parameters and wanted to assert individual properties of the results returned from the query in a unit test.
I called the method:
var result = (db.SqlQuery<Customer>("select * from customers").First();
and verified the data it returned:
Assert.AreEqual("John", result.FirstName);
I defined a private class Customer inside my test class (unfortunately, I'm not using Entity Framework):
private class Customer
{
public string FirstName { get; set; }
}
The properties of Customer must match the column names returned in the SQL query, and they must be properties (not just variables of the Customer class. You don't have to create properties for all of the columns returned from the query.

Set value in model when dynamically creating object of model

I have a bunch of models that may or may not have a property "CommonProperty". I want to set that property when I am creating a new object of a selected model. What I have so far, which works is:
ModuleItem model = db.ModuleItems.Find(ModuleItemID);
object o = GetModuleType(model.ControllerName);
private object GetModuleType(string ModelName)
{
string projectName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
Type classtype = Type.GetType(string.Format("{0}.Models.{1}", projectName, ModelName));
PropertyInfo[] properties = classtype.GetProperties();
var classObject = classtype.GetConstructor(new Type[] { }).Invoke(null);
return classObject;
}
What I want to achieve is to set CommonProperty:
o.CommonProperty = DistinctValue;
I tried creating a class that all of my models inherit from with a virtual method and each model then has an override method. Because its not static I can't call it directly and if I create a new ModelBase then when calling the method it doesn't get overriden by the type that object "o" is. I looked at creating an interface but 1 I don't even know how these work, and 2 (probably because of 1) I am not even able to create a way of doing this without build errors.
When stepping through the code I can see all the properties of "o" using the quickwatch or intellisense or whatever it's called. Clearly it is being recognised as the correct type. I can't seem to be able to call the method though because it's not a recognised (or set) type during build only during runtime and therefore I can't build the solution.
What I would do is create a base model (eg: BaseModel) for viewmodels that have the property CommonProperty then check whether the model is of type BaseModel then set the property
if (obj is BaseModel)
{
obj.CommonProperty = DistinctValue;
}
As it turns out I was way overthinking this. Typically the answer is ridiculously simple. No base model necessary, no overrides, no virtuals, no instances, dump it in a try catch and just set the value of the context object.
object o = GetModuleType(model.ControllerName);
db.Entry(o).State = EntityState.Added;
try
{
db.Entry(o).CurrentValues["CommonProperty"] = DistinctValue;
}
catch (Exception err) { }
I did have to make sure I set the state BEFORE setting a current value otherwise it wasn't in the context (or something like that).

Reflection + Linq + DbSet

I use EF code-first 4.1. in my application. Now I want to get entities through WCF services using generic types.
I'm trying to reflect generic type and invoke the method ToList of DbSet Object.
Here is my code:
public string GetAllEntries(string objectType)
{
try
{
var mdc =
Globals.DbConnection.Create(#"some_db_connection", true);
// Getting assembly for types
var asob = Assembly.GetAssembly(typeof(CrmObject));
// getting requested object type from assembly
var genericType = asob.GetType(objectType, true, true);
if (genericType.BaseType == typeof(CrmObject))
{
// Getting Set<T> method
var method = mdc.GetType().GetMember("Set").Cast<MethodInfo>().Where(x => x.IsGenericMethodDefinition).FirstOrDefault();
// Making Set<SomeRealCrmObject>() method
var genericMethod = method.MakeGenericMethod(genericType);
// invoking Setmethod into invokeSet
var invokeSet = genericMethod.Invoke(mdc, null);
// invoking ToList method from Set<> invokeSet
var invokeToList = invokeSet.GetType().GetMember("ToList").Cast<MethodInfo>().FirstOrDefault();
//this return not referenced object as result
return invokeToList.ToString();
}
return null;
}
catch (Exception ex)
{
return ex.Message + Environment.NewLine + ex.StackTrace;
}
}
In fact then I write the code like return mdc.Set<SomeRealCrmObject>().ToList() - works fine for me! But then I use the generic types I cannot find the method ToList in object DbSet<SomeRealCrmObject>().
Eranga is correct, but this is an easier usage:
dynamic invokeSet = genericMethod.Invoke(mdc, null);
var list = Enumerable.ToList(invokeSet);
C# 4's dynamic takes care of the cumbersome generic reflection for you.
ToLIst() is not a member of DbSet/ObjectSet but is an extension method.
You can try this instead
var method = typeof(Enumerable).GetMethod("ToList");
var generic = method.MakeGenericMethod(genericType);
generic.Invoke(invokeSet, null);
I had a similar situation where I needed a way to dynamically load values for dropdown lists used for search criteria on a page allowing users to run adhoc queries against a given table. I wanted to do this dynamically so there would be no code change on the front or backend when new fields were added. So using a dynamic type and some basic reflection, it solved my problem.
What I needed to do was invoke a DbSet property on a DbContext based on the generic type for the DbSet. So for instance the type might be called County and the DbSet property is called Counties. The load method below would load objects of type T (the County) and return an array of T objects (list of County objects) by invoking the DbSet property called Counties. EntityList is just a decorator object that takes a list of lookup items and adds additional properties needed for the grid on the front-end.
public T[] Load<T>() where T : class
{
var dbProperty = typeof(Data.BmpDB).GetProperties().FirstOrDefault(
x => x.GetMethod.ReturnType.GenericTypeArguments[0].FullName == typeof(T).FullName);
if (dbProperty == null)
return null;
dynamic data = dbProperty.GetMethod.Invoke(BmpDb, null);
var list = Enumerable.ToList(data) as List<T>;
var entityList = new Data.EntityList<T>(list);
return entityList.Results;
}

Resources