Reflection with Entity Framework - c#

I have table name as string in my hand. Help to access the entity using reflection. My query is,
TestEntities entity = new TestEntities();
var countryList = entity.GetType().GetProperty("Countries").GetValue(entity, null) as
typeof(ObjectSet).MakeGenericType(Type.GetType("CRUDService.Country"));
I queried country table using reflection and try to converting result as Country type. But it's not working. Kindly help me to convert specific type at runtime.

Expression var value = obj astypeof(YourType) is invalid; as keyword cannot be followed by operator typeof; you actually need to reference the type without the help of typeof
Your only option is to treat it as a non generic ObjectQuery since you don't know the type at code time:
TestEntities entity = new TestEntities();
var countryList =
entity.GetType().GetProperty("Countries").GetValue(entity, null) as ObjectQuery;
Be sure this will return an ObjectSet<Country>. The problem you face now is that you loose the generic benefits of ObjectSet<T>. You can solve this later by creating a generic extension method that can be used once your code knows about the actual ElementType of your IQueryable.
public static ObjectSet<T> Cast<T>(this ObjectQuery objectSet)
{
if(!(objectSet is ObjectSet<T>))
throw new Exception("Invalid instance passed or entity type specified");
return objectSet as ObjectSet&ltT>;
}
Use var countries = countryList.Cast<Country>(); to get your generic ObjectSet<>

Related

Query an Entity Framework entity without knowing the object type (class/table) in advance

