I am trying to create a generic method to get a list of records from different tables. Based on my internet research I thought this would work but have been unable to make it work. A lot of code is omitted for clarity:
The method:
public IEnumerable<object> GetEntityUpdates(string entity)
{
string query = "select * from " + entity;
IEnumerable<object> entityList;
entityList = db.Database.SqlQuery<object>(query).ToList();
return entityList;
}
This is then the calling code where I am attempting the cast which fails no matter how I spin it:
var theList = da.GetEntityUpdates("EntityName");
IEnumerable<EntityName> entityList = theList.Cast<EntityName>();
EntityName is a database model which corresponds to the select above.
InvalidCastException is then thrown.
"Unable to cast object of type 'System.Object' to type 'EntityName'."
Change your GetEntityUpdates to use generics. Entity Framework also has a Set method for DbContext that lets you access entities of a certain type. More info here: https://msdn.microsoft.com/en-us/library/gg696521(v=vs.113).aspx
public IEnumerable<T> GetEntityUpdates<T>() where T : class
{
return db.Set<T>().ToList();
}
Then to call it you just pass in your entity class as a type parameter:
var foo = da.GetEntityUpdates<EntityName1>();
var bar = da.GetEntityUpdates<EntityName2>();
The method you're using to extract information, SqlQuery<object>, relies on reflection in order to build your objects, not sure what framework is being used but would guess making your method generic would solve the problem
public IEnumerable<T> GetEntityUpdates<T>(string entity)
{
string query = "select * from " + entity;
return db.Database.SqlQuery<T>(query).ToList();
}
Related
I want to get a DbSet with the class name that I have stored in a variable.
I have tried with this code:
string name = "ParosLineas";
var dbset = (System.Data.Entity.DbSet)efContext.GetType().GetProperty(name).GetValue(efContext);
dbset.AddRange(dates.[name]);
efContext.SaveChanges();
but I get this error:
System.InvalidCastException: 'Unable to cast object of type 'System.Data.Entity.DbSet1[Report.Models.ParosLinea]' to type 'System.Data.Entity.DbSet'
this is my ParosLine declaration:
The problem is that the generic DbSet<TEntity> class does not inherit (hence cannot be cast to) the non generic DbSet.
I see two options.
If you know the namespace name of the entity class, you can use Type.GetType to get the corresponding Type which in turn can be used to call the non generic DbContext.Set method which returns a non generic DbSet object, e.g.
string nameSpace = "MyEntities";
string name = "ParosLineas";
var type = Type.GetType($"{namespace}.{name}");
var dbSet = efContext.Set(type);
Another way is to use reflection to get the DbSet<T> property as you are doung, but cast the result to IQueryable. Then you can use the IQueryable.ElementType to call the non generic Set method as above:
string name = "ParosLineas";
var type = ((IQueryable)efContext.GetType().GetProperty(name).GetValue(efContext))
.ElementType;
var dbSet = db.Set(type);
The first method is preferable. First, because it uses less calls, and second, because does not require DbSet<T> property in the context and does not assume that DbSet<T> property name is the same as the entity class name.
I am passing a string of the name of the entity type I want to query and getting the type based on the string. I want to get the DbSet back and return an IQueryable. The problem is where I am doing (DbSet<tableEntity>) and getting the following error:
tableEntity is a variable but used like a type
when trying to cast. Is there a way to resolve this?
public object GetList(string tableEntity)
{
Type tableEntity = Type.GetType("TestProject." + typeName + ", TestProject");
var dbObject = (DbSet<tableEntity>)typeof(DbContext).GetMethod("Set", Type.EmptyTypes)
.MakeGenericMethod(tableEntity)
.Invoke(databaseContext, null);
return dbObject.AsQueryable();
}
EDIT
Just to add I don't have access to the type that's we I am passing the name through a string.
So it turns out that the entity type is literally not known, or knowable, at compile time. It has to be a string.
The only place you're using the type at compile time is in the cast to (DbSet<tableEntity>). Well, you may not need that. All you need from that type is to call AsQueryable(), and AsQueryable() is an extension method for IEnumerable, with generic and non-generic versions. IF we call it through non-generic IEnumerable, that's non-generic AsQueryable(), returning non-generic IQueryable. But we're returning object anyway, so hey. For the result of this thing to be useful, something somewhere must be doing a fair amount of reflection on it anyway, so the declared type is likely to be of little consequence.
See if this works:
public object GetList(string typeName)
{
Type tableEntity = Type.GetType("TestProject." + typeName + ", TestProject");
var dbObject = (System.Collections.IEnumerable)
typeof(DbContext).GetMethod("Set", Type.EmptyTypes)
.MakeGenericMethod(tableEntity)
.Invoke(databaseContext, null);
return dbObject.AsQueryable();
}
If it turns out you need generic IQueryable<TEntityType>, we'll have to use reflection to get MethodInfo for AsQueryable<TEntityType> for the unknown (at compile time) entity type, and call MakeGenericMethod(tableEntity) on that.
First try:
In the language, type parameters to generics must be actual types, not instances of the Type class. That's because they're resolved at compile time.
But that's no problem; to pass a type parameter to a generic method, simply write a generic method with a type parameter.
You can't do this:
var stuff = GetList("MyTableEntityClass");
But this is just as good:
var stuff = GetList<MyTableEntityClass>();
...
public object GetList<TTableEntity>()
{
var dbObject = (DbSet<TTableEntity>)typeof(DbContext)
.GetMethod("Set", Type.EmptyTypes)
.MakeGenericMethod(typeof(TTableEntity))
.Invoke(databaseContext, null);
return dbObject.AsQueryable();
}
Reflection is different; that's why we pass typeof(TTableEntity) to MakeGenericMethod().
And once we're using an actual type that the compiler can check, we can do better with our return type, too:
public IQueryable<TTableEntity> GetList<TTableEntity>()
{
var dbObject = (DbSet<TTableEntity>)typeof(DbContext)
.GetMethod("Set", Type.EmptyTypes)
.MakeGenericMethod(typeof(TTableEntity))
.Invoke(databaseContext, null);
return dbObject.AsQueryable();
}
Since, as Ed mentioned, you don't use the table entity type at compile time, why not just use the non-generic databaseContext.Set (tableEntity).AsQueryable ()? But if you've set your heart on Set<>, try this:
public object GetList(string tableEntity)
{
Type tableEntity = Type.GetType("TestProject." + typeName + ", TestProject");
return GetType ()
.GetMethod ("GetListHelper")
.MakeGenericMethod (tableEntity)
.Invoke (this) ;
}
public object GetListHelper<T> () where T : class
{
var dbObject = databaseContext.Set<T> (null) ;
return dbObject.AsQueryable();
}
Is it possible to pass type to IQueryable to realize at run time.
Like,
//Get type at runtime
Type type = Type.GetType("fully qualified class name");
IQueryable<type> test = // <-returned object of this type
Actual problem as below:
Below I am able to get right side object with specific type, but that is not casting to type of query variable. Also I will have known type for query.
Dictionary<string, Type> myDictionary = new Dictionary<string, Type>()
{
{ "tableName", typeof(tableName) }
};
//Below I am able to get right side object with specific type, but that is not casting to type of query variable. Also I will have known type for query.
IQueryable<Type> query= EFContext.Set(myDictionary[tableName]).AsQueryable();
Later using this query object to select data by passing select/where condition dynamically.
var data = query.Select(x=> new
{
id= x.id,
name= x.name .. etc
}).ToList();
Later I need to use this test variable to dynamically query data.
Also please suggest any alternative to resolve this scenario.
If you have an example of the IQueryable type you need, you can use an generic method to capture the type - this one returns a null of the proper type:
public static T NullByExample<T>(this T _) where T : class => (T)null;
If you have an example of the items returned, you can use an extension like:
public static IEnumerable<T> EmptyByExample<T>(this T _) => Enumerable.Empty<T>();
Either use AsQueryable on the result:
var test = EmptyByExample(classObject).AsQueryable();
or create an IQueryable variant - unfortunately, there isn't really an IQueryable equivalent to Enumerable.Empty:
public static IQueryable<T> EmptyQueryByExample<T>(this T _) => Enumerable.Empty<T>().AsQueryable();
var test = EmptyQueryByExample(queryObject);
Otherwise, as mentioned, you are in the world of reflection, which probably indicates you are doing something wrong. The problem is you will find you can only get test to be of type object in that case, because the compiler can't know what type represents and e.g. var is a compile time shorthand, unless you want to use dynamic (and you shouldn't do that either).
There are ways to do what you're asking via reflection, but maintainability will be limited. You're probably better off creating a shared interface to your types and writing a generic method, something like this:
interface IMyInterface
{
string SomeProperty {get;set;}
}
class MyClass : IMyInterface
{
public string SomeProperty {get;set;}
}
IQueryable<T> SomeMethod<T>() where T : IMyInterface, new()
{
var result = new List<T>() {
new T() { SomeProperty = "a"},
new T() { SomeProperty = "b"}
};
return result.AsQueryable();
}
So a call to the generic method might be:
var temp = SomeMethod<MyClass>();
I'm working with Entity Framework (v4) under Visual Studio 2010 (going against an Oracle database).
A record has been read in as follows:
MYTABLE_DEF t;
t = db.MYTABLE_DEF.Find(identifier);
Now, I know I can access the fields in t directly:
Console.WriteLine(t.SOMEVALUE);
What I would like to be able to do is reference the fields in t, either by doing some sort of 'index' (something like t[0] for first field, t[1] for second field). If that's not possible, then is it possible to bind the field at run time? Something like:
string whichfield = Console.ReadLine();
Console.WriteLine(t[whichfield]) // I know this won't work
I've basically been learning Entity Framework through trial and error (and google) but haven't come across anything sort of indirect reference like that.
Assuming MYTABLE_DEF is an ordinary Entity Class, you should be able to simply reflect over the public fields and return the nth one. Something like this: (untested)
public object this[int index]
{
Type myType = this.GetType();
PropertyInfo[] myProperties =
myType.GetProperties(BindingFlags.Public|BindingFlags.Instance);
object result = myProperties[myIndex].GetValue(myClassInstance, null);
return result;
}
For convenience, I would write this as an extension method on object:
public static Property IndexedProperty(this object obj, int index)
{
Type myType = obj.GetType();
PropertyInfo[] myProperties =
myType.GetProperties(BindingFlags.Public|BindingFlags.Instance);
return myProperties[myIndex];
}
which you can then call thusly:
var theSixthProperty = myObject.IndexedProperty(6);
var valueOfSixthProperty = theSixthProperty.GetValue();
Having it as an Extension Method eliminates the problem of having to write an indexer for every Entity Class.
I'm implementing generics for Add, Remove, and Find, because I'm implementing multi-tenancy that gives me access to IQueryables from the database, as opposed to DbSets.
How Add was implemented:
Code before:
interest = db.Interests.Add(new Interest()) // Interests is dbSet
Code after:
interest = db.Add(new Interest()) // Interests is dbSet
// in db class
public T Add<T>(T item) where T : class, ITenantData
{
var set = _appContext.Set<T>();
return set.Add(item);
}
This works. Now I'm trying to do the same thing for Find, but because I'm working with an Id and not a Typed parameter, I'm a bit stuck.
Code before:
var aptitude = db.Aptitudes.Find(interest.AptitudeId); // Aptitudes is a DbSet
Code after:
var aptitude = db.Find(interest.AptitudeId); // how to identify type?
I can't do this:
var aptitude = db.Aptitudes.Find(interest.AptitudeId);
Because Aptitudes is now an IQueryable.
So this is currently failing:
public T Find<T>(T query) where T : class, ITenantData
{
var set = _mentorContext.Set<T>();
return set.Find(query);
}
Because the type isn't identified - I get :
The type 'int' must be a reference type in order to use it as
parameter 'T' in the generic type or method
'App.Context.TenantContext.Find(T)' Service.cs
I'm a bit new to generics, any help here would be much appreciated.
Solve this with extension methods.
public IQueryable<T> Query<T>() where T : class, ITenantData
{
return _mentorContext.Set<T>().AsNoTracking();
}
...meanwhile in another class...
public static MentorContextExtensions
{
public static InterestEntity ByAptitudeId(
this IQueryable<InterestEntity> queryable,
int aptitudeId)
{
return queryable.SingleOrDefault(x => x.AptitudeId == aptitudeId);
}
}
...usage against IQueryable<T>...
db.Query<InterestEntity>().ByAptitudeId(interest.AptitudeId);
The example above returns a SingleOrDefault, but you could just as easily return sets (pre-filtered IQueryables or IEnumerables) using .Where, bools using .Any, etc.
To maintain usage of the .Find (by primary key) method on the IDbSet, you can do this:
public T Find<T>(params object[] keyComponents) where T : class, ITenantData
{
return _mentorContext.Set<T>().Find(keyComponents);
}
...and use it like this...
var aptitude = db.Find<Aptitude>(interest.AptitudeId);
... that said, using .Find may only be faster when you are doing inserts, updates, deletes with the entity, in some kind of mutation code. .Find will always keep the entity attached to the context, which is what you want when you need to mutate the data. However you cannot invoke .AsNoTracking() on it because it returns a single entity. If you only need the entity to read its properties or for display purposes, .AsNoTracking will keep the entire entity graph (the entity and its navigation / collection properties) detached from the context, which may be faster than using .Find. If you later want to use the same entity instance in mutation code, you can attach it to the context at that time.