Is it possible get the model from a string? - entity-framework

I'm trying to build a dynamic filter, where the user can search for columns in tables. To that I'm trying to use System.Linq.Dynamic.Core
var data = Db.MyTable1.Select($"new ({string.Join(", ", queryParams.Columns)})", "T", StringComparison.OrdinalIgnoreCase);
But now I also would like to get the table from the parameter string, something like
var query = Db.Get(queryParams.Table);
query = query.Select...
Working with EntityFrameworkCore 3.1, is there a way to achieve this?

You can override the Query method of DbContext.
First,create a custom Query method:
public static partial class CustomExtensions
{
public static IQueryable Query(this DbContext context, string entityName) =>
context.Query(context.Model.FindEntityType(entityName).ClrType);
static readonly MethodInfo SetMethod = typeof(DbContext).GetMethod(nameof(DbContext.Set));
public static IQueryable Query(this DbContext context, Type entityType) =>
(IQueryable)SetMethod.MakeGenericMethod(entityType).Invoke(context, null);
}
Then, you need to store the table names and types of all the tables involved in the Dictionary.
Dictionary<string, Type> TableTypeDictionary = new Dictionary<string, Type>()
{
{ "Teachers", typeof(Teachers) },//store the tables name and type.
{ "Students", typeof(Students) },
{ "Product", typeof(Product) }
//...
};
Last, use db to call Query method :
var query = Db.Query(TableTypeDictionary[queryParams.Table]).Select($"new ({string.Join(",", queryParams.Columns)})", "T", StringComparison.OrdinalIgnoreCase);
You can also bring "Namespace.MyTable" in the Query method. At this time, you need to rewrite the Query method to another writing method, please refer to this.

Related

C# Dynamic IEnumerable <class>

Anyone know how to pass the IEnumerable<xxxxx> into method. XXXXX is refer to any class.
I want to make it dynamic to accept any class. Example, <student>, <course>, <semester>, etc.
Currently it is fixed as Employee class.
public IEnumerable<Employee> GetJsonFile(string strFolder, string strFileName)
{
string strFile = Path.Combine(WebHostEnvironment.WebRootPath, strFolder, strFileName);
using (var jsonFileReader = File.OpenText(strFile))
{
return JsonSerializer.Deserialize<Employee[]>(jsonFileReader.ReadToEnd(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true});
}
}
Use Generics. IEnumerable<T> Where T is a class.
Generics Guide
Instead of using <Employee> use <T>
public IEnumerable<T> GetJsonFile<T>(string strFolder, string strFileName)
{
...return JsonSerializer.Deserialize<T[]>...
}
Then when you call the method you supply it with the type.
var x = GetJsonFile<student>(folder,file);
For more information lookup information on C# Generics.

Use EF Core to get a list of Longs

