EF Core Get DBSet by string then use it on a query - c#

I am trying to make a tool that enables a user to select tables/columns from a ui. When the user posts the "query configuration object" to the server, I want to create a dynamic SQL Query using Entity Framework Core
Example
Given var tableName = "Table1";
I want to grab the DbSet<tableName> and then run something like
var dbSet = GetDbSet("Table1");
var stuff = await dbSet.Take(10).ToListAsync();
I'm sure this is possible in some shape/form but all the articles I'm seeing are similar in the approach. Everything compiles, but when I run it, I get Object Refeerence not set to an instance of an object fun.
Here's what I have:
var select = GetDbSet(tableName);
var results = await select.Take(10).ToListAsync();
The GetDbSet method:
This works obviously But, I need to variablize the T (which I can't do??)
private DbSet<Table1> GetDbSet(string name)
{
return _dbContext.Set<Table1>();
}
From what I've read, I created an extension method since I need Set(T) and not Set<T>()
public static IQueryable<object> Set(this DbContext _context, Type t)
{
return (IQueryable<object>) _context.GetType().GetMethod("Set").MakeGenericMethod(t).Invoke(_context, null);
}
This does what I think I'm expecting it to:
private IQueryable GetDbSet(string name)
{
// forcing a type instead of fiddling with the string for simplicity
return _dbContext.Set(_dbContext.Table1.GetType());
}
But, this code won't compile:
var select = GetDbSet(tableName);
var results = await select.Take(10).ToListAsync();
'IQueryable' does not contain a definition for 'Take' and no
accessible extension method 'Take' accepting a first argument of type
'IQueryable' could be found (are you missing a using directive or an
assembly reference?)
Am I even on the right path for grabbing a DbSet and using it to query? Do I need to build extension methods for Take, and any other methods I want to use?

Related

Type error trying to call Any() using Expression<Func<..>> from method

I am creating a reusable mapping expression from my EF entity to my BLL entity:
private Expression<Func<Person, bool>> GetNameFilter(string name)
{
return person => person.Profile.UserName == name;
}
internal Expression<Func<Ef.Perk, Bll.Perk>> MapPerkPerk(string name)
{
return perk => new Bll.Perk
{
Id = perk.PerkId,
Name = perk.PerkName,
Description = perk.PerkDescription,
//Compilation Error
Owned = perk.Players.Any(GetNameFilter(name))
};
}
And I am getting a compilation error on the noted line. The error reads:
ICollection does not contain a definition for 'Any' and the best extension method overload 'Queryable.Any(IQueryable, Expression>)' requires a receiver of type 'IQueryable'
But this does not happen when I push this expression in directly:
internal Expression<Func<Ef.Perk, Bll.Perk>> MapPerkPerk(string name)
{
return perk => new Bll.Perk
{
Id = perk.PerkId,
Name = perk.PerkName,
Description = perk.PerkDescription,
//No Compilation Error
Owned = perk.Players.Any(person => person.Profile.UserName == name)
};
}
Why is this happening? The type of both expressions is the same.
Using the LinqExpression solution found below, I am now getting the following error at runtime:
An exception of type 'System.InvalidCastException' occurred in LinqKit.dll but was not handled in user code
Additional information: Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpressionN' to type 'System.Linq.Expressions.LambdaExpression'.
internal FutureQuery<Perk> GetPlayerInfoPerks(string username)
{
return Master
.Perks
.VisiblePerks
.Select(Master.Perks.MapPerkPerk(username))
.Future();
}
Is this due to the use of the EntityFramework.Future library?
Why is this happening? The type of both expressions is the same.
The type of both expressions is not the same - they just look visually the same. The following is valid:
dbContext.Persons.Any(GetNameFilter(name))
and this is not:
perk.Players.Any(GetNameFilter(name))
Why? Because the first expects Expression<Func<...>> while the second - just Func<..> (the typical difference between IQueryable<T> and IEnumerable<T> methods with the same name). Hope you see the difference. When you type it directly, the C# compiler does its magic to emit one or the another, but when you do that manually, you are supposed to use the correct one.
The problem is addressed by LinqKit package with Invoke / Expand custom extension methods (and more generally with AsExpandable).
For your concrete example, the LinqKit solution could be like this:
using LinqKit;
...
internal Expression<Func<Ef.Perk, Bll.Perk>> MapPerkPerk(string name)
{
// LinqKit requires expressions to be in variables
var nameFilter = GetNameFilter(name);
return Linq.Expr((Ef.Perk perk) => new Bll.Perk
{
Id = perk.PerkId,
Name = perk.PerkName,
Description = perk.PerkDescription,
Owned = perk.Players.Any(p => nameFilter.Invoke(p))
}).Expand();
}

Abstracting out queries to the Azure Table Storage using Expressions

I'm creating a class to perform CRUD functionality with Azure Table Storage.
I'm using generic types in this.
I have the following method, which i'm trying to pass in an expression to get used in the TableQuery, but am having some issues.
The line TableQuery<T> query = new TableQuery<T>().Where<T>(criteria); won't compile, and gives me the message
Cannot implicitly convert type 'System.Linq.IQueryable<T>'
to 'Microsoft.WindowsAzure.Storage.Table.TableQuery<T>'.
An explicit conversion exists (are you missing a cast?)
I understand the message and know it's telling me i'm missing a cast, though I'm unsure how to code it correctly.
My full method is:
public List<T> GetSome<T>(Expression<Func<T, bool>> criteria) where T : ITableEntity, new()
{
TableQuery<T> query = new TableQuery<T>().Where<T>(criteria); // <---- This line isn't working
List<T> results = table.ExecuteQuery<T>(query).ToList<T>();
return results;
}
Ok, so I figured it out - how i can pass in a lambda expression for Azure Table Storage to use.
I changed my method to the following:
public List<T> GetSome<T>(Expression<Func<T, bool>> criteria) where T : ITableEntity, new()
{
// table, in this case, is my `CloudTable` instance
List<T> results = table.CreateQuery<T>().Where(criteria).ToList();
return results;
}
I can now pass in an expression. For example, to search against a DynamicTableEntity I can use:
// my table storage class
TableStorage ts = new TableStorage("ContactData");
// search with expression
List<DynamicTableEntity> results = ts.GetSome<DynamicTableEntity>(t => t.Properties["FirstName"].StringValue == "Darren");
If this is something you wouldn't/shouldn't do against an Azure Table Store, please do let me know.
In the meantime, this is how I have met the requirement.
The error is because the value returned by the extension method Where is of type IQueryable<T>, but you're assigning to a variable of type TableQuery<T> and there is no implicitly valid type conversion between these types.

c# Linq to Sql dynamic Data Context assignment

`Hi,
Can somebody please give me a pointer on this? I have 8 servers each with 8 databases which look identical exept server/database name. We are talking thousands of tables.
I create my data contexts with sqlmetal.exe
After creating my data contexts, I import them into the application and then I run comparison scripts over the databases to compare results.
My problem is dynamically switching between data contexts.
Datacontext.DAL.DUK1 duk1sdi = new Datacontext.DAL.DUK1(connectionString);
Datacontext.DAL.DUK3 duk3sdi = new Datacontext.DAL.DUK3(connectionString);
string fromOne = runQuery(duk1sdi);
string fromThree = runQuery(duk3sdi);
public static string runQuery(DataContext duk)
{
var query =
from result in duk.TableA
select result.Total;
string returnString = query;
return returnString;
}
I have no problem with the query running when the duk is predefined, however how do I define and pass the datacontext to the function?
The error I get is:
Error 1 'System.Data.Linq.DataContext' does not contain a definition
for 'TableA' and no extension method 'TableA' accepting a first
argument of type 'System.Data.Linq.DataContext' could be found (are
you missing a using directive or an assembly reference?)
You could use the GetTable<T> method, where T is the type of the table, e.g. TableA.
public static string runQuery(DataContext duk) {
var table = duk.GetTable<TableA>();
var query = from result in table select result.Total;
...
}
However, all types of TableA will need to be the same type, strictly (I'm pretty sure).
Otherwise you would need to literally branch the logic for the handling of each context. Since you can extend your DataContext instances (in general, maybe not in your specific case) then you could have them share an interface that exposes a collection property of TableA, but you would need a higher level context wrapper to pass around then - unless you pass around the collection by altering the method signature.
You can use interfaces. Check this answer, but be sure to script the interfaces using a .tt file with the amount of tables you have.
Edit:
If you have generated contexts which you want to use interchangeably in a reusable method, you have the problem that the generated TableA classes are not reusable, since they are different types (even though the names may match, but that doesn't make them equal). Therefore you need to abstract the actual types, and one way to do this, is to use interfaces. You build your reusable method around an interface which abstracts the specific context-type and table-type. The downside is that you have to implement the interfaces on the generated contexts and tabletypes. This though is something you can solve using a .tt script.
Pseudo code:
// Define interface for table
public interface ITableA {
// ... properties
}
// Define interface for context
public interface IMyContext {
IQueryable<ITableA> TableA { get; }
}
// Extend TableA from DUK1
public partial class TableA: ITableA {
}
// Extend DUK1
public partial class Datacontext.DAL.DUK1: IMyContext {
IQueryable<ITableA> IMyContext.TableA {
get { return TableA; }
}
}
// Same for DUK3 and TableA FROM DUK3
// Finally, your code
Datacontext.DAL.DUK1 duk1sdi = new Datacontext.DAL.DUK1(connectionString);
Datacontext.DAL.DUK3 duk3sdi = new Datacontext.DAL.DUK3(connectionString);
string fromOne = runQuery(duk1sdi);
string fromThree = runQuery(duk3sdi);
public static string runQuery(IMyContext duk) {
// Note: method accepts interface, not specific context type
var query = from result in duk.TableA
select result.Total;
string returnString = query;
return returnString;
}
If your schema is identical between databases, why script the dbml for all of them? Just create one context with it's associated classes and dynamically switch out the connection string when instantiating the context.
var duk1sdi = new Datacontext.DAL.DUK1(connectionString1);
var duk3sdi = new Datacontext.DAL.DUK1(connectionString2);
Thanks, guys, I think I found the simplist solution for me based a bit of both your answers and by RTFM (Programming Microsoft Linq in Microsoft .NET Framework 4 by Paulo Pialorsi and Marco Russo)
In this way I don't have to use the large DBML files. It is a shame because I'm going to have to create hundreds of tables in this way, but I can now switch between connection strings on the fly.
First I create the table structure. (outside the program code block)
[Table(Name = "TableA")]
public class TableA
{
[Column] public int result;
}
Then I define the table for use:
Table<TableA> TableA = dc.GetTable<TableA>();
And then I can query from it:
var query =
from result in TableA
select TableA.result;

How to define anonymous method types to build dynamic queries with LINQ?

I'm busy with a LINQ to SQL project that basically creates multiple threads for each entity type in my database, which constantly queries information from the DB in a thread.
Here's a pseudo example:
streamer.DefineDataExpression<Contacts>(x => x.FirstName == "Bob");
while(true)
{
List<Contacts> MyContactsResult = streamer.ResultList;
// do whatever with MyContactsResult
}
The above code doesn't exist, but this is what I have so far for the 'streamer' class (it obviously doesn't work, but you can see what I'm trying to achieve above):
public void DefineExpression(System.Linq.Expressions.Expression<System.Func<T, bool>> expression)
{
using (var db = new LINQDataContext())
{
ResultList = db.GetTable<T>().Where(expression);
}
}
How do I go about creating a method like 'DefineExpression' that will allow me to query a LINQ type dynamically?
Why not use the Dynamic LINQ provider, as mentioned by Scott Guthrie. I think that would give you everything you are looking for, because you can define the query as a string. Therefore, you can more easily build a string representation of your query, and execute on the fly.

Error in generic repository method for Entity Framework

I'm trying to create a generic method to use in my base class for my repositories and I'm having an issue. Here's the method...
public virtual T First(System.Linq.Expressions.Expression<Func<T, bool>> where, List<string> properties)
{
IQueryable<T> query = null;
if (where != null)
{
query = _context.CreateQuery<T>(String.Format("[{0}]", typeof(T).Name.ToString())).Where(where);
}
else
{
query = _context.CreateQuery<T>(String.Format("[{0}]", typeof(T).Name.ToString()));
}
foreach (string s in properties)
{
query = query.Include(s);
}
T _result = (T)query.First();
return _result;
}
When I run the code it gives me this error:
'Company' could not be resolved in
the current scope or context. Make
sure that all referenced variables are
in scope, that required schemas are
loaded, and that namespaces are
referenced correctly. Near escaped
identifier, line 1, column 1.
I have an idea on why it's doing this, I just don't know how to fix it. I think it's doing it because my ObjectContext doesn't know about the object "Company" but it does know "Companies". Any ideas on how to fix this??
The error happens on this line:
T _result = (T)query.First();
Thanks!
Dan, get the entity set name with something like the following:
string entitySetName = context.MetadataWorkspace
.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace)
.BaseEntitySets.Where(bes => bes.ElementType.Name == typeof(T).Name).First().Name;
string name = String.Format("{0}.{1}", context.DefaultContainerName, entitySetName);
query = context.CreateQuery<T>(name).Where(where);
Doing this will resolve the proper plural name for the query.
Using Yury's method would be the best option though.
EDIT By the way, you should return FirstOrDefault() instead of First() in case the query returns no entities (it will throw an InvalidOperationException).
Try to use
query = _context.CreateObjectSet<T>().Where(where);
instead of
query = _context.CreateQuery<T>(String.Format("[{0}]", typeof(T).Name.ToString())).Where(where);

Categories

Resources