I'm wondering, if it's even possible in the first place, how I would go about querying the database (using EF) using an ID and a table name.
For example, writing a function as:
QueryDynamicData(string tableName, long entityID){return GetItem(tableName, entityID);}
And could be called like:
var entry = QueryDynamicData("Person", 143);
To clarify, this is for a MVC ASP.Net project using Entity Frameworks.
Thanks in advance!
EDIT:
Following the example from #JPVenson, I came up with the following code. Note that it returns a list of Dictionaries, even though Id is unique, since I'm thinking ahead to when we may want to get all results for a dynamic table instead of just by Id. (This is only proof of concept level)
public List<Dictionary<string, object>> QueryDynamicData(string table, int entityID)
{
try
{
//Get the table desired based on the table name passed
PropertyInfo dbSetInfo = DBContext.GetType().GetProperties().FirstOrDefault(p => p.Name.ToLower().Equals(table.ToLower()));
//Return all results from the table into IQueryable set where Id = entityID passed
IQueryable anyDbSet = ((IQueryable)dbSetInfo.GetValue(DBContext)).Where("Id=" + entityID);
List<Dictionary<string,object>> listObjects = new List<Dictionary<String, Object>>();
//Iterate through results
foreach (Object entity in anyDbSet)
{
//Create dictionary of Field Name, Field Value from results
Dictionary<string, object> listDBValues = entity.GetType().GetProperties().ToDictionary(propertyInfo => propertyInfo.Name, propertyInfo => propertyInfo.GetValue(entity));
//Add dictionary to list of dictionaries - useful for returning list of found results instead of just one
listObjects.Add(listDBValues);
}
//Return list of dictionaries
return listObjects;
}
catch (Exception e) { }
return null;
}
Yes you can. There is a blog from ScottGu
https://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library
(MS Version of DynamicLinq https://github.com/kahanu/System.Linq.Dynamic/wiki)
that contains the wiki for a lib called DynamicLinq. I'm using it currently in a Project and it will fit your approach.
You still have to wrap it and use some Reflection to build a proper IQueryable but it does a lot of work for you
Edit Code Example
With some reflection you can access your dbSet like this (Untested Pseudocode!):
public object[] QueryDynamicData(string table, int entityId) {
//Your DbContext that contains all of your
var dbContext = new FooBaa()
//Get the DbSet in your DbContext that matches the "Table" name.
//You are searching for the generic parameter of the DbSet
var dbSetInfo = dbContext.GetType().GetProperties().FirstOrDefault(e => e.GetGenericArguments().Any(f => f.Name.Equals(table));
//Now get the DbSet from the DbContext and cast it to an IQueryabe
IQueryable anyDbSet = (IQueryable)dbSetInfo.GetValue(dbContext);
//Use Dynamic Linq to create a Query that selects an ID
//warning SQL-Injection possible checkout the 2nd argument of type IDictionary
return anyDbSet.Where("Id=" + entityId).ToArray();
}
I had a few minutes to work on this between meetings, and came up with this (add it to your DbContext class:
public dynamic FindEntity(string table, long Id)
{
PropertyInfo prop = this.GetType().GetProperty(table, BindingFlags.Instance | BindingFlags.Public);
dynamic dbSet = prop.GetValue(this, null);
return dbSet.Find(Id);
}
It uses some reflection to find the property on the DbContext with the name of the table, and grabs a reference to it. Then it calls Find on that DbSet<T> to find the object with the specified primary key. Since you aren't sending any actual Type information along, everything has to be dynamically typed.
You can call it like this:
using (var db = new MyContext())
{
var e = db.FindEntity("Animals", 1);
}
I can't vouch for how useful it will be do you, but it does return (dynamically typed) data in my test setup.

Dynamically Instantiate Model object in Entity Framework DB first by passing type as parameter

Have a requirement to create instance of Entity Framework generated Model class dynamically by passing table name as parameter (Model generated in DB first approach and using EF 6.0)
like,
// Input Param
string tableName
// Context always same
DBContext dbContext= new DBContext();
//Need to create object query dynamically by passing
//table name from front end as below
IQueryable<"tableName"> query = dbContext."tableName ";
Need to pass 100+ tables as input param and structure of all table is same.
Please help.
You can use a Dictionary. Maybe you want something like this:
// Input Param
string tableName = "TblStudents";
Dictionary<string, Type> myDictionary = new Dictionary<string, Type>()
{
{ "TblStudents", typeof(TblStudent) },
{ "TblTeachers", typeof(TblTeacher) }
};
// Context always same
DBContext dbContext = new DBContext();
DbSet dbSet = dbContext.Set(myDictionary[tableName]);
But you can not use none of the LINQ extension methods because they are defined on the generic type IQueryable<T> but the non-generic overload of DbContext.Set, returns a non-generic DbSet. Also this class implements non generic IQueryable.
You have two option to use LINQ methods here:
Add System.Linq.Dynamic to your project (to install System.Linq.Dynamic, run the following command in the Package Manager Console ):
Install-Package System.Linq.Dynamic
And then you can:
var dbSet = dbContext.Set(myDictionary[tableName]).Where("Id = #a", 12);
Use the Find method:
//But this returns a single instance of your type
var dbSet = dbContext.Set(myDictionary[tableName]).Find(12);

Passing A DbSet<T> created at runtime via reflection to Queryable

I'm trying to execute a dynamic linq query where my DbSet Type is created at runtime via reflection an I'm getting the error:
"The best overloaded method match for
'System.Linq.Queryable.Where(System.Linq.IQueryable,
System.Linq.Expressions.Expression>)'
has some invalid arguments"
Here's my code
MyDataContext db = new MyDataContext ();
var dbType = db.GetType();
var dbSet = dbType.GetProperty("MyType").GetValue(db,null);
dbSet.GetType().InvokeMember("Local", BindingFlags.GetProperty, null, dbSet , null)
//just to show that it equal
dbSet.Equals(db.MyType); //returns true;
//here i create a dynamic expression tree
dynamic l = Expression.Lambda(delagateType, greater, param);
//here it fails when i pass in my dbSet var but not when i pass db.MyType
dynamic q = ((IEnumerable<object>)Queryable.Where(dbSet , l)).ToList();
The problem is that your dynamic call contains two parameters, the first being static and the second being dynamic. In such case the compiler is using the static type information for the first argument, which is different - object when you pass dbSet variable and DbSet<MyType> when you pass db.MyType.
The trick is to hide the static type information from the compiler like this
dynamic q = ((IEnumerable<object>)Queryable.Where((dynamic)dbSet, l)).ToList();

Dynamic lambda expression (OrderBy) and nullable property type

I'm trying to dynamically create expression that will sort data from database through Entity Framework. But I encountered one problem and cannot overcome it. Maybe let me explain what I'm trying to do. My goal is to create expression like this:
x => x.Property
Where "Property" is the name of property which I'd like to specify dynamically.
Now, let's go to the class, which represents the table in database (I simplified it to make things more clear):
public class MyModelClass
{
public long MyLongProperty { get; set; }
public decimal? MyNullableDecimalProperty { get; set; } // IMPORTANT: it's nullable
}
It is my code, where I'm trying to create expression described earlier:
// Db is EntityFramework context
IQueryable<MyModelClass> list = Db.MyModels.Select(x => x);
// x =>
var argument = Expression.Parameter(list.ElementType, "x");
// x.MyNullableDecimalProperty
var propertyToOrder = Expression.Property(argument, "MyNullableDecimalProperty");
// x => x.MyNullableDecimalProperty
var finalExpression = Expression.Call(
typeof (Queryable),
"OrderBy",
new[] { list.ElementType, typeof(IComparable) },
list.Expression,
Expression.Lambda<Func<MyModelClass, IComparable>>(propertyToOrder, argument));
list = list.Provider.CreateQuery<MyModelClass>(finalExpression);
Problem occurs at 4th statement (var finalExpression = Expression.Call(...)). I get an exception:
Expression of type „System.Nullable`1[System.Decimal]” cannot be used for return type „System.IComparable”.
As far I understand the problem is with me using "IComparable" type where "MyNullableDecimalProperty" is Nullable and Nullable doesn't user IComparable interface. The exception isn't thrown when I'm ordering by "MyLongProperty" or when I replace "IComparable".
So my questions:
What type should I use to make it work with any nullable properties?
Is it possible to use one type and it will work with all properties whether they are nullable or non-nullable.
Notice: I know I can use for ex. Dynamic Linq library, but I'm not interested in this solution - I'd like to learn how to overcome it without using 3rd party libraries.
There's no reason to use IComparable. Indeed, many types that are comparable do not implement IComparable. Simply use the runtime type of whatever you're passing:
var finalExpression = Expression.Call(
typeof (Queryable),
"OrderBy",
new[] { list.ElementType, propertyToOrder.Type },
list.Expression,
Expression.Lambda(propertyToOrder, new [] { argument }));
You don't need to specify the IComparable part, and you can also use the Queryable.OrderBy / OrderByDescending methods to help you here:
IQueryable<TSource> source = .....
var sourceType = typeof(TSource);
var parameter = Expression.Parameter(sourceType, "item");
var propertyInfo = GetProperty(sourceType, propertyName);
var orderByProperty = Expression.Property(parameter, propertyInfo);
orderBy = Expression.Lambda(orderByProperty, new[] { parameter });
return Queryable.OrderBy(source, (dynamic)orderBy)
Give that a go and see how you get on, I'm pretty sure this will work for native types and nullable types.

c# EF DbContext - generate object dynamically from string

I want to make a factory class to return DbContext object where table name will be passed as string. There are more than 100 tables in database, each having a different structure/schema.
The idea is to pass tableName as string in the method which will return object in EF and we can select/update these records. I found this code in some article but have some confusion how to use it:
public ObjectContext Context(EntityObject entity)
{
var relationshipManager = ((IEntityWithRelationships)entity).RelationshipManager;
var wrappedOwnerProperty = relationshipManager.GetType().GetProperty("WrappedOwner", BindingFlags.Instance | BindingFlags.NonPublic);
var wrappedOwner = wrappedOwnerProperty.GetValue(relationshipManager);
var contextProperty = wrappedOwner.GetType().GetProperty("Context");
return (ObjectContext)contextProperty.GetValue(wrappedOwner);
}
I am not sure if this is what I need. Also what should I pass in EntityObject entity and where I should pass the tableName?
Please let me know if there is any other way to achieve the same thing.
One simple way to do this is to use the DbContext.Set() method, and get the type based on the string.
using (var db = new MyDbContext)
{
string tableName = "ApplicationUser";
var type = Assembly.GetExecutingAssembly()
.GetTypes()
.FirstOrDefault(t => t.Name == tableName);
if(type != null)
DbSet catContext = context.Set(type);
}
However, this has one drawback and that's that this is a non-generic DbSet (ie it's a DbSet not a DbSet<T>).
A way to get the Generic DbSet (which allows linq functionality) would be to do something like this:
using (var db = new IdentityDbContext())
{
string tableName = "ApplicationUser";
var type = Assembly.GetExecutingAssembly()
.GetTypes().FirstOrDefault(t => t.Name == tableName);
var method = db.GetType().GetMethods()
.First(x => x.IsGenericMethod && x.Name == "Set");
MethodInfo generic = method.MakeGenericMethod(type);
var set = generic.Invoke(db, null);
}
Of course set will be an object here, and you'll have to cast it somehow, which is still part of the problem.
When it boils down to it, if you aren't going to work with statically compiled types, you're going to have to keep dealing with reflection, particularly when you have generic types to deal with (ie DbSet<T>, among others). You have to get them cast to a static type at some point to call the methods, or keep doing MethodInfo.Invoke's.
Another option is to use dynamic, but you can't use dynamics with c# extension methods (without casting to a concrete type) so you're back in the same boat of no Linq support.
Using Linq by reflection is a huge pain.
To be honest, If you have 100 classes, I'd just bite the bullet and write the hard coded types, or use a code generator to do it for you like CodeSmith.

Categories

Resources