I have a stored procedure that returns me a list of IDs. (I then use this list of IDs as keys for objects.)
I am migrating this from .NET to .NET Core. In normal .NET I could use an extension library to get the numbers out like this:
var getOrderDetailIdsStoredProc = new GetOrderDetailIdsStoredProc()
{
NumberOfOrderDetailIdsNeeded = numberOfOrderDetailIdsNeeded
};
var orderDetailIds = contextProvider.Context.Database
.ExecuteStoredProcedure<long>(getOrderDetailIdsStoredProc);
But that library (EntityFrameworkExtras) is not working with EF Core (I found a version for EF Core, but it doesn't work.)
So I have been looking for other solutions:
DbContext.Database.ExecuteSqlCommand: Cannot return records, only output variables
DbSet.FromSQL: Can only be run on a DbSet<T> (basically it needs an entity type)
Right now, all I can think of is to make an entity called Number:
public class Number
{
public long Value;
}
public DbSet<Number> Numbers;
And then do something like this:
Numbers.FromSql("exec GenerateOrderDetailSequencedIds #numberNeeded", numberNeeded)
Aside from the fact that this is very ugly (making an entity out of a native type), I have no table to hook it up to, so I worry it will not work.
Is there any way in EF Core to run a stored procedure and get back a list of numbers?
NOTE: This worked, but was not compatable with BreezeJs (it could not deal with a DbQuery). See my other answer for what I ended up doing.
OrderDetailIdHolder.cs
public class OrderDetailIdHolder
{
public long NewId { get; set; }
}
MyEntitiesContext
public DbQuery<OrderDetailIdHolder> OrderDetailIdHolders { get; set; }
internal List<long> GetOrderDetailIds(int numberOfIdsNeeded)
{
var result = OrderDetailIdHolders.FromSql($"exec Sales.GenerateOrderDetailIds {numberOfIdsNeeded}").ToList();
return result.Select(x=>x.NewId).ToList();
}
This a bit extra complexity for just a list of longs. But it works.
It is important to note that the property (NewId in this case) must match what is returned from the sproc. Also, the type is not a DbSet. It is a DbQuery.
It is also important to note that this is only for EF Core 2.2. EF Core 3 has a different way to do this (Keyless Entity Types)
This is what I ended up using:
public static List<T> SqlQueryList<T>(this DatabaseFacade database, string query, params SqlParameter[] sqlParameters)
{
// TODO: Add a using statement here so we don't leak the connection's resources.
var conn = database.GetDbConnection();
conn.Open();
var command = conn.CreateCommand();
command.CommandText = query;
command.Parameters.AddRange(sqlParameters);
var reader = command.ExecuteReader();
List<T> result = new List<T>();
while (reader.Read())
{
T typedRow;
var row = reader.GetValue(0);
if (typeof(T).IsValueType)
{
typedRow = (T) row;
}
else
{
typedRow = (T)Convert.ChangeType(result, typeof(T));
}
result.Add(typedRow);
}
return result;
}
Called like this:
var numberOfOrderDetailIdsNeededParam = new SqlParameter
{
ParameterName = "#numberOfOrderDetailIdsNeeded",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.Input
};
numberOfOrderDetailIdsNeededParam.Value = numberOfOrderDetailIdsNeeded;
var result = contextProvider.Context.Database.SqlQueryList<long>($"exec Sales.GenerateOrderDetailIds #numberOfOrderDetailIdsNeeded", numberOfOrderDetailIdsNeededParam);
I did it this way because it was compatible with BreezeJs for .NET Core. Note that I only really tested this with Value types.

Unable to cast the type 'System.Int32' to type 'System.Object during EF Code First Orderby Function

I'm using Specification pattern in EF Code First. When I do order by operation, VS throw a new exception
The specification pattern is copy from eShopOnWeb
I just change a little bit, here is my change code:
public class Specification<T> : ISpecification<T>
{
public Expression<Func<T, object>> OrderBy { get; private set; }
public Specification(Expression<Func<T, bool>> criteria)
{
Criteria = criteria;
}
public Specification<T> OrderByFunc(Expression<Func<T, object>> orderByExpression)
{
OrderBy = orderByExpression;
return this;
}
}
Here is my invoke code, it's very pretty simple:
static void TestSpec()
{
var spec = new Specification<ExcelData>(x => x.RowIndex == 5)
.OrderByFunc(x => x.ColumnIndex);
using (var dbContext = new TechDbContext())
{
var top10Data = dbContext.ExcelData.Take(10).ToList();
var listExcel = dbContext.ApplySpecification(spec).ToList();
Console.WriteLine();
}
}
If I comment OrderByFunc, then everything is fine to me. no error throw from vs.
I had try many times search the error message in google, but none of answer is my case.
So I have to ask a question in here.
When I debug OrderBy property in SpecificationEvaluator.cs, I found there is a Convert method.
So I know the error is about cast error, but how do I fix this cast type error?
Please help me!
The solution is to create new lambda expression with cast (Convert) removed, and then use it to call the Queryable class OrderBy / OrderByDescending method either dynamically (using DLR dispatch or reflection) or by emitting Expression.Call to it.
For the first part, add the following helper method to the SpecificationEvaluator class:
static LambdaExpression RemoveConvert(LambdaExpression source)
{
var body = source.Body;
while (body.NodeType == ExpressionType.Convert)
body = ((UnaryExpression)body).Operand;
return Expression.Lambda(body, source.Parameters);
}
Then replace the code
query = query.OrderBy(specification.OrderBy);
with either
query = Queryable.OrderBy((dynamic)query, (dynamic)RemoveConvert(specification.OrderBy));
or
var keySelector = RemoveConvert(specification.OrderBy);
query = query.Provider.CreateQuery<T>(Expression.Call(
typeof(Queryable), nameof(Queryable.OrderBy),
new[] { typeof(T), keySelector.ReturnType },
query.Expression, keySelector));
Do similar for the specification.OrderByDescending.

Execute SP using Entity Framework and .net Core

I created a web api project using .net core and entity framework.
This uses a stored procedure which returns back most of the properties of a database table defined by entity framework.
The entity framwrok does not bring back all the columns of the table. And I get an error when I call the api complaining it cannot find the missing columns when I execute the stored procedure using ,
_context.Set<TableFromSql>().FromSql("execute dbo.spr_GetValue").ToList();
I created another model class which defines the properties brought back from the SP( called NewClass).
_context.Set<NewClass>().FromSql("execute dbo.spr_GetValue").ToList();
This works, but just wanted to check if there is a convention that the SP should only return the model classes from the database.
The SQL query must return data for all properties of the entity or query type
For this limitation, it is caused when mapping the sql query result to Model. It loop through the properties in model and try to retrive the values from query result. If the model properties are not exist in query result, it will throw error.
If you want to return required columns instead of all columns, one options is to define the returned model by Query.
For your demo code, you may define this in OnModelCreating.
builder.Query<TableFromSql>();
Note, for this way, you need to make sure all properties in TableFromSql exist in execute dbo.spr_GetValue.
For another way, you may implement your own FromSql which will add condition to check whether the properties are exist in query result.
public static class DbContextExtensions
{
public static List<T> RawSqlQuery<T>(this DbContext context,string query)
{
using (var command = context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = query;
command.CommandType = CommandType.Text;
context.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
var entities = new List<T>();
return DataReaderMapToList<T>(result);
}
}
}
public static List<T> DataReaderMapToList<T>(IDataReader dr)
{
List<T> list = new List<T>();
T obj = default(T);
while (dr.Read())
{
obj = Activator.CreateInstance<T>();
foreach (PropertyInfo prop in obj.GetType().GetProperties())
{
if (ColumnExists(dr, prop.Name))
{
if (!object.Equals(dr[prop.Name], DBNull.Value))
{
prop.SetValue(obj, dr[prop.Name], null);
}
}
}
list.Add(obj);
}
return list;
}
public static bool ColumnExists(IDataReader reader, string columnName)
{
return reader.GetSchemaTable()
.Rows
.OfType<DataRow>()
.Any(row => row["ColumnName"].ToString() == columnName);
}
}
Use above code like :
var result = _context.RawSqlQuery<ToDoItemVM>("execute [dbo].[get_TodoItem]");

