Dynamic results using dapper in mvc - c#

I am trying the below way to return the dynamic results using dapper and stored procedure. Am I doing it in correct way?
using (IDbConnection dbConnection = Connection)
{
dbConnection.Open();
var result = dbConnection.Query<dynamic>("LMSSP_GetSelectedTableData",
new
{
TableName = TableName,
LangaugeID = AppTenant.SelectedLanguageID,
UserID = AppTenant.UserID
}, commandType: CommandType.StoredProcedure).ToList();
if (result != null)
{
// Added just for checking the data
foreach (var item in (IDictionary<string, object>)result.FirstOrDefault())
{
string key = item.Key;
string value = item.Value.ToString();
}
}
}
What my stored procedure do is, I will pass any table name and based on that it will return the results/records.So, obviously my number of records, columns will be varied as per the table name passed.
To achieve this I have used dynamic keyword along with dapper.
So my question is how can I pass this data to view as a model and render the controls on the view as per the properties/column data type. Can I get the data type of column OR PropertyInfo?
But, when dapper retrieves the records from database it returns as dapper row type?

Using same SP to fetch data from different table would be confusing (not good design). However to solve your problem technically, you can create model having list of control information. Example of control information
public class ControlInformation
{
public string Name { get; set; }
public dynamic Value { get; set; }
public string ControlType { get; set; }
// Applicable for drop down or multi select
public string AllValues { get; set; }
}
Model will have list of ControlInformations
public List<ControlInformation> ControlInformations { get; set; }
View will render the controls (partial view based on control type) Ex: very basic case to render different view for int and another view for rest. I have 2 partial views "IntCtrl" and "StringCtrl".
#foreach (var item in Model.ControlInformations)
{
if (#item.ControlType == "System.Int32")
{
Html.RenderPartial("IntCtrl", item);
}
else
{
Html.RenderPartial("StringCtrl", item);
}
}
Hope this help.

Here we are calling method which returns Datatable :
public DataTable GetMTDReport(bool isDepot, int userId)
{
using (IDbConnection _connection = DapperConnection)
{
var parameters = new DynamicParameters();
parameters.Add("#IsDepot", isDepot);
parameters.Add("#userId", userId);
var res = this.ExecuteSP<dynamic>(SPNames.SSP_MTDReport, parameters);
return ToDataTable(res);
}
}
In this we can call stored procedures by calling our custom method "ExecuteSP" :
public virtual IEnumerable<TEntity> ExecuteSP<TEntity>(string spName, object parameters = null)
{
using (IDbConnection _connection = DapperConnection)
{
_connection.Open();
return _connection.Query<TEntity>(spName, parameters, commandTimeout:0 , commandType: CommandType.StoredProcedure);
}
}
and here is "DapperConnection" method to connect the database:
You can give connection string with key ["MainConnection"]
public class DataConnection
{
public IDbConnection DapperConnection
{
get
{
return new SqlConnection(ConfigurationManager.ConnectionStrings["MainConnection"].ToString());
}
}
}
And at last we call "ToDataTable" method to change our response in datatable . We will receive response in DapperRow from the database because we passsed dynamic type in stored procedure.
public DataTable ToDataTable(IEnumerable<dynamic> items)
{
if (items == null) return null;
var data = items.ToArray();
if (data.Length == 0) return null;
var dt = new DataTable();
foreach (var pair in ((IDictionary<string, object>)data[0]))
{
dt.Columns.Add(pair.Key, (pair.Value ?? string.Empty).GetType());
}
foreach (var d in data)
{
dt.Rows.Add(((IDictionary<string, object>)d).Values.ToArray());
}
return dt;
}

Related

How to run raw SQL query by Entity Framework Core in Blazor [duplicate]

With Entity Framework Core removing dbData.Database.SqlQuery<SomeModel> I can't find a solution to build a raw SQL Query for my full-text search query that will return the tables data and also the rank.
The only method I've seen to build a raw SQL query in Entity Framework Core is via dbData.Product.FromSql("SQL SCRIPT"); which isn't useful as I have no DbSet that will map the rank I return in the query.
Any Ideas???
If you're using EF Core 3.0 or newer
You need to use keyless entity types, previously known as query types:
This feature was added in EF Core 2.1 under the name of query types.
In EF Core 3.0 the concept was renamed to keyless entity types. The
[Keyless] Data Annotation became available in EFCore 5.0.
To use them you need to first mark your class SomeModel with [Keyless] data annotation or through fluent configuration with .HasNoKey() method call like below:
public DbSet<SomeModel> SomeModels { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SomeModel>().HasNoKey();
}
After that configuration, you can use one of the methods explained here to execute your SQL query. For example you can use this one:
var result = context.SomeModels.FromSqlRaw("SQL SCRIPT").ToList();
var result = await context.SomeModels.FromSql("SQL_SCRIPT").ToListAsync();
If you're using EF Core 2.1
If you're using EF Core 2.1 Release Candidate 1 available since 7 may 2018, you can take advantage of the proposed new feature which is query types:
In addition to entity types, an EF Core model can contain query types,
which can be used to carry out database queries against data that
isn't mapped to entity types.
When to use query type?
Serving as the return type for ad hoc FromSql() queries.
Mapping to database views.
Mapping to tables that do not have a primary key defined.
Mapping to queries defined in the model.
So you no longer need to do all the hacks or workarounds proposed as answers to your question. Just follow these steps:
First you defined a new property of type DbQuery<T> where T is the type of the class that will carry the column values of your SQL query. So in your DbContext you'll have this:
public DbQuery<SomeModel> SomeModels { get; set; }
Secondly use FromSql method like you do with DbSet<T>:
var result = context.SomeModels.FromSql("SQL_SCRIPT").ToList();
var result = await context.SomeModels.FromSql("SQL_SCRIPT").ToListAsync();
Also note that DbContexts are partial classes, so you can create one or more separate files to organize your 'raw SQL DbQuery' definitions as best suits you.
Building on the other answers I've written this helper that accomplishes the task, including example usage:
public static class Helper
{
public static List<T> RawSqlQuery<T>(string query, Func<DbDataReader, T> map)
{
using (var context = new DbContext())
{
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>();
while (result.Read())
{
entities.Add(map(result));
}
return entities;
}
}
}
}
Usage:
public class TopUser
{
public string Name { get; set; }
public int Count { get; set; }
}
var result = Helper.RawSqlQuery(
"SELECT TOP 10 Name, COUNT(*) FROM Users U"
+ " INNER JOIN Signups S ON U.UserId = S.UserId"
+ " GROUP BY U.Name ORDER BY COUNT(*) DESC",
x => new TopUser { Name = (string)x[0], Count = (int)x[1] });
result.ForEach(x => Console.WriteLine($"{x.Name,-25}{x.Count}"));
I plan to get rid of it as soon as built-in support is added. According to a statement by Arthur Vickers from the EF Core team it is a high priority for post 2.0. The issue is being tracked here.
In EF Core you no longer can execute "free" raw sql. You are required to define a POCO class and a DbSet for that class.
In your case you will need to define Rank:
var ranks = DbContext.Ranks
.FromSql("SQL_SCRIPT OR STORED_PROCEDURE #p0,#p1,...etc", parameters)
.AsNoTracking().ToList();
As it will be surely readonly it will be useful to include the .AsNoTracking() call.
EDIT - Breaking change in EF Core 3.0:
DbQuery() is now obsolete, instead DbSet() should be used (again). If you have a keyless entity, i.e. it don't require primary key, you can use HasNoKey() method:
ModelBuilder.Entity<SomeModel>().HasNoKey()
More information can be found here
For now, until there is something new from EFCore I would used a command
and map it manually
using (var command = this.DbContext.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "SELECT ... WHERE ...> #p1)";
command.CommandType = CommandType.Text;
var parameter = new SqlParameter("#p1",...);
command.Parameters.Add(parameter);
this.DbContext.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
while (result.Read())
{
.... // Map to your entity
}
}
}
Try to SqlParameter to avoid Sql Injection.
dbData.Product.FromSql("SQL SCRIPT");
FromSql doesn't work with full query. Example if you want to include a WHERE clause it will be ignored.
Some Links:
Executing Raw SQL Queries using Entity Framework Core
Raw SQL Queries
You can execute raw sql in EF Core - Add this class to your project.
This will allow you to execute raw SQL and get the raw results without having to define a POCO and a DBSet.
See https://github.com/aspnet/EntityFramework/issues/1862#issuecomment-220787464 for original example.
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.EntityFrameworkCore
{
public static class RDFacadeExtensions
{
public static RelationalDataReader ExecuteSqlQuery(this DatabaseFacade databaseFacade, string sql, params object[] parameters)
{
var concurrencyDetector = databaseFacade.GetService<IConcurrencyDetector>();
using (concurrencyDetector.EnterCriticalSection())
{
var rawSqlCommand = databaseFacade
.GetService<IRawSqlCommandBuilder>()
.Build(sql, parameters);
return rawSqlCommand
.RelationalCommand
.ExecuteReader(
databaseFacade.GetService<IRelationalConnection>(),
parameterValues: rawSqlCommand.ParameterValues);
}
}
public static async Task<RelationalDataReader> ExecuteSqlQueryAsync(this DatabaseFacade databaseFacade,
string sql,
CancellationToken cancellationToken = default(CancellationToken),
params object[] parameters)
{
var concurrencyDetector = databaseFacade.GetService<IConcurrencyDetector>();
using (concurrencyDetector.EnterCriticalSection())
{
var rawSqlCommand = databaseFacade
.GetService<IRawSqlCommandBuilder>()
.Build(sql, parameters);
return await rawSqlCommand
.RelationalCommand
.ExecuteReaderAsync(
databaseFacade.GetService<IRelationalConnection>(),
parameterValues: rawSqlCommand.ParameterValues,
cancellationToken: cancellationToken);
}
}
}
}
Here's an example of how to use it:
// Execute a query.
using(var dr = await db.Database.ExecuteSqlQueryAsync("SELECT ID, Credits, LoginDate FROM SamplePlayer WHERE " +
"Name IN ('Electro', 'Nitro')"))
{
// Output rows.
var reader = dr.DbDataReader;
while (reader.Read())
{
Console.Write("{0}\t{1}\t{2} \n", reader[0], reader[1], reader[2]);
}
}
You can use this:
public static class SqlQueryExtensions
{
public static IList<T> SqlQuery<T>(this DbContext db, string sql, params object[] parameters) where T : class
{
using (var db2 = new ContextForQueryType<T>(db.Database.GetDbConnection()))
{
// share the current database transaction, if one exists
var transaction = db.Database.CurrentTransaction;
if (transaction != null)
db2.Database.UseTransaction(transaction.GetDbTransaction());
return db2.Set<T>().FromSqlRaw(sql, parameters).ToList();
}
}
private class ContextForQueryType<T> : DbContext where T : class
{
private readonly DbConnection connection;
public ContextForQueryType(DbConnection connection)
{
this.connection = connection;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connection, options => options.EnableRetryOnFailure());
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<T>().HasNoKey();
base.OnModelCreating(modelBuilder);
}
}
}
And the usage:
using (var db = new Db())
{
var results = db.SqlQuery<ArbitraryType>("select 1 id, 'joe' name");
//or with an anonymous type like this
var results2 = db.SqlQuery(() => new { id =1, name=""},"select 1 id, 'joe' name");
}
try this: (create extension method)
public static List<T> ExecuteQuery<T>(this dbContext db, string query) where T : class, new()
{
using (var command = db.Database.GetDbConnection().CreateCommand())
{
command.CommandText = query;
command.CommandType = CommandType.Text;
db.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
var lst = new List<T>();
var lstColumns = new T().GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
while (reader.Read())
{
var newObject = new T();
for (var i = 0; i < reader.FieldCount; i++)
{
var name = reader.GetName(i);
PropertyInfo prop = lstColumns.FirstOrDefault(a => a.Name.ToLower().Equals(name.ToLower()));
if (prop == null)
{
continue;
}
var val = reader.IsDBNull(i) ? null : reader[i];
prop.SetValue(newObject, val, null);
}
lst.Add(newObject);
}
return lst;
}
}
}
Usage:
var db = new dbContext();
string query = #"select ID , Name from People where ... ";
var lst = db.ExecuteQuery<PeopleView>(query);
my model: (not in DbSet):
public class PeopleView
{
public int ID { get; set; }
public string Name { get; set; }
}
tested in .netCore 2.2 and 3.0.
Note: this solution has the slow performance
Add Nuget package - Microsoft.EntityFrameworkCore.Relational
using Microsoft.EntityFrameworkCore;
...
await YourContext.Database.ExecuteSqlCommandAsync("... #p0, #p1", param1, param2 ..)
This will return the row numbers as an int
See - https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.executesqlcommand?view=efcore-3.0
In Core 2.1 you can do something like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query<Ranks>();
}
and then define you SQL Procedure, like:
public async Task<List<Ranks>> GetRanks(string value1, Nullable<decimal> value2)
{
SqlParameter value1Input = new SqlParameter("#Param1", value1?? (object)DBNull.Value);
SqlParameter value2Input = new SqlParameter("#Param2", value2?? (object)DBNull.Value);
List<Ranks> getRanks = await this.Query<Ranks>().FromSql("STORED_PROCEDURE #Param1, #Param2", value1Input, value2Input).ToListAsync();
return getRanks;
}
This way Ranks model will not be created in your DB.
Now in your controller/action you can call:
List<Ranks> gettingRanks = _DbContext.GetRanks(value1,value2).Result.ToListAsync();
This way you can call Raw SQL Procedures.
I used Dapper to bypass this constraint of Entity framework Core.
IDbConnection.Query
is working with either sql query or stored procedure with multiple parameters.
By the way it's a bit faster (see benchmark tests )
Dapper is easy to learn. It took 15 minutes to write and run stored procedure with parameters. Anyway you may use both EF and Dapper. Below is an example:
public class PodborsByParametersService
{
string _connectionString = null;
public PodborsByParametersService(string connStr)
{
this._connectionString = connStr;
}
public IList<TyreSearchResult> GetTyres(TyresPodborView pb,bool isPartner,string partnerId ,int pointId)
{
string sqltext "spGetTyresPartnerToClient";
var p = new DynamicParameters();
p.Add("#PartnerID", partnerId);
p.Add("#PartnerPointID", pointId);
using (IDbConnection db = new SqlConnection(_connectionString))
{
return db.Query<TyreSearchResult>(sqltext, p,null,true,null,CommandType.StoredProcedure).ToList();
}
}
}
I found the package EntityFrameworkCore.RawSQLExtensions on github. To use it, add the nuget package.
<PackageReference Include="EntityFrameworkCore.RawSQLExtensions" Version="1.2.0" />
The library is not documented but below is my using of it with .NET 6 + EF Core 6 + Npgsql 6
public class DbResult
{
public string Name { get; set; }
public int Age { get; set; }
}
using EntityFrameworkCore.RawSQLExtensions.Extensions;
var results = await context.Database
.SqlQuery<DbResult>(
#"select name, age from ""users"" where age > #Age",
new NpgsqlParameter("#Age", 15))
.ToListAsync();
Not directly targeting the OP's scenario, but since I have been struggling with this, I'd like to drop these ex. methods that make it easier to execute raw SQL with the DbContext:
public static class DbContextCommandExtensions
{
public static async Task<int> ExecuteNonQueryAsync(this DbContext context, string rawSql,
params object[] parameters)
{
var conn = context.Database.GetDbConnection();
using (var command = conn.CreateCommand())
{
command.CommandText = rawSql;
if (parameters != null)
foreach (var p in parameters)
command.Parameters.Add(p);
await conn.OpenAsync();
return await command.ExecuteNonQueryAsync();
}
}
public static async Task<T> ExecuteScalarAsync<T>(this DbContext context, string rawSql,
params object[] parameters)
{
var conn = context.Database.GetDbConnection();
using (var command = conn.CreateCommand())
{
command.CommandText = rawSql;
if (parameters != null)
foreach (var p in parameters)
command.Parameters.Add(p);
await conn.OpenAsync();
return (T)await command.ExecuteScalarAsync();
}
}
}
My case used stored procedure instead of raw SQL
Created a class
Public class School
{
[Key]
public Guid SchoolId { get; set; }
public string Name { get; set; }
public string Branch { get; set; }
public int NumberOfStudents { get; set; }
}
Added below on my DbContext class
public DbSet<School> SP_Schools { get; set; }
To execute the stored procedure:
var MySchools = _db.SP_Schools.FromSqlRaw("GetSchools #schoolId, #page, #size ",
new SqlParameter("schoolId", schoolId),
new SqlParameter("page", page),
new SqlParameter("size", size)))
.IgnoreQueryFilters();
I updated extension method from #AminRostami to return IAsyncEnumerable (so LINQ filtering can be applied) and it's mapping Model Column name of records returned from DB to models (Tested with EF Core 5):
Extension itself:
public static class QueryHelper
{
private static string GetColumnName(this MemberInfo info)
{
List<ColumnAttribute> list = info.GetCustomAttributes<ColumnAttribute>().ToList();
return list.Count > 0 ? list.Single().Name : info.Name;
}
/// <summary>
/// Executes raw query with parameters and maps returned values to column property names of Model provided.
/// Not all properties are required to be present in model (if not present - null)
/// </summary>
public static async IAsyncEnumerable<T> ExecuteQuery<T>(
[NotNull] this DbContext db,
[NotNull] string query,
[NotNull] params SqlParameter[] parameters)
where T : class, new()
{
await using DbCommand command = db.Database.GetDbConnection().CreateCommand();
command.CommandText = query;
command.CommandType = CommandType.Text;
if (parameters != null)
{
foreach (SqlParameter parameter in parameters)
{
command.Parameters.Add(parameter);
}
}
await db.Database.OpenConnectionAsync();
await using DbDataReader reader = await command.ExecuteReaderAsync();
List<PropertyInfo> lstColumns = new T().GetType()
.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
while (await reader.ReadAsync())
{
T newObject = new();
for (int i = 0; i < reader.FieldCount; i++)
{
string name = reader.GetName(i);
PropertyInfo prop = lstColumns.FirstOrDefault(a => a.GetColumnName().Equals(name));
if (prop == null)
{
continue;
}
object val = await reader.IsDBNullAsync(i) ? null : reader[i];
prop.SetValue(newObject, val, null);
}
yield return newObject;
}
}
}
Model used (note that Column names are different than actual property names):
public class School
{
[Key] [Column("SCHOOL_ID")] public int SchoolId { get; set; }
[Column("CLOSE_DATE", TypeName = "datetime")]
public DateTime? CloseDate { get; set; }
[Column("SCHOOL_ACTIVE")] public bool? SchoolActive { get; set; }
}
Actual usage:
public async Task<School> ActivateSchool(int schoolId)
{
// note that we're intentionally not returning "SCHOOL_ACTIVE" with select statement
// this might be because of certain IF condition where we return some other data
return await _context.ExecuteQuery<School>(
"UPDATE SCHOOL SET SCHOOL_ACTIVE = 1 WHERE SCHOOL_ID = #SchoolId; SELECT SCHOOL_ID, CLOSE_DATE FROM SCHOOL",
new SqlParameter("#SchoolId", schoolId)
).SingleAsync();
}
Done this for Entity Framework Core 5, need to install
Microsoft.EntityFrameworkCore.Relational
The helper extension methods
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
public static class EfHelper
{
public static DbTransaction GetDbTransaction(this IDbContextTransaction source)
{
return (source as IInfrastructure<DbTransaction>).Instance;
}
private class PropertyMapp
{
public string Name { get; set; }
public Type Type { get; set; }
public bool IsSame(PropertyMapp mapp)
{
if (mapp == null)
{
return false;
}
bool same = mapp.Name == Name && mapp.Type == Type;
return same;
}
}
public static IEnumerable<T> FromSqlQuery<T>(this DbContext context, string query, params object[] parameters) where T : new()
{
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;
List<PropertyMapp> entityFields = (from PropertyInfo aProp in typeof(T).GetProperties(flags)
select new PropertyMapp
{
Name = aProp.Name,
Type = Nullable.GetUnderlyingType(aProp.PropertyType) ?? aProp.PropertyType
}).ToList();
List<PropertyMapp> dbDataReaderFields = new List<PropertyMapp>();
List<PropertyMapp> commonFields = null;
using (var command = context.Database.GetDbConnection().CreateCommand())
{
if (command.Connection.State != ConnectionState.Open)
{
command.Connection.Open();
}
var currentTransaction = context.Database.CurrentTransaction;
if (currentTransaction != null)
{
command.Transaction = currentTransaction.GetDbTransaction();
}
command.CommandText = query;
if (parameters.Any())
{
command.Parameters.AddRange(parameters);
}
using (var result = command.ExecuteReader())
{
while (result.Read())
{
if (commonFields == null)
{
for (int i = 0; i < result.FieldCount; i++)
{
dbDataReaderFields.Add(new PropertyMapp { Name = result.GetName(i), Type = result.GetFieldType(i) });
}
commonFields = entityFields.Where(x => dbDataReaderFields.Any(d => d.IsSame(x))).Select(x => x).ToList();
}
var entity = new T();
foreach (var aField in commonFields)
{
PropertyInfo propertyInfos = entity.GetType().GetProperty(aField.Name);
var value = (result[aField.Name] == DBNull.Value) ? null : result[aField.Name]; //if field is nullable
propertyInfos.SetValue(entity, value, null);
}
yield return entity;
}
}
}
}
/*
* https://entityframeworkcore.com/knowledge-base/35631903/raw-sql-query-without-dbset---entity-framework-core
*/
public static IEnumerable<T> FromSqlQuery<T>(this DbContext context, string query, Func<DbDataReader, T> map, params object[] parameters)
{
using (var command = context.Database.GetDbConnection().CreateCommand())
{
if (command.Connection.State != ConnectionState.Open)
{
command.Connection.Open();
}
var currentTransaction = context.Database.CurrentTransaction;
if (currentTransaction != null)
{
command.Transaction = currentTransaction.GetDbTransaction();
}
command.CommandText = query;
if (parameters.Any())
{
command.Parameters.AddRange(parameters);
}
using (var result = command.ExecuteReader())
{
while (result.Read())
{
yield return map(result);
}
}
}
}
}
Model
public class UserModel
{
public string Name { get; set; }
public string Email { get; set; }
public bool? IsDeleted { get; set; }
}
Manual mapping
List<UserModel> usersInDb = Db.FromSqlQuery
(
"SELECT Name, Email FROM Users WHERE Name=#paramName",
x => new UserModel
{
Name = (string)x[0],
Email = (string)x[1]
},
new SqlParameter("#paramName", user.Name)
)
.ToList();
usersInDb = Db.FromSqlQuery
(
"SELECT Name, Email FROM Users WHERE Name=#paramName",
x => new UserModel
{
Name = x["Name"] is DBNull ? "" : (string)x["Name"],
Email = x["Email"] is DBNull ? "" : (string)x["Email"]
},
new SqlParameter("#paramName", user.Name)
)
.ToList();
Auto mapping using reflection
List<UserModel> usersInDb = Db.FromSqlQuery<UserModel>
(
"SELECT Name, Email, IsDeleted FROM Users WHERE Name=#paramName",
new SqlParameter("#paramName", user.Name)
)
.ToList();
This solution leans heavily on the solution from #pius. I wanted to add the option to support query parameters to help mitigate SQL injection and I also wanted to make it an extension off of the DbContext DatabaseFacade for Entity Framework Core to make it a little more integrated.
First create a new class with the extension:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Threading.Tasks;
namespace EF.Extend
{
public static class ExecuteSqlExt
{
/// <summary>
/// Execute raw SQL query with query parameters
/// </summary>
/// <typeparam name="T">the return type</typeparam>
/// <param name="db">the database context database, usually _context.Database</param>
/// <param name="query">the query string</param>
/// <param name="map">the map to map the result to the object of type T</param>
/// <param name="queryParameters">the collection of query parameters, if any</param>
/// <returns></returns>
public static List<T> ExecuteSqlRawExt<T, P>(this DatabaseFacade db, string query, Func<DbDataReader, T> map, IEnumerable<P> queryParameters = null)
{
using (var command = db.GetDbConnection().CreateCommand())
{
if((queryParameters?.Any() ?? false))
command.Parameters.AddRange(queryParameters.ToArray());
command.CommandText = query;
command.CommandType = CommandType.Text;
db.OpenConnection();
using (var result = command.ExecuteReader())
{
var entities = new List<T>();
while (result.Read())
{
entities.Add(map(result));
}
return entities;
}
}
}
}
}
Note in the above that "T" is the type for the return and "P" is the type of your query parameters which will vary based on if you are using MySql, Sql, so on.
Next we will show an example. I'm using the MySql EF Core capability, so we'll see how we can use the generic extension above with this more specific MySql implementation:
//add your using statement for the extension at the top of your Controller
//with all your other using statements
using EF.Extend;
//then your your Controller looks something like this
namespace Car.Api.Controllers
{
//Define a quick Car class for the custom return type
//you would want to put this in it's own class file probably
public class Car
{
public string Make { get; set; }
public string Model { get; set; }
public string DisplayTitle { get; set; }
}
[ApiController]
public class CarController : ControllerBase
{
private readonly ILogger<CarController> _logger;
//this would be your Entity Framework Core context
private readonly CarContext _context;
public CarController(ILogger<CarController> logger, CarContext context)
{
_logger = logger;
_context = context;
}
//... more stuff here ...
/// <summary>
/// Get car example
/// </summary>
[HttpGet]
public IEnumerable<Car> Get()
{
//instantiate three query parameters to pass with the query
//note the MySqlParameter type is because I'm using MySql
MySqlParameter p1 = new MySqlParameter
{
ParameterName = "id1",
Value = "25"
};
MySqlParameter p2 = new MySqlParameter
{
ParameterName = "id2",
Value = "26"
};
MySqlParameter p3 = new MySqlParameter
{
ParameterName = "id3",
Value = "27"
};
//add the 3 query parameters to an IEnumerable compatible list object
List<MySqlParameter> queryParameters = new List<MySqlParameter>() { p1, p2, p3 };
//note the extension is now easily accessed off the _context.Database object
//also note for ExecuteSqlRawExt<Car, MySqlParameter>
//Car is my return type "T"
//MySqlParameter is the specific DbParameter type MySqlParameter type "P"
List<Car> result = _context.Database.ExecuteSqlRawExt<Car, MySqlParameter>(
"SELECT Car.Make, Car.Model, CONCAT_WS('', Car.Make, ' ', Car.Model) As DisplayTitle FROM Car WHERE Car.Id IN(#id1, #id2, #id3)",
x => new Car { Make = (string)x[0], Model = (string)x[1], DisplayTitle = (string)x[2] },
queryParameters);
return result;
}
}
}
The query would return rows like:
"Ford", "Explorer", "Ford Explorer"
"Tesla", "Model X", "Tesla Model X"
The display title is not defined as a database column, so it wouldn't be part of the EF Car model by default. I like this approach as one of many possible solutions. The other answers on this page reference other ways to address this issue with the [NotMapped] decorator, which depending on your use case could be the more appropriate approach.
Note the code in this example is obviously more verbose than it needs to be, but I thought it made the example clearer.
Actually you can create a generic repository and do something like this
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : BaseEntity
{
private readonly DataContext context;
private readonly DbSet<TEntity> dbSet;
public GenericRepository(DataContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public IEnumerable<TEntity> ExecuteCommandQuery(string command)
=> dbSet.FromSqlRaw(command);
}
For Querying Data: Without existing Entity
string query = "SELECT r.Name as roleName, ur.roleId, u.Id as userId FROM dbo.AspNetUserRoles AS ur INNER JOIN dbo.AspNetUsers AS u ON ur.UserId = u.Id INNER JOIN dbo.AspNetRoles AS r ON ur.RoleId = r.Id ";
ICollection<object> usersWithRoles = new List<object>();
using (var command = _identityDBContext.Database.GetDbConnection().CreateCommand())
{
command.CommandText = query;
command.CommandType = CommandType.Text;
await _identityDBContext.Database.OpenConnectionAsync();
using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
usersWithRoles.Add(new {
roleName = reader.GetFieldValueAsync<string>(0).Result,
roleId = reader.GetFieldValueAsync<string>(1).Result,
userId = reader.GetFieldValueAsync<string>(2).Result
});
}
}
}
Detailed:
[HttpGet]
[Route("GetAllUsersWithRoles")]
public async Task<IActionResult> GetAllUsersWithRoles()
{
string query = "SELECT r.Name as roleName, ur.roleId, u.Id as userId FROM dbo.AspNetUserRoles AS ur INNER JOIN dbo.AspNetUsers AS u ON ur.UserId = u.Id INNER JOIN dbo.AspNetRoles AS r ON ur.RoleId = r.Id ";
try
{
ICollection<object> usersWithRoles = new List<object>();
using (var command = _identityDBContext.Database.GetDbConnection().CreateCommand())
{
command.CommandText = query;
command.CommandType = CommandType.Text;
await _identityDBContext.Database.OpenConnectionAsync();
using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
usersWithRoles.Add(new {
roleName = reader.GetFieldValueAsync<string>(0).Result,
roleId = reader.GetFieldValueAsync<string>(1).Result,
userId = reader.GetFieldValueAsync<string>(2).Result
});
}
}
}
return StatusCode(200, usersWithRoles); // Get all users
}
catch (Exception e)
{
return StatusCode(500, e);
}
}
RESULT looks like this:
[
{
"roleName": "admin",
"roleId": "7c9cb1be-e987-4ec1-ae4d-e4c9790f57d8",
"userId": "12eadc86-6311-4d5e-8be8-df30799df265"
},
{
"roleName": "user",
"roleId": "a0d5ef46-b1e6-4a53-91ce-9ff5959f1ed8",
"userId": "12eadc86-6311-4d5e-8be8-df30799df265"
},
{
"roleName": "user",
"roleId": "a0d5ef46-b1e6-4a53-91ce-9ff5959f1ed8",
"userId": "3e7cd970-8c52-4dd1-847c-f824671ea15d"
}
]
You can also use QueryFirst. Like Dapper, this is totally outside EF. Unlike Dapper (or EF), you don't need to maintain the POCO, you edit your sql SQL in a real environment, and it's continually revalidated against the DB. Disclaimer: I'm the author of QueryFirst.
I've came to this question because we have over 100 instances of entity-less usages of SqlQuery in Entity Framework 6 and so going the Microsoft suggested way(s) simply cannot not easily work in our case.
In addition, we had to maintain a single EF (Entity Framework 6) / EFC (Entity Framework Core 5) code base for several months, while migrating from EF to EFC. The code base is fairly large and it was simply impossible to migrate "overnight".
The answer below is based on great answers above and it is just a small extension to make them work for a few more edge cases.
First, for each EF based project we created an EFC based project (e.g. MyProject.csproj ==> MyProject_EFC.csproj) and inside all such EFC projects we defined a constant EFCORE. If you are doing a quick one-time migration from EF to EFC, then you don't need that and you can just keep what's inside #if EFCORE ... #else and remove what's inside #else ... #endif below.
Here is the main interop extension class.
using System;
using System.Collections.Generic;
using System.Threading;
#if EFCORE
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage;
using Database = Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade;
using MoreLinq.Extensions;
#else
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
#endif
namespace YourNameSpace.EntityFrameworkCore
{
/// <summary>
/// Collection of extension methods to simplify migration from EF to EFC.
/// </summary>
public static class EntityFrameworkCoreInterop
{
/// <summary>
/// https://stackoverflow.com/questions/6637679/reflection-get-attribute-name-and-value-on-property
/// </summary>
public static TAttribute? TryGetAttribute<TAttribute>(this PropertyInfo prop) where TAttribute : Attribute =>
prop.GetCustomAttributes(true).TryGetAttribute<TAttribute>();
public static TAttribute? TryGetAttribute<TAttribute>(this Type t) where TAttribute : Attribute =>
t.GetCustomAttributes(true).TryGetAttribute<TAttribute>();
public static TAttribute? TryGetAttribute<TAttribute>(this IEnumerable<object> attrs) where TAttribute : Attribute
{
foreach (object attr in attrs)
{
switch (attr)
{
case TAttribute t:
{
return t;
}
}
}
return null;
}
/// <summary>
/// Returns true if the source string matches *any* of the passed-in strings (case insensitive)
/// </summary>
public static bool EqualsNoCase(this string? s, params string?[]? targets)
{
if (s == null && (targets == null || targets.Length == 0))
{
return true;
}
if (targets == null)
{
return false;
}
return targets.Any(t => string.Equals(s, t, StringComparison.OrdinalIgnoreCase));
}
#if EFCORE
public class EntityException : Exception
{
public EntityException(string message) : base(message)
{
}
}
public static TEntity GetEntity<TEntity>(this EntityEntry<TEntity> entityEntry)
where TEntity : class => entityEntry.Entity;
#region SqlQuery Interop
/// <summary>
/// kk:20210727 - This is a little bit ugly but given that this interop method is used just once,
/// it is not worth spending more time on it.
/// </summary>
public static List<T> ToList<T>(this IOrderedAsyncEnumerable<T> e) =>
Task.Run(() => e.ToListAsync().AsTask()).GetAwaiter().GetResult();
private static string GetColumnName(this MemberInfo info) =>
info.GetCustomAttributes().TryGetAttribute<ColumnAttribute>()?.Name ?? info.Name;
/// <summary>
/// See: https://stackoverflow.com/questions/35631903/raw-sql-query-without-dbset-entity-framework-core
/// Executes raw query with parameters and maps returned values to column property names of Model provided.
/// Not all properties are required to be present in the model. If not present then they will be set to nulls.
/// </summary>
private static async IAsyncEnumerable<T> ExecuteQuery<T>(this Database database, string query, params object[] parameters)
{
await using DbCommand command = database.GetDbConnection().CreateCommand();
command.CommandText = query;
command.CommandType = CommandType.Text;
if (database.CurrentTransaction != null)
{
command.Transaction = database.CurrentTransaction.GetDbTransaction();
}
foreach (var parameter in parameters)
{
// They are supposed to be of SqlParameter type but are passed as objects.
command.Parameters.Add(parameter);
}
await database.OpenConnectionAsync();
await using DbDataReader reader = await command.ExecuteReaderAsync();
var t = typeof(T);
// TODO kk:20210825 - I do know that the code below works as we use it in some other place where it does work.
// However, I am not 100% sure that R# proposed version does. Check and refactor when time permits.
//
// ReSharper disable once CheckForReferenceEqualityInstead.1
if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
t = Nullable.GetUnderlyingType(t)!;
}
var lstColumns = t
.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.ToList();
while (await reader.ReadAsync())
{
if (t.IsPrimitive || t == typeof(string) || t == typeof(DateTime) || t == typeof(Guid) || t == typeof(decimal))
{
var val = await reader.IsDBNullAsync(0) ? null : reader[0];
yield return (T) val!;
}
else
{
var newObject = Activator.CreateInstance<T>();
for (var i = 0; i < reader.FieldCount; i++)
{
var name = reader.GetName(i);
var val = await reader.IsDBNullAsync(i) ? null : reader[i];
var prop = lstColumns.FirstOrDefault(a => a.GetColumnName().EqualsNoCase(name));
if (prop == null)
{
continue;
}
prop.SetValue(newObject, val, null);
}
yield return newObject;
}
}
}
#endregion
public static DbRawSqlQuery<TElement> SqlQuery<TElement>(this Database database, string sql, params object[] parameters) =>
new(database, sql, parameters);
public class DbRawSqlQuery<TElement> : IAsyncEnumerable<TElement>
{
private readonly IAsyncEnumerable<TElement> _elements;
internal DbRawSqlQuery(Database database, string sql, params object[] parameters) =>
_elements = ExecuteQuery<TElement>(database, sql, parameters);
public IAsyncEnumerator<TElement> GetAsyncEnumerator(CancellationToken cancellationToken = new ()) =>
_elements.GetAsyncEnumerator(cancellationToken);
public async Task<TElement> SingleAsync() => await _elements.SingleAsync();
public TElement Single() => Task.Run(SingleAsync).GetAwaiter().GetResult();
public async Task<TElement> FirstAsync() => await _elements.FirstAsync();
public TElement First() => Task.Run(FirstAsync).GetAwaiter().GetResult();
public async Task<TElement?> SingleOrDefaultAsync() => await _elements.SingleOrDefaultAsync();
public async Task<int> CountAsync() => await _elements.CountAsync();
public async Task<List<TElement>> ToListAsync() => await _elements.ToListAsync();
public List<TElement> ToList() => Task.Run(ToListAsync).GetAwaiter().GetResult();
}
#endif
}
}
and the usages are indistinguishable from the former EF usages:
public async Task<List<int>> GetMyResults()
{
using var ctx = GetMyDbContext();
const string sql = "select 1 as Result";
return await ctx.GetDatabase().SqlQuery<int>(sql).ToListAsync();
}
where GetMyDbContext is a method to get your database context and GetDatabase is an one-liner interop that returns ((DbContext)context).Database for a given IMyDbContext : DbContext. This is to simplify simultaneous EF / EFC operations.
This works for primitive types (the example is above), entities, local classes (but not anonymous ones). Column renaming is supported via GetColumnName, but, ... it was already done above.
With Entity Framework 6 you can execute something like below
Create Modal Class as
Public class User
{
public int Id { get; set; }
public string fname { get; set; }
public string lname { get; set; }
public string username { get; set; }
}
Execute Raw DQL SQl command as below:
var userList = datacontext.Database.SqlQuery<User>(#"SELECT u.Id ,fname , lname ,username FROM dbo.Users").ToList<User>();

Generate a class based on table schema

I want to dynamically generate a class based on the results from a query that user submits. For instance, if the user enters Select name, age from tbl, the result is a name column which is string and age which is an int. The resulting class should be:
public class Test
{
public string Name { get; set; }
public int Age { get; set; }
}
Is there an efficient way to do this via EntityFramework or features in C# or I have to use maybe reflection to create a new type and instantiate it.
PS: My purpose is to run this query on the database and show the results in a Grid to the user and run some filter/sort/etc. on it.
You could use TypeBuilder to create a new type and execute the query against database using EF's SqlQuery() as mentioned here.
OR
A cleaner method would be to use dynamic objects to bind the grid. Extend EF to return a collection of dynamic objects as suggested by ChristineBoersen here. The code was written before EF went to RTM. Here's a version that works:
public static class EFExtensions
{
public static IEnumerable<dynamic> CollectionFromSql(this DbContext dbContext, string Sql, Dictionary<string, object> Parameters)
{
using (var cmd = dbContext.Database.Connection.CreateCommand())
{
cmd.CommandText = Sql;
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
foreach (KeyValuePair<string, object> param in Parameters)
{
DbParameter dbParameter = cmd.CreateParameter();
dbParameter.ParameterName = param.Key;
dbParameter.Value = param.Value;
cmd.Parameters.Add(dbParameter);
}
//var retObject = new List<dynamic>();
using (var dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
var dataRow = GetDataRow(dataReader);
yield return dataRow;
}
}
}
}
private static dynamic GetDataRow(DbDataReader dataReader)
{
var dataRow = new ExpandoObject() as IDictionary<string, object>;
for (var fieldCount = 0; fieldCount < dataReader.FieldCount; fieldCount++)
dataRow.Add(dataReader.GetName(fieldCount), dataReader[fieldCount]);
return dataRow;
}
}
You could invoke the above method as follows:
var results = context.CollectionFromSql("Select Name, Age from tbl", new Dictionary<string, object>()).ToList();
// Bind results to grid

