Dynamic EF Query - entity-framework

I am thinking of designing a business rule engine which basically generates an EF query from a set of string values stored in a database.
For e.g. I will store the connection string, table name, the where condition predicate, and select predicate as string fields in a db and would like to construct the EF query dynamically. For e.g.
var db = new DbContext(“connectionstring”);
var wherePredicate = Expression.FromString(“p => p.StartDate > new DateTime(2014,5,1))
var selectPredicate = Expression.FromString(“p => p”)
var results = db.Set(“Projects”).Where(wherepredicate).Select(selectPredicate)
For constructing the predicates I can use DynamicExpression or Dynamic LINQ library.
However how do I access db.Set(“Projects”) where Projects is the entity name and apply the where and select predicates? (or something like db[“Projects”].Where().Select).
I tried the non-generic version of the DbContext.Set(Type entityttype) method, however couldn’t figure out how to apply Where and Select predicates to the returned object.
I am trying to avoid generating SQL queries and instead rely on dynamically generated EF code.

This doesn't make much sense. You can create method that will work on string instead of generic type using reflection, but you'd have to return DbSet not DBSet<T>. And on that one you cannot execute LINQ's methods (basically), because there's no type (during compilation). Of course you can do it all the way using reflection, but then, why??? You're loosing 90% of what O/R mapper does for you.

Related

How can I get the SqlDbType or DbType from IModel in EntityFramework Core 2?

My use case is:
I have a graph of objects from many entities in Entity Framework Core 2 (EFC 2). In EFC 2, the SaveChanges Operation is very slow. The reason appears to be the limit in number of parameters that Sql Server can receieve per query. As Sql Server receive 2100 parameters per query, saving hundreds of thousands of registers must cause many roundtrips, with much latency implied. See issue 2484 for more information. My current solution is to generate a SqlCommand with a query with Table Valued Parameters (TVP). The plan is to use only one query with a TVP for each table and operation (insert, update and delete), using only one roundtrip for save all the changes. EFC cannot do that actually.
In theory, i'm almost finish this, but i have a problem. To use TVP, i must get the SqlDbType for each column from my Table Type. The Table Types are generated using the metadata in the IModel from EFC. But, i cant get the SqlDbType. Simplified, I tried with:
var typeMapper = new SqlServerTypeMapper(new RelationalTypeMapperDependencies());
var entityType = context.Model.GetEntityTypes().First();
var prop = entityType.GetProperties().First();
var mapping = typeMapper.GetMapping(prop);
var dbType = mapping.DbType;
Having dbType, the plan is get the SqlDbType from dbType using a Dictionary. The problem is dbType is getting null.
I'm searched in the api, and i can get the way to extract a SqlType from IModel. Is this possible?

Asp.Net Core Automapper LINQ expression