Entity Framework 6 Generic Eager Loading Query Method

I am writing a generic querying method in Entity Framework 6, based off of this helpful article. Here's how it looks:
public static T QueryEagerLoad<T>(Expression<Func<T, bool>> match) where T : class
{
using (var databaseContext = new ClearspanDatabaseContext())
{
databaseContext.Configuration.LazyLoadingEnabled = false;
T retrievedObject = databaseContext.Set<T>().SingleOrDefault(match);
return retrievedObject;
}
}
I'm attempting to eagerly load any related entities, so I include disable to configuration variable LazyLoadingEnabled. While it loads the object, it does not load the related entities, per my view in the debugger. Why would this be? Am I missing something? I should note that I'm using Npgsql. Thanks in advance.
See Mikael Östberg's answer to this question. To use a generic method for querying with eager loading, it seems necessary to inject the includes. Here's how the generic method shaped up:
public static T Query<T>(Expression<Func<T, bool>> match, List<Expression<Func<T, object>>> includes) where T : class
{
using (var databaseContext = new ClearspanDatabaseContext())
{
var dataSet = databaseContext.Set<T>(); // Get the relevant DataSet
T retrievedObject = includes.Aggregate( // Eagerly load the passed navigation properties
dataSet.AsQueryable(),
(current, include) => current.Include(include)
).SingleOrDefault(match); // Find exactly one or zero matches
return retrievedObject;
}
}
And an example of a call that injects the properties to eagerly load (the includes parameter in the generic method above):
public static Lumber GetLumber(int databaseId)
{
Expression<Func<Lumber, object>> lengthProperty = (lumber => lumber.Length);
Expression<Func<Lumber, object>> thicknessProperty = (lumber => lumber.Thickness);
Expression<Func<Lumber, object>> widthProperty = (lumber => lumber.Width);
List<Expression<Func<Lumber, object>>> lumberNaviationProperties = new List<Expression<Func<Lumber, object>>>() { lengthProperty, thicknessProperty, widthProperty };
Lumber retrievedLumber = DatabaseOperations.Query<Lumber>((lumber => lumber.DatabaseId == databaseId), lumberNaviationProperties);
return retrievedLumber;
}