Dynamic table name in linq [duplicate] - c#

This question already has answers here:
Querying data using Entity Framework from dynamically created table
(2 answers)
Dynamically set the table name in LINQ query
(3 answers)
Closed 8 years ago.
I'm trying to execute some LINQ commands using a dynamic table name. For example, instead of:
var o = (from x in context.users select x);
I want to use something like:
var o = (from x in getTableObjectByName("users", context) select x);
More or less. Here's the code I have so far, which both compiles and runs:
using (MySiteEntities ipe2 = new MySiteEntities()) {
var propinfo1 = Type.GetType("MySiteNamespace.MySiteEntities").GetProperty("users");
var propval1 = propinfo1.GetValue(ipe2, null);
}
That runs, but always returns zero records. The users table most definitely contains records, and in any case when I call it directly using the first method above I get all of the records as expected. How can I modify my code to actually pull down records, rather than just an empty collection?
Edit: I've also tried this:
using (MySiteEntities ipe = new MySiteEntities())
{
var prop = Type.GetType("MySiteNamespace.MySiteEntities").GetProperty("users");
Type dbsetType = typeof(DbSet<>);
dbsetType = dbsetType.MakeGenericType(Type.GetType("MySiteNamespace.user"));
Type t = dbsetType.GetType();
var val = prop.GetValue(ipe, null);
}
In this case, the code not only runs, but actually returns the results as expected. However, val is an Object. I need to cast it to the type DbSet<user>, which would be easy enough, except that the parameter user is only known at runtime....the cast needs to be dynamic as well. I've tried using Convert.ChangeType(val, t);, but that throws an
InvalidCastException (Object must implement IConvertible).
How can I convert the val variable to an actually usable object?
No idea if this is relevant, but this is on EntityFramework 4.

In your DbContext class, add a method say called Set that returns:
public DbSet Set(string name)
{
// you may need to fill in the namespace of your context
return base.Set(Type.GetType(name));
}
Which you can query like this:
using (var db = new YourDataContext())
{
// Since your DbSet isn't generic, you can can't use this:
// db.Set("Namespace.EntityName").AsQueryable().Where(a=> a.HasSomeValue...
// Your queries should also be string based.
// Use the System.Linq.Dynamic nuget package/namespace
var results = db.Set("Namespace.EntityName")
.AsQueryable()
.Where("SomeProperty > #1 and SomeThing < #2", aValue, anotherValue);
// you can now iterate over the results collection of objects
}
More information on System.Linq.Dynamic can be found here

Related

Xamarin/C# - Insert data into SQLite database programmatically for multiple tables?

My Xamarin app pulls data from an API and inserts that data into a SQLite table. The API currently has 9 tables defined, and as such there are 9 classes in my app that match those tables. I used the code snippet from this question's accepted answer:
Getting all types in a namespace via reflection
Below is my code, using the snippet from the answer and the foreach loop I'm trying to build that'll insert the data.
string nspace = "App.Tables";
var q = from t in Assembly.GetExecutingAssembly().GetTypes()
where t.IsClass && t.Namespace == nspace
select t.Name; // Getting list of classes as IEnumerable
var L = q.ToList(); // Converting to List
foreach (var name in L) // Inserts data for every class found
{
var response = await httpClient.Value.GetStringAsync("http://website.com/api/" + name + "s"); // Substitutes each class name into API url
var data = JsonConvert.DeserializeObject<List<TableName>>(response); // Deserializes into List
using (SQLiteConnection conn = new SQLiteConnection(App.DatabaseLocation)) // Opens database connection
{
conn.CreateTable<TableName>(); // Create table
conn.DeleteAll<TableName>(); // Delete all old data
conn.InsertAll(data); // Inserts new data
}
}
I don't know what TableName should be in order to get the correct class for each item on the list. For example: say the list contained the strings Table1, Table2, and Table3 - it got those strings from the App.Tables namespace which contains three separate classes called Table1, Table2, and Table3. When the first part of the code gets the list and stores it as the variable L, it should get the data for each "name" variable in L and then insert it into the matching table. How do I refer it to the table?
Before I would give my answer, I would like to tell you that I do not
recommend updating tables via reflection - tables should contain
logically distinct entities, so batch deleting and updating them is
kinda weird. This is one of those few occurrences where I would never
work around typing the updates of the tables one by one. But those are
just my two cents... Of course if I had a thousand tables my opinion
would not stand.
This kind of reflection also makes your code hard to follow and trace - think about how you will search for usages of the CreateTable<ExampleClass>() method? You will never trace it back - or only via great efforts - that you called it in this piece of code.
So to answer your question...
You first get the method group, then create a generic version of it based on the type. I think converting from Type to string is unnecessary for the part you're looking for, since you need to convert back to Type
using (SQLiteConnection conn = new SQLiteConnection(App.DatabaseLocation)) {
MethodInfo nonGenMethodCreate = typeof(SQLiteConnection).GetMethod("CreateTable");
MethodInfo nonGenMethodDeleteAll = typeof(SQLiteConnection).GetMethod("DeleteAll");
MethodInfo nonGenMethodInsertAll = typeof(SQLiteConnection).GetMethod("InsertAll");
foreach(Type t in Assembly.GetExecutingAssembly.GetTypes()) {
MethodInfo genMethodCreate = nonGenMethodCreate.MakeGenericMethod(t);
MethodInfo genMethodDeleteAll = nonGenMethodDeleteAll.MakeGenericMethod(t);
MethodInfo genMethodInsertAll = nonGenMethodInsertAll.MakeGenericMethod(t);
genMethodCreate.Invoke(conn, null);
genMethodDeleteAll.Invoke(conn, null);
genMethodInsertAll.Invoke(conn, new[] { data });
}
}