I'm currently working with ASP.NET Core and I want to use Automapper to map a Linq Expression. The mapping statement is:
var targetConditions = _mapper.Map<Expression<Func<Entity, bool>>>(filter);
where filter is a formal parameter in the form:
(Expression<Func<EntityDTO, bool>> filter
In the mapping profile I have the following map created:
CreateMap<Expression<Func<EntityDTO, bool>>, Expression<Func<Entity, bool>>>();
I'm using a generic repository pattern with EF. I want to get a list of DTOs filtered by, of course, DTO's fields from my controller. I then need to convert from DTO filter to entities filter in the Business Layer before doing any query using Linq for EF.
Even though the expression does get coverted from EntityDTO to Entity, the parameters in the lambda expressions inside don't, raising all sorts of errors when I further use it with EF. Any idea how can this be done?
Try this:
IQueryable<Customer> query = repository.getCustomers(); // entities query
query.ProjectTo<CustomerDto>().Where(dtoFilterExpression)

How to use a SQL query result as an object in Entity Framework?

I'm creating a data analysis process in c# using EF (code first). The data model involves about a dozen interrelated classes. Most of these directly correspond with tables in an existing database, but a couple key read-only object types are really the product of complex, multi-step SQL queries. I can't create a view or stored proc for the query.
I'd like to be able to use something like DbSet.SqlQuery() to load those query results into EF objects, and still leverage EF's ORM features to relate them with the rest of the object graph.
I think what I want is a way to override EF's SQL SELECT code when the DbSet tries to populate that query-based object from the database. Is this possible? Is there a better alternative approach?
Could you perhaps write your arbitrary query into a stored procedure? You could then use Entity framework to map that to a function.
I've found a way that's working for me, but it's hacky. (Is there a better way?)
Let's say Foo is a poco class that corresponds with an expensive query result that I want to explicitly write in SQL and use as an object in a read-only EF object graph. Foo includes foreign keys and navigation properties. My dbContext has a DbSet called Foos.
// Pre-req: prevent EF from trying to sync model with db, since Foos doesn't map to a db table.
public FooDbContext()
{
Database.SetInitializer<FooDbContext>(null);
}
// load the query data with SqlQuery() and Attach()
var foos = db.Foos.SqlQuery("<hand written SQL SELECT>", parameters);
foreach(var foo in foos) db.Foos.Attach(foo);
// Subsequently reference the data with .Local to prevent db hits (which would fail, since no Foo table in db.)
var result = db.Foos.Local.Select(...);
I do have to explicitly load related objects, but then all the relationship association happens automatically. For example let's say Bar has a 1-to-many with Foo, Foo has a BarId property (and a Bar navigation property) and maybe the Bar class has an observable collection of Foos. If Bars is stored as a db lookup table, I could just do db.Bars.Load(), or something more filtered like:
var barIds = db.Foos.Local.Select(f => f.BarId).Distinct().ToArray();
db.Bars.Where(b => barIds.Contains(b.barId)).Load();
Then I can use Foo.Bar or Bar.Foos and everything works.

Convert String to Int in LINQ to Entities

I am trying to duplicate the following SQL statement as a LINQ to Entities query (where "PRODUCTS" is the table mapped to the entity) ... NOTE IQueryable ... most of what I have seen posted as solutions convert either the search parameters, or the dump the results into an IEnumerable and then proceed to convert from there. I am dealing with 100's of millions of records and cannot afford to load 200 million records into memory, only to have to filter through them again. I would like, if possible to do this in a single query to the databse.
select *
from PRODUCTS
where
MODEL_CODE = '65' and
CAST(SERIAL_NUMBER as int) > 927000 and
CAST(SERIAL_NUMBER as int) < 928000
I have tried the following ...
int startSN, endSN;
startSN = 9500
endSN = 9500
if (!int.TryParse(startSerialNumber, out startSN))
throw new InvalidCastException("The start serial number was not a valid value");
if (!int.TryParse(endSerialNumber, out endSN))
throw new InvalidCastException("The end serial number was not a valid value");
IQueryable<PRODUCT> resultList = base.Context.PRODUCTS.Where(b =>
(Convert.ToInt32(b.SERIAL_NUMBER) > startSN) &&
(Convert.ToInt32(b.SERIAL_NUMBER) < endSN)).AsQueryable();
I have tried a couple of other version of things similiar to this with no luck. I have looked at the following posts also with no luck.
Convert string to int in an Entity Framework linq query and handling the parsing exception - the solution converts query to a list before converting the entity properties.
Convert string to Int in LINQ to Entities ? -
This problem was just with converting the parameters which can be easily done outside the LINQ to Entities statement. I am already doing this for the parameters.
LINQ to Entities StringConvert(double)' cannot be translated to convert int to string - This problem is actually the reverse of mine, trying to convert an int to a string. 1) SqlFunctions does not provide a function for converting TO an int. 2) Ultimately the solution is to, again convert to an IEnumerable before converting/casting the values.
Anybody got any other ideas? I am little stumped on this one!
Thank you,
G
If you don't use code-first, but an EDMX based approach model defined functions are probably the best solution: Convert String to Int in EF 4.0
Alternatively you can use...
base.Context.PRODUCTS.SqlQuery(string sql, params object[] parameters)
...and then pass in the raw SQL statement from your question.
DbSet<T>.SqlQuery(...) returns a DbSqlQuery<T> as result. It is important to keep in mind that this type does not implement IQueryable<T>, but only IEnumerable<T>. Its signature is:
public class DbSqlQuery<TEntity> : IEnumerable<TEntity>, IEnumerable, IListSource
where TEntity : class
So you can extend this result with further LINQ methods, but it is only LINQ to Objects that will be executed in memory with the returned result set from the SQL query. You can not extend it with LINQ to Entities that would be executed in the database. Hence, adding .Where filters to DbSqlQuery<T> does not have any influence on the database query and the set of data that is loaded from the DB into memory.
That's actually not surprising as it would mean otherwise that a partial expression tree (from a Where method) had to be translated into SQL and then merged into a hand-written SQL statement so that a correct new composed SQL statement results and could be sent to the database. Sounds like a pretty hard task to me.

Attempting to use EF/Linq to Entities for dynamic querying and CRUD operations

(as advised re-posting this question here... originally posted in msdn forum)
I am striving to write a "generic" routine for some simple CRUD operations using EF/Linq to Entities. I'm working in ASP.NET (C# or VB).
I have looked at:
Getting a reference to a dynamically selected table with "GetObjectByKey" (But I don't want anything from cache. I want data from database. Seems like not what this function is intended for).
CRM Dynamic Entities (here you can pass a tablename string to query) looked like the approach I am looking for but I don't get the idea that this CRM effort is necessarily staying current (?) and/or has much assurance for the future??
I looked at various ways of drilling thru Namespaces/Objects to get to where I could pass a TableName parameter into the oft used query syntax var query = (from c in context.C_Contacts select c); (for example) where somehow I could swap out the "C_Contacts" TEntity depending on which table I want to work with. But not finding a way to do this ??
Slightly over-simplyfing, I just want to be able to pass a tablename parameter and in some cases some associated fieldnames and values (perhaps in a generic object?) to my routine and then let that routine dynamically plug into LINQ to Entity data context/model and do some standard "select all" operations for parameter table or do a delete to parameter table based on a generic record id. I'm trying to avoid calling the various different automatically generated L2E methods based on tablename etc...instead just trying to drill into the data context and ultimately the L2E query syntax for dynamically passed table/field names.
Has anyone found any successful/efficient approaches for doing this? Any ideas, links, examples?
The DbContext object has a generic Set() method. This will give you
from c in context.Set<Contact>() select c
Here's method when starting from a string:
public void Test()
{
dynamic entity = null;
Type type = Type.GetType("Contract");
entity = Activator.CreateInstance(type);
ProcessType(entity);
}
public void ProcessType<TEntity>(TEntity instance)
where TEntity : class
{
var result =
from item in this.Set<TEntity>()
select item;
//do stuff with the result
//passing back to the caller can get more complicated
//but passing it on will be fine ...
}