When calling a T-SQL stored procedure with Dapper do I need to use '#'?

I am want to call a stored procedure with Dapper and all the code I've seen so far does this by specifying the stored procedure parameters with the # character. This means that I cannot simply define a model class and pass it into Dapper's Query or Execute method with re-translating the model instance, which seems like a waste of time and memory. Is this what I really need to do?
For example, the code below accepts a model instance. The model has all the properties of the sp_GetUser table. I would expect Dapper to pass those model properties into the stored procedure's parameters when calling it. Is this the case or does I really need to define the object being passed in the 'param' parameter?
public IList<User> GetUsers(UserSP user)
{
using (var cn = new SqlConnection(ConnectionString))
{
var users = cn.Query<User>("sp_GetUsers",
param: new
{
#Id = user.Id,
#NAme = user.Name,
#Age = user.Age
},
commandType: CommandType.StoredProcedure).ToList();
return users;
}
}
Try something like this
the class
public class ReportIndex
{
public int SlideNumber { get; set; }
public string ChartName { get; set; }
public string SheetName { get; set; }
}
fill the class with dapper
public List<ReportIndex> GetReportIndex(int reportId)
{
List<ReportIndex> reportIndex = null;
using (var conn = new SqlConnection(connString))
{
conn.Open();
var p = new DynamicParameters();
p.Add("#ReportId", reportId);
reportIndex = conn.Query<ReportIndex>("sp_ReportGetReportIndex",
p,
commandType: CommandType.StoredProcedure).ToList();
}
return reportIndex;
}