Dynamically get a DbSet<T> by Entity class name

I'm trying to use System.Reflections to get a DbSet<T> dynamically from its name.
What I've got right now is:
The DbSet name
The DbSet's Type stored on a variable
The issue I'm facing comes out when trying to use the dbcontext.Set<T>() method, since (these are my tries so far):
When I try to assign to <T> my DbSet Type, it throws me the following compilation error:
"XXX is a variable but is used like a type"
If I try with using both the Extension methods that you will find below in my code (which I made in order to try to get an IQueryable<T>), it returns a IQueryable<object>, which unfortunately is not what I am looking for, since of course when I try to manipulate it with further Reflections, it lacks of all the properties that the original class has…
What am I doing wrong? How can I get a DbSet<T>?
My code is the following, but of course, let me know if you need more infos, clarifications or code snippets.
My Controller's Method:
public bool MyMethod (string t, int id, string jsonupdate)
{
string _tableName = t;
Type _type = TypeFinder.FindType(_tableName); //returns the correct type
//FIRST TRY
//throws error: "_type is a variable but is used like a type"
var tableSet = _context.Set<_type>();
//SECOND TRY
//returns me an IQueryable<object>, I need an IQueryable<MyType>
var tableSet2 = _context.Set(_type);
//THIRD TRY
//always returns me am IQueryable<object>, I need an IQueryable<MyType>
var calcInstance = Activator.CreateInstance(_type);
var _tableSet3 = _context.Set2(calcInstance);
//...
}
Class ContextSetExtension
public static class ContextSetExtension
{
public static IQueryable<object> Set(this DbContext _context, Type t)
{
var res= _context.GetType().GetMethod("Set").MakeGenericMethod(t).Invoke(_context, null);
return (IQueryable<object>)res;
}
public static IQueryable<T>Set2<T>(this DbContext _context, T t)
{
var typo = t.GetType();
return (IQueryable<T>)_context.GetType().GetMethod("Set").MakeGenericMethod(typo).Invoke(_context, null);
}
}
EDIT Added TypeFinder's inner code.
In brief, this method does the same of Type.GetType, but searches Type on ALL the generated assemblies
public class TypeFinder
{
public TypeFinder()
{
}
public static Type FindType(string name)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
var result = (from elem in (from app in assemblies
select (from tip in app.GetTypes()
where tip.Name == name.Trim()
select tip).FirstOrDefault())
where elem != null
select elem).FirstOrDefault();
return result;
}
}
UPDATE as requested in the comments, here's the specific case:
In my DB i've got some tables which are really similar each other, so the idea was to create a dynamic table-update method which would be good for every table, just passing to this method the table name, the ID of the row to update and the JSON containing data to update.
So, in brief, I would perform some updates on the table given in input as DbSet type, updating the row with ID==id in input with the data contained inside the JSON, which will be parsed inside an object of type X(the same of dbset)/into a dictionary.
In pseudo-code:
public bool MyMethod (string t, int id, string jsonupdate)
{
string _tableName = t;
Type _type = TypeFinder.FindType(_tableName); //returns the correct type
//THIS DOESN'T WORKS, of course, since as said above:
//<<throws error: "_type is a variable but is used like a type">>
var tableSet = _context.Set<_type>();
//parsing the JSON
var newObj = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonupdate, _type);
//THIS OF COURSE DOESN'T WORKS TOO
//selecting the row to update:
var toUpdate = tableSet.Where(x => x.Id == id).FirstOrDefault();
if(toUpdate!=null)
{
var newProperties = newObj.GetType().GetProperties();
var toUpdateProperties = toUpdate.GetType().GetProperties();
foreach(var item in properties)
{
var temp = toUpdateProperties.Where(p => p.Name==item.Name)
{
//I write it really in briefand fast, without lots of checks.
//I think this is enough, I hope
temp.SetValue(toUpdate, item.GetValue());
}
}
_context.SaveChanges();
}
return false;
}
returns me an IQueryable<object>, I need an IQueryable<MyType>
Well, that will never work. Your IQueryable cannot be of type IQueryable<MyType>because that would mean the compiler would need to know what MyType is and that is not possible, because the whole point of this exercise is to decide that on runtime.
Maybe it's enough to know that those objects are in fact instances of MyType?
If not, I think you have painted yourself into a corner here and you are trying to figure out what paint to use to get out of there. Take a step back, it's probably not a technical problem. Why do you need to do this? Why do you have the conflicting needs of knowing the type at runtime only and knowing it at compile time?
You need to think about your requirements, not about the technical details.
I needed to dynamically load a single record from the database for each type in a list of known types, to print a test email when an admin is editing the template, so I did this:
List<object> args = new List<object>();
//...
//other stuff happens that isn't relevant to the OP, including adding a couple fixed items to args
//...
foreach (Type type in EmailSender.GetParameterTypes())
{
//skip anything already in the list
if (args.Any(a => a.GetType().IsAssignableFrom(type))) continue;
//dynamically get an item from the database for this type, safely assume that 1st column is the PK
string sql = dbContext.Set(type).Sql.Replace("SELECT", "SELECT TOP 1") + " ORDER BY 1 DESC";
var biff = dbContext.Set(type).SqlQuery(sql).AsNoTracking().ToListAsync().Result.First();
args.Add(biff);
}
Caveat: I know at least one record will exist for all entities I'm doing this for, and only one instance of each type may be passed to the email generator (which has a number of Debug.Asserts to test validity of implementation).
If you know the record ID you're looking for, rather than the entire table, you can use dbContext.Set(type).Find(). If you want the entire table of whatever type you've sussed out, you can just do this:
string sql = dbContext.Set(type).Sql; //append a WHERE clause here if needed/feasible, use reflection?
var biff = dbContext.Set(type).SqlQuery(sql).ToListAsync().Result;
Feels a little clunky, but it works. There is strangely no ToList without Async, but I can run synchronously here. In my case, it was essential to turn off Proxy Creation, but you look like you want to maintain a contextful state so you can write back to db. I'm doing a bunch of reflection later, so I don't really care about strong typing such a resulting collection (hence a List<object>). But once you have the collection (even just as object), you should be able to use System.Reflection as you are doing in your UPDATE sample code, since you know the type and can use SetValue with known/given property names in such a manner.
And I'm using .NET Framework, but hopefully this may translate over to .NET Core.
EDIT: tested and working:
public async Task<bool> MyMethod(string _type)
{
Type type = Type.GetType(_type);
var tableSet = _context.Set(type);
var list = await db.ToListAsync();
// do something
}
// pass the full namespace of class
var result = await MyMethod("Namespace.Models.MyClass")
IMPORTANT NOTE: your DbContext need to have the DbSet declared to work!
public class MyContext : DbContext
{
public DbSet<MyClass> MyClasses { get; set; }
}