Drop SQLite tables using list of tables names

I am trying to drop a collection of tables by using tables names in the list and then get the type of each string and drop it :
List<string> models = new List<string> { "WebBrowser", "Notebook", "Members"};
foreach (string mod in models)
{
Type type = Type.GetType(mod));
using (var dbConn = new SQLiteConnection(app.DBPath))
{
dbConn.RunInTransaction(() =>
{
dbConn.DropTable<type>();
//dbConn.DropTable<WebBrowser>();
dbConn.Dispose();
dbConn.Close();
});
}
}
Problem : I can't drop table using this way, DropTable need the name of class (e.g WebBrowser ) and I don't want to drop each table alone (i.e dbConn.DropTable< WebBrowser >();) since I have more than 50 tables to drop.
Error : "The type or namespace name 'type' could not be found". ( and this error is expected since there is no Class 'type' in my Namespace .
You can drop tables using a SQL command in SQLite. All you need to do is iterate over your collection and build a SQL string each time, and execute it
List<string> models = new List<string> { "WebBrowser", "Notebook", "Members"};
foreach (string mod in models)
{
using (var dbConn = new SQLiteConnection(app.DBPath))
{
SQLiteCommand command = new SQLiteCommand(dbConn);
command.CommandText = string.Format("DROP TABLE {0};", mod);
command.ExecuteNonQuery();
}
}
I'm not sure if this syntax is exactly correct for your case (I only ever use sqlite-net in Windows 8.1) but the general approach is sound
You could create your own Extensionmethod like this:
public static class SQLiteConnectionExtensions
{
public static int DropTable(this SQLiteConnection conn, string tableName)
{
var query = string.Format("drop table if exists \"{0}\"", tableName);
return conn.Execute(query);
}
}
And then use it like this:
var tables = new List<string> { "WebBrowser", "Notebook", "Members" };
using (var dbConn = new SQLiteConnection(app.DBPath))
{
dbConn.RunInTransaction(() =>
{
foreach (string table in tables)
{
dbConn.DropTable(table);
}
});
}
You could also use Reflections. Here are the two extension methods:
public static void DropTable(this SQLiteConnection Connection, Type TableType)
{
typeof(SQLiteConnection).GetMethod("DropTable", Array.Empty<Type>())?.MakeGenericMethod(TableType).Invoke(Connection, null);
}
public static void DropTable(this SQLiteConnection Connection, Type[] AllTableTypes)
{
MethodInfo? Method = typeof(SQLiteConnection).GetMethod("DropTable", Array.Empty<Type>());
if (Method != null)
{
foreach (Type? OneTableType in AllTableTypes)
{
Method.MakeGenericMethod(OneTableType).Invoke(Connection, null);
}
}
}
You can call them on a SQLiteConnection object:
TheSqlLiteConnection.DropTable(typeof(SomeClass));
TheSqlLiteConnection.DropTable(new Type[] { typeof(SomeClass), typeof(SomeOtherClass) });

filtering list using linq

I am writing to seek help, in how can I filter data and then merge two results, below:
Update code*
public class TestController : ApiController
{
private cdw db = new cdw();
public HttpResponseMessage get([FromUri] Query query)
{
var data = db.data_qy.AsQueryable();
if (query.startDate != null)
{
data = data.Where(c => c.UploadDate >= query.startDate);
}
if (!string.IsNullOrEmpty(query.tag))
{
var ids = query.tag.Split(',');
data = data.Where(c => c.TAG.Any(t => ids.Contains(t)));
}
if (!string.IsNullOrEmpty(query.name))
{
var ids = query.name.Split(',');
data = data.Where(c => c.NAME.Any(t => ids.Contains(t)));
}
if (!data.Any())
{
var message = string.Format("No data found");
return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
}
return Request.CreateResponse(HttpStatusCode.OK, data);
}
}
entity class:
public partial class data_qy
{
public int ID { get; set; }
public string Name { get; set; }
public string TAG { get; set; }
public string TAG_IS { get; set; }
[Newtonsoft.Json.JsonProperty(PropertyName = "Date")]
public Nullable<System.DateTime> UploadDate { get; set; }
}
Sample Dataset:
Name Tag Tag_IS
AMCAR 2013-5 03065EAC9
ARES 2006-6RA 04009JAA9
ARES 2012-1A 04013TAB9
ATOM 2003-I A 0182690668
BACM 2006-2 AM 05950EAG3
BCAP 2007-AA3 05530VAN9
BCAP 2007-AA3 05530VAN9
BCJAF 9 C 0312888037
BLNDLN 0 0213093627
BLNDLN 0 0213093627
The underlying SQL query should resemble:
select *
from [dbo].[data_qy]
where TAG like '%78473TAC4%'
or TAG LIKE '%05946XYZ0%'
OR NAME LIKE '%TAP%'
OR NAME LIKE '%STORM%'
Using the following list method above, when I run a query such as (api/test/tag=78473,12669,05946,... (30 values)), i get a -- Exception Message:
Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries
Am I missing something. Any help would be most appreciated.
Thanks
I'm not in a situation to test this on the spot, but I suspect that either of the following should work:
public class TestController : ApiController
{
private cdw db = new cdw();
public HttpResponseMessage get([FromUri] Query query)
{
IQueryable<data_qy> data = null;
if (!string.IsNullOrEmpty(query.tag))
{
var ids = query.tag.Split(',');
var dataMatchingTags = db.data_qy.Where(c => ids.Any(id => c.TAGS.Contains(id)));
if (data == null)
data = dataMatchingTags;
else
data = data.Union(dataMatchingTags);
}
if (!string.IsNullOrEmpty(query.name))
{
var ids = query.name.Split(',');
var dataMatchingName = db.data_qy.Where(c => ids.Any(id => c.NAME.Contains(id)));
if (data == null)
data = dataMatchingName;
else
data = data.Union(dataMatchingName);
}
if (data == null) // If no tags or name is being queried, apply filters to the whole set of products
data = db.data_qy;
if (query.startDate != null)
{
data = data.Where(c => c.UploadDate >= query.startDate);
}
var materializedData = data.ToList();
if (!materializedData.Any())
{
var message = string.Format("No data found");
return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
}
return Request.CreateResponse(HttpStatusCode.OK, materializedData);
}
}
I also suspect that you don't need to check against Null in your query, since EF will understand that when transforming the Expression Tree to SQL, but if needed, you can add it.
That would remove the need to use the foreach, the Aggregate and the call to Count. and results in a much simpler query that should use the IN operator in SQL.
Currently you're executing the same query multiple times (the call to .Any at the end will execute the query and then passing the data variable will execute it again. This can be very costly. Instead, materialize the results and act upon that, as above.

Categories