NHibernate: How to return list of scalar values (from one column) using CreateSQLQuery?

What is the best/cleanest method of calling a native SQL returning a list of scalar values (ints in my case) having ISession object?
I am trying to run the following but I am always getting some errors:
var query = _session.CreateSQLQuery("SELECT Id FROM SomeTable");
A. var ids = query.List<int>(); // <-- throws ArgumentNullException "Value cannot be null.\r\nParameter name: item"
B. var ids = query.List(); returns one element array with no valid information.
C. query.SetResultTransformer(NHibernate.Transform.Transformers.AliasToBean<int>());
var ids = query.List<int>(); // throws PropertyNotFoundException: "Could not find a setter for property 'Id' in class 'System.Int32'"
Is there a way to retrieve a list of ints without creating an entity class containing just one int property named Id?
When you call List from CreateSQLQuery you will get a instance of IList and it internally it is a List<object>. If you have null values on this result, you will not be able to convert to int because it is a value type. So, a solution is to iterate over the result and convert it when it is a valid integer. For sample:
var values = _session.CreateSQLQuery("SELECT Id FROM SomeTable").List();
var ids = new List<int>();
foreach (var item in values)
{
if (item != null)
ids.Add(Convert.ToInt32(item));
}
If this is a mapped table on the nhibernate scope, you could use LINQ to do this, for sample:
var ids = session.Query<SomeEntity>().Select(x => x.Id).ToList();
I know you are not using IQueryOver, but it is simpler, dynamic and cleaner than the way you are doing this now.
public IList<TReturn> GetValues<TEntity, TReturn>(IProjection column, Junction where, int top) where TEntity : BaseEntity
{
IQueryOver<TEntity> query = null;
if(where == null)
query = session.QueryOver<TEntity>().Select(column);
else
query = session.QueryOver<TEntity>().Select(column).Where(where);
IList<TReturn> instance = null;
if(top == 0)
instance = query.List<TReturn>();
else
instance = query.Take(top).List<TReturn>();
return instance;
}
TEntity in above code is entity that represent (mapped to) your table. Note that this is just to build the query. It will NOT return Entity.
TReturn is the return type. This can be any standard data type like int in your case.
IProjection column parameter is the name of the column you want to select.
Junction where parameter allows you to specify the filter on rows if any. To retrieve all rows, pass it null.
Following is the way you call it:
Junction where = Restrictions.Conjunction();
where.Add(Restrictions.Eq(..........));
IList<int> idList = GetValues<SomeTableEntity, int>(Projections.Property<SomeTableEntity>(x => x.Id), where, 0);
This way, you avoid writing hard-coded SQL query as string in your code. As you can see, this function can be used with any Entity (table) and any column.

ORMLite SqlList with Tuples

I'm having some troubles selecting a Tuple of Objects from my custom SQL query with ORMLite.
I have the following code:
var query = "select definition.*, timeslot.*, type.* from <blah blah>";
var defs = dbConnection.SqlList<Tuple<Definition, Timeslot, Type>>(query, new
{
/* query parameters */
});
The query itself is fine (I've tested it in SQL Management Studio).
The code above sets attributes only for the first item of the Tuple, leaving to the default state the others.
I've selected singularly each object and the result is correct (so no trouble during the conversion to POCO I guess).
Same thing goes if I use Select<Tuple<Definition, Timeslot, Type>> instead of SqlList.
I couldn't manage to try with MultiSelect since it appears to not take a string.
What is the correct way to select a Tuple in this manner?
I am working in C#.
Thanks in advance!
SelectMulti seems to be what you're looking for here.
From the documentation under the Selecting multiple columns across joined tables heading:
// Note: I'm making an assumption on your query here.
// Build the `q` object however it needs to be.
var q = db.From<Definition>()
.Join<Definition, Timeslot>()
.Join<Definition, Type>();
var results = db.SelectMulti<Definition, Timeslot, Type>(q);
foreach (var tuple in results)
{
var definition = tuple.Item1;
var timeslot = tuple.Item2;
var type = tuple.Item3;
}

Convert anonymous type to array or arraylist. Can it be done

Trying to retrieve data using linq from a database. I would like to use anonymous types and convert to an Ilist, Array, ArrayList or Collection. The data is used in a third party object that accepts Ilist, arraylist or collections.
I can't seem to get this to work. I get the following error, "Sequence operators not supported for type 'System.String'"
using (var db = new dbDataContext())
{
var query = from e in db.people
select new
{
Count = e.LastName.Count()
};
Array test;
test = query.ToArray();
}
It's got nothing to do with converting the results to array lists, or even anonymous types. Here's another version of your code which will fail:
using (var db = new dbDataContext())
{
var query = db.people.Select(x => x.LastName.Count());
foreach (int x in query)
{
Console.WriteLine(x);
}
}
That will still fail in the same way - because it's the translation of this bit:
x => x.LastName.Count()
into SQL which is causing problems.
Change it to:
x => x.LastName.Length
and I suspect you'll find it works. Note that this isn't really a C# issue - it's just LINQ to SQL's translation abilities.
I would suggest that you don't use an anonymous type here though - it's pointless. Maybe this isn't your complete code, but in general if you find yourself creating an anonymous type with a single member, ask yourself if it's really doing you any good.
The ArrayList class has a constructor that accepts ICollection.
You should be able to feed it a List version of your LINQ result.
using (var db = new dbDataContext()) {
var query = from e in db.people
select new { Count = e.LastName.Count() };
ArrayList list = new ArrayList(query.ToList());
}
I don't have Visual Studio here (I'm on my Mac), but it might be of help.
(ToArray should suffice as well)
You might need to replace your Count() by Length.

Categories