Executing Stored Procedure with Entity Framework - entity-framework

We are currently developing an internal system, using the repository pattern and Entity Framework 5 as our ORM.
We have a bunch of stored procedures in our database that we call. Some of these procedures have similar parameter names. So when we make a call to execute the stored procedure, like so:
DbContext.Database.SqlQuery<ReturnType>(query, parameters)
The resulting enumerable fails on the second call to another procedure with a similar parameter name, saying:
ArgumentException was unhandled by user code: The SqlParameter is already contained by another SqlParameterCollection.
We have tried putting all SqlParameters into a collection which we loop through and null out all the items in the collection, but this fails as well....gloriously.
Is there a way to reach and manipulate the Object array in this stack trace?
at System.Data.SqlClient.SqlParameterCollection.Validate(Int32 index, Object value)
at System.Data.SqlClient.SqlParameterCollection.AddRange(Array values)
at System.Data.Objects.ObjectContext.CreateStoreCommand(String commandText, Object[] parameters)
at System.Data.Objects.ObjectContext.ExecuteStoreQueryInternal[TElement](String commandText, String entitySetName, MergeOption mergeOption, Object[] parameters)
at System.Data.Objects.ObjectContext.ExecuteStoreQuery[TElement](String commandText, Object[] parameters)
at System.Data.Entity.Internal.InternalContext.ExecuteSqlQuery[TElement](String sql, Object[] parameters)
at System.Data.Entity.Internal.InternalContext.ExecuteSqlQueryAsIEnumerable[TElement](String sql, Object[] parameters)
at System.Data.Entity.Internal.InternalContext.ExecuteSqlQuery(Type elementType, String sql, Object[] parameters)
at System.Data.Entity.Internal.InternalSqlNonSetQuery.GetEnumerator()
at System.Data.Entity.Internal.InternalSqlQuery1.GetEnumerator()
at System.Linq.Enumerable.First[TSource](IEnumerable1 source)

you can do something like that:
List<ReturnType> obj = DbContext.Database.SqlQuery<ReturnType>(query, parameters).ToList();
it will bind the variable obj, after that you can call the obj how many times you want without problem with sqlParameter.

I have not been able to resolve the issue, the command object in the built in SqlQuery method does not seem to clear the sql parameters, however I have a work around.
you can create your own command based on the current connection - but you will lose the ability to easily map out a return type.
public void CallStoredProc(string query, List<SqlParameter> parameters)
{
//this.Database.Connection (this = DBContext)
System.Data.Common.DbCommand cmd = this.Database.Connection.CreateCommand();
cmd.CommandText = query;
// add params
foreach (var item in parameters)
{
cmd.Parameters.Add(item);
}
// execute query (not differed)
var reader = cmd.ExecuteReader();
// attempt to read rows
if (reader.HasRows)
{
while (reader.Read())
{
// row level data
}
}
// cleanup
cmd.Parameters.Clear();
cmd.Dispose();
}

Are you able to make a copy of your parameters collection and each parameter that in rather than reusing the same instances of the parameters? Maybe have a helper method somewhere that automatically duplicates it for you.

Create Stored Procedure
ALTER PROCEDURE [dbo].[sp_GelUserContactDetails]
(
#Id INT=0,
#UserId int=0
)
AS
BEGIN
SELECT M.Id AS UserID, M.FirstName as Name,M.Designation,M.CompanyName FROM dbo.Flo_MemberShip M
WHERE ID=#Id
END
// Creating Stored Procedure that hold result of class MyContacts
public class MyContacts
{
public int UserID { get; set; }
public string Designation { get; set; }
public string Name { get; set; }
public string Organization { get; set; }
public string Image { get; set; }
public string RequestDate { get; set; }
public string IsContact { get; set; }
}
here I am creating a Entity that`s name is MyContacts. Here the MyContacts properties name must be the same as the returned column of the select statement of the Stored Procedure.
using (var context = new FLOEntities())
{
string query = "sp_GelUserContactDetails #Id,#UserId";
SqlParameter Id = new SqlParameter("#Id", selfId);
SqlParameter UserId = new SqlParameter("#UserId", userId);
obj = context.Database.SqlQuery<MyContacts>(query, Id, UserId).FirstOrDefault();
}
return obj;
click here

I found a solution, sort of a work around this issue. The stored proc call is made to instantiate a new DbContext that will be disposed after execution, like so.
IEnumerable<ReturnType> Results = null;
using (DbContext NewContext = new PrometheusEntities())
{
Results = NewContext.Database.SqlQuery<ReturnType>(query, parameters).ToList();
}
return Results;
I haven't experienced any issues since employing this method.

Related

Using DbParameter instead of SqlParameter with _context.Database.SqlQuery for stored proc execution

I am using Entity Framework 6.1 and GenericRepository. To execute stored procedure I am using SqlQuery method. Currently I am using SqlParameters but being a GenericRepository I don't want to use some specific class. I tried multiple options but how can I use DbParameter here. I tried to access providerName (System.Data.SqlClient) from dbcontext but that is also not available. Any direction would help. Please let me know incase you want me to provide more informaiton.
public IEnumerable<T> GetAllFromStoredProcedure(string storedProcedureName, List<StoredProcedureParameter> parameters)
{
List<SqlParameter> sqlParameters = new List<SqlParameter>();
foreach (StoredProcedureParameter parameter in parameters)
{
sqlParameters.Add(new SqlParameter(parameter.Name, parameter.Type) { Value = parameter.Value});
}
return _context.Database.SqlQuery<T>(storedProcedureName, sqlParameters.ToArray());
}
calling as -
clientRepository.GetAllFromStoredProcedure("GetClients", parameters).ToList();
What I did for this -
In my Domain project (that has the Context and Entities created using Code First From Database), I added a class StoredProcedureParameter -
public class StoredProcedureParameter
{
public string Name { get; set; }
public DbType Type { get; set; }
public object Value { get; set; }
public static object[] CreateParameters(List<StoredProcedureParameter> parameters)
{
List<SqlParameter> sqlParameters = new List<SqlParameter>();
foreach (StoredProcedureParameter parameter in parameters)
{
sqlParameters.Add(new SqlParameter(parameter.Name, parameter.Type) { Value = parameter.Value });
}
return sqlParameters.ToArray();
}
}
And then in my Repository project (that has the GenericRepository and UnitOfWork), I added a new method -
public IEnumerable<T> GetAllFromStoredProcedure(string storedProcedureName, List<StoredProcedureParameter> parameters)
{
return _context.Database.SqlQuery<T>(storedProcedureName, StoredProcedureParameter.CreateParameters(parameters));
}
Now when calling Repository->GetAllFromStoredProcedure method (from my Application project) I will create the parameter list from Domain->StoredProcedureParameter->CreateParameters method. This ensures the parameters passed are of the correct type, my GenericRepository remains generic and my Application project is also unaffected.

Extract strongly-typed data context instance from arbitrary query

Background
We have a class library which has a grid (inherits from WPF DataGrid) with refresh functionality. The grid has a IQueryable Query property, which enables the refresh. Each grid's query is defined not in the class library, but in the referencing end-project:
var dg = new RefreshableDataGrid();
dg.Query = () => new ProjectDbContext().Persons;
Each grid also has a textbox for text filtering. When text is entered in the filter, an expression is generated which checks if any string property or string-convertible property (using SqlFunctions.StringConvert) contains the filter string. The expression is then appended to the original query as an argument to Where, and thus only the records containing matching strings are returned.
//within the class library
//pseudo-code -- this is actually done via reflection, because at compile time the
//actual type of the grid is not known, and there is no generic placeholder
this.ItemsSource = this.Query.Where(filterExpression)
In some cases, the filter logic is defined in end-projects, on the entity type. For example:
public interface IFilterable {
public Expression<Func<String, Boolean>> TextSearchExpression();
}
public class Email {
public int ID {get;set;}
public int PersonID {get;set;}
public string Address {get;set;}
}
public class Person : IFilterable
public int ID {get;set;}
public string LastName {get;set;}
public string FirstName {get;set;}
public Expression<Func<String, Boolean>> TextSearchExpression() {
Dim ctx = new ProjectDbContext();
return phrase => LastName.Contains(phrase) || FirstName.Contains(phrase) ||
ctx.Emails.Where(x => x.PersonID = ID && x.Address.Contains(prase).Any();
}
}
This expression tree uses an instance of the project-specific context, which is a different instance from that of the original query. Queries cannot use components from multiple contexts (at least not in Entity Framework). I can rewrite the expression tree to use a specific instance, but I need to extract the original instance from the query.
It seems obvious that the query holds some reference to the context instance, otherwise the query would not be able to return results.
I do not want to pass the context instance to the class library.
Hence:
Given a query, how can I get the strongly-typed DbContext instance used to create the query?
In other words, what goes in the body of this method:
DbContext GetDbContext<TSource>(IQueryable<TSource> qry) {
// ???
}
It seems obvious that the query holds some reference to the context instance, otherwise the query would not be able to return results.
That's true, but it's implementation specific detail and in EF is encapsulated inside internal members/classes/interfaces.
Also taking into account that DbContext is build on top of the ObjectContext, holding a reference to the DbContext is not strongly necessary. Fortunately that's not the case :)
The following uses reflection and implementation details of the latest EF6.1.3 (tested and working if you don't use some 3rd party extensions like LinqKit and similar that replace the query provider):
public static DbContext GetDbContext<TSource>(this IQueryable<TSource> query)
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
var provider = query.Provider;
var internalContextProperty = provider.GetType().GetProperty("InternalContext", flags);
if (internalContextProperty == null) return null;
var internalContext = internalContextProperty.GetValue(provider, null);
if (internalContext == null) return null;
var ownerProperty = internalContext.GetType().GetProperty("Owner", flags);
if (ownerProperty == null) return null;
var dbContext = (DbContext)ownerProperty.GetValue(internalContext, null);
return dbContext;
}
I would recommend passing an instance of MyDataContext into your query function
public class DACPerson
{
public static IQueryable<Person> GetAllAsQueryable(MyDataContext db)
{
return db.People.AsQueryable();
}
}
This allows you to do the following in the calling function:
public List<Person> UpdateListofPeople(string term)
{
using(DataContext db = new DataContext())
{
var people = DACPerson.GetAllAsQueryable(db);
var result = people.Where(x=>x.Username.Contains(term)).
//call other Data Access Component FUnctions here using same DB....
}
}
i.e. you can bring the filtering to the layer above the data access class.
Some people may not like to do this. You may get the advice that its best to keep all entityframeowrk functionality within the same layer and just return the DTO. I like the above approach though. It depends on who will have to maintain each part of your application in the future.
Hope this helps at least

EF get dbset name in runtime from Type

Purpose:
I need to get the name of the dbset name of the entity
typeof(UserAccount) = "UserAccounts".
But in runtime I need a common type for the loop and therefor do not know example "UserAccount".
Only the "name" from typeof?
I have created an DbContext with some entities.
I have been googling for some time but it do not seem to be working for me because of the Type converting?
Please see my method GetDbSetName in the bottom of this description.
I am pretty new at this EF stuff - so please help med with my issue as described below ;-)
public class MyEntities : DbContext
{
public DbSet<UserAccount> UserAccounts { get; set;}
public DbSet<UserRole> UserRoles { get; set; }
public DbSet<UserAccountRole> UserAccountRoles { get; set; }
}
Defined a list of Type to control the output:
public static List<Type> ModelListSorted()
{
List<Type> modelListSorted = new List<Type>();
modelListSorted.Add(typeof(UserRole));
modelListSorted.Add(typeof(UserAccountRole));
modelListSorted.Add(typeof(UserAccount));
return modelListSorted;
}
The problem is below using Type - If I use "UserAccount" it Works and I get "UserAccounts".
But I do not have the "UserAccount" in runtime as I am in a loop with a serie of types.
I do only have the Type list giving the e
public static loopList()
{
List<Type> modelListSorted = ModelListSorted();
foreach (Type currentType in modelListSorted)
{
string s = DataHelper.GetDbSetName(currentType, db);
}
}
HERE IS THE METHOD GIVING ME THE CHALLANGES ;-)
Meaning not compiling.
saying I am missing a assembly?
I know it is pretty pseudo but can this be done smoothly?
public static string GetDbSetName(Type parmType, MyEntities db)
{
string dbsetname = (db as IObjectContextAdapter).ObjectContext.CreateObjectSet<parmType>().EntitySet.Name;
return dbsetname;
}
The challenge here is that two reflection steps are involved, one to invoke the generic CreateObjectSet method and one to get the EntitySet from the result. Here's a way to do this:
First, the method:
string GetObjectSetName(ObjectContext oc, MethodInfo createObjectSetMethodInfo,
Type objectSetType, Type entityType)
{
var objectSet = createObjectSetMethodInfo.MakeGenericMethod(entityType)
.Invoke(oc, null);
var pi = objectSetType.MakeGenericType(entityType).GetProperty("EntitySet");
var entitySet = pi.GetValue(objectSet) as EntitySet;
return entitySet.Name;
}
As you see, I first get the ObjectSet by invoking the MethodInfo representing the generic method CreateObjectSet<T>(). Then I find the PropertyInfo for the EntitySet property of the generic type ObectSet<T>. Finally, I get this property's value and the name of the obtained EntitySet.
To do this, I first get a MethodInfo for CreateObjectSet<>() (the one without parameters) and the ObjectSet<> type
var createObjectSetMethodInfo =
typeof(ObjectContext).GetMethods()
.Single(i => i.Name == "CreateObjectSet"
&& !i.GetParameters().Any());
var objectSetType = Assembly.GetAssembly(typeof(ObjectContext))
.GetTypes()
.Single(t => t.Name == "ObjectSet`1");
In GetObjectSetName their generic parameters are specified by a concrete entity type, which is done by these "MakeGeneric..." methods.
var oc = (dbContextInstance as IObjectContextAdapter).ObjectContext;
var entityType = typeof(UserRole);
var name = GetObjectSetName(oc, createObjectSetMethodInfo, objectSetType, entityType);
In EF 6 these should be the usings:
using System.Data.Entity.Core.Metadata.Edm
using System.Data.Entity.Core.Objects
using System.Data.Entity.Infrastructure
using System.Linq
using System.Reflection

How can I use a stored procedure + repository + unit of work patterns in Entity Framework?

I have MVC web application project with Entity Framework code first. In this project I am going to use generic repository and unit of work patterns. Plus I want to use stored procedures for get list by and get-list methods.
How can I use stored procedures with generic repository and unit of work patterns?
To your generic repository add
public IEnumerable<T> ExecWithStoreProcedure(string query, params object[] parameters)
{
return _context.Database.SqlQuery<T>(query, parameters);
}
And then you can call it with any unitofwork/repository like
IEnumerable<Products> products =
_unitOfWork.ProductRepository.ExecWithStoreProcedure(
"spGetProducts #bigCategoryId",
new SqlParameter("bigCategoryId", SqlDbType.BigInt) { Value = categoryId }
);
You shouldn't be trying to use SPs with UoW/Repository pattern, because they are hard to control in code and often don't map back to the same entity type. UoW and Repository pattern are better suited to using ADO.NET directly and not Entity Framework, as EF is already a Repository pattern. I would suggest CQRS as a better pattern when using SPs. Elaborating on the answer by #sunil and my comment on it, I created a class specifically for handling stored procedures. It's easy to mock and test, too.
public class ProcedureManager : IProcedureManager
{
internal DbContext Context;
public ProcedureManager(DbContext context)
{
Context = context;
}
//When you expect a model back (async)
public async Task<IList<T>> ExecWithStoreProcedureAsync<T>(string query, params object[] parameters)
{
return await Context.Database.SqlQuery<T>(query, parameters).ToListAsync();
}
//When you expect a model back
public IEnumerable<T> ExecWithStoreProcedure<T>(string query)
{
return Context.Database.SqlQuery<T>(query);
}
// Fire and forget (async)
public async Task ExecuteWithStoreProcedureAsync(string query, params object[] parameters)
{
await Context.Database.ExecuteSqlCommandAsync(query, parameters);
}
// Fire and forget
public void ExecuteWithStoreProcedure(string query, params object[] parameters)
{
Context.Database.ExecuteSqlCommand(query, parameters);
}
}
For Generic Repository Add this :
public IEnumerable<TEntity> GetdataFromSqlcommand(string command, System.Data.SqlClient.SqlParameter[] parameter)
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.Append($"EXECUTE {command}");
strBuilder.Append(string.Join(",", parameter.ToList().Select(s => $" #{s.ParameterName}")));
return Context.Set<TEntity>().FromSql(strBuilder.ToString(), parameter);
}
And you just need to send Stored Procedure name and the array of parameters :
public IEnumerable<MainData> GetMainData(Param query)
{
var param1 = new SqlParameter("param1", query.param1);
var param2 = new SqlParameter("param2", query.param2);
return GetdataFromSqlcommand("StoredProcedurename", parameter: new[] { param1, param2 }).ToList();
}
If you are using .net core 3.1, you have to make work around
You will create a class that will carry result of stored procedure
You will create another partial class from DBcontext and put inside it the previous class
You will create IStoredProcedure interface and implement it in stored procedure using generic
Inject your stored procedure class in startup class
Don't forget to make your result class fields, same as result form stored procedure
Execute the stored procedure
Implementation:
(1) first step
public class TaskPercents
{
public long Id { get; set; }
public long SchoolRepId { get; set; }
public string Name { get; set; }
}
(2) second step
public partial class SchoolsPartnershipDBContext : DbContext
{
public virtual DbSet<TaskPercents> TaskPercents { get; set; }
}
(3) third step
public interface IStoredProcedure<T>
{
public List<T> ExecuteStored(string query);
}
{
private SchoolsPartnershipDBContext _context;
public StoredProcedure(SchoolsPartnershipDBContext Context)
{
_context = Context;
}
public List<T> ExecuteStored(string query)
{
//Context = new SchoolsPartnershipDBContext();
var r = _context.Set<T>().FromSqlRaw(query);
return r.ToList();
// return null;
}
}
Last step
var result = _storedProcedure.ExecuteStored("TaskExecPercentForSchoolRep");
return result.ToList();

How to use Entity Framework to map results of a stored procedure to entity with differently named parameters

I am trying to create a basic example using Entity Framework to do the mapping of the output of a SQL Server Stored procedure to an entity in C#, but the entity has differently (friendly) names parameters as opposed to the more cryptic names. I am also trying to do this with the Fluent (i.e. non edmx) syntax.
What works ....
The stored procedure returns values called: UT_ID, UT_LONG_NM, UT_STR_AD, UT_CITY_AD, UT_ST_AD, UT_ZIP_CD_AD, UT_CT
If I create an object like this ...
public class DBUnitEntity
{
public Int16 UT_ID { get; set; }
public string UT_LONG_NM { get; set; }
public string UT_STR_AD { get; set; }
public string UT_CITY_AD { get; set; }
public string UT_ST_AD { get; set; }
public Int32 UT_ZIP_CD_AD { get; set; }
public string UT_CT { get; set; }
}
and an EntityTypeConfiguration like this ...
public class DbUnitMapping: EntityTypeConfiguration<DBUnitEntity>
{
public DbUnitMapping()
{
HasKey(t => t.UT_ID);
}
}
... which I add in the OnModelCreating of the DbContext, then I can get the entities just fine out of the database, which is nice, using this ....
var allUnits = _context.Database.SqlQuery<DBUnitEntity>(StoredProcedureHelper.GetAllUnitsProc);
BUT, What Doesn't Work
If I want an entity like this, with friendlier names ....
public class UnitEntity : IUnit
{
public Int16 UnitId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public Int32 Zip { get; set; }
public string Category { get; set; }
}
and an EntityTypeConfiguration like this ...
public UnitMapping()
{
HasKey(t => t.UnitId);
Property(t => t.UnitId).HasColumnName("UT_ID");
Property(t => t.Name).HasColumnName("UT_LONG_NM");
Property(t => t.Address).HasColumnName("UT_STR_AD");
Property(t => t.City).HasColumnName("UT_CITY_AD");
Property(t => t.State).HasColumnName("UT_ST_AD");
Property(t => t.Zip).HasColumnName("UT_ZIP_CD_AD");
Property(t => t.Category).HasColumnName("UT_CT");
}
When I try to get the data I get a System.Data.EntityCommandExecutionException with the message ....
"The data reader is incompatible with the specified 'DataAccess.EFCodeFirstSample.UnitEntity'. A member of the type, 'UnitId', does not have a corresponding column in the data reader with the same name."
If I add the "stored procedure named" property to the entity, it goes and complains about the next "unknown" property.
Does "HasColumnName" not work as I expect/want it to in this code-first stored procedure fluent style of EF?
Update:
Tried using DataAnnotations (Key from ComponentModel, and Column from EntityFramework) ... ala
public class UnitEntity : IUnit
{
[Key]
[Column("UT_ID")]
public Int16 UnitId { get; set; }
public string Name { get; set; }
That did remove the need for any EntityTypeConfiguration at all for the DBUnitEntity with the database-identical naming (i.e. just adding the [Key] Attribute), but did nothing for the entity with the property names that don't match the database (same error as before).
I don't mind using the ComponentModel Annotations in the Model, but I really don't want to use the EntityFramework Annotations in the model if I can help it (don't want to tie the Model to any specific data access framework)
From Entity Framework Code First book (page 155):
The SQLQuery method always attempts the column-to-property matching based on property name...
None that the column-to-property name matching does not take any mapping into account. For example, if you had mapped the DestinationId property to a column called Id in the Destination table, the SqlQuery method would not use this mapping.
So you cannot use mappings when calling stored procedure. One workaround is to modify your stored procedure to return result with aliases for each column that will match your object properties' names.
Select UT_STR_AD as Address From SomeTable etc
This isn't using Entity Framework but it is stemming from dbcontext. I have spent hours upon hours scouring the internet and using dot peek all for nothing. I read some where that the ColumnAttribute is ignored for SqlQueryRaw. But I have crafted up something with reflection, generics, sql datareader, and Activator. I am going to be testing it on a few other procs. If there is any other error checking that should go in, comment.
public static List<T> SqlQuery<T>( DbContext db, string sql, params object[] parameters)
{
List<T> Rows = new List<T>();
using (SqlConnection con = new SqlConnection(db.Database.Connection.ConnectionString))
{
using (SqlCommand cmd = new SqlCommand(sql, con))
{
cmd.CommandType = CommandType.StoredProcedure;
foreach (var param in parameters)
cmd.Parameters.Add(param);
con.Open();
using (SqlDataReader dr = cmd.ExecuteReader())
{
if (dr.HasRows)
{
var dictionary = typeof(T).GetProperties().ToDictionary(
field => CamelCaseToUnderscore(field.Name), field => field.Name);
while (dr.Read())
{
T tempObj = (T)Activator.CreateInstance(typeof(T));
foreach (var key in dictionary.Keys)
{
PropertyInfo propertyInfo = tempObj.GetType().GetProperty(dictionary[key], BindingFlags.Public | BindingFlags.Instance);
if (null != propertyInfo && propertyInfo.CanWrite)
propertyInfo.SetValue(tempObj, Convert.ChangeType(dr[key], propertyInfo.PropertyType), null);
}
Rows.Add(tempObj);
}
}
dr.Close();
}
}
}
return Rows;
}
private static string CamelCaseToUnderscore(string str)
{
return Regex.Replace(str, #"(?<!_)([A-Z])", "_$1").TrimStart('_').ToLower();
}
Also something to know is that all of our stored procs return lowercase underscore delimited. The CamelCaseToUnderscore is built specifically for it.
Now BigDeal can map to big_deal
You should be able to call it like so
Namespace.SqlQuery<YourObj>(db, "name_of_stored_proc", new SqlParameter("#param",value),,,,,,,);
The example posted by "DeadlyChambers" is great but I would like to extend the example to include the ColumnAttribute that you can use with EF to add to a properties to map a SQL field to a Class property.
Ex.
[Column("sqlFieldName")]
public string AdjustedName { get; set; }
Here is the modified code.
This code also include a parameter to allow for custom mappings if needed by passing a dictionary.
You will need a Type Converter other than Convert.ChangeType for things like nullable types.
Ex. If you have a field that is bit in the database and nullable boolean in .NET you will get a type convert issue.
/// <summary>
/// WARNING: EF does not use the ColumnAttribute when mapping from SqlQuery. So this is a "fix" that uses "lots" of REFLECTION
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="database"></param>
/// <param name="sqlCommandString"></param>
/// <param name="modelPropertyName_sqlPropertyName">Model Property Name and SQL Property Name</param>
/// <param name="sqlParameters">SQL Parameters</param>
/// <returns></returns>
public static List<T> SqlQueryMapped<T>(this System.Data.Entity.Database database,
string sqlCommandString,
Dictionary<string,string> modelPropertyName_sqlPropertyName,
params System.Data.SqlClient.SqlParameter[] sqlParameters)
{
List<T> listOfT = new List<T>();
using (var cmd = database.Connection.CreateCommand())
{
cmd.CommandText = sqlCommandString;
if (cmd.Connection.State != System.Data.ConnectionState.Open)
{
cmd.Connection.Open();
}
cmd.Parameters.AddRange(sqlParameters);
using (var dataReader = cmd.ExecuteReader())
{
if (dataReader.HasRows)
{
// HACK: you can't use extension methods without a type at design time. So this is a way to call an extension method through reflection.
var convertTo = typeof(GenericExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(mi => mi.Name == "ConvertTo").Where(m => m.GetParameters().Count() == 1).FirstOrDefault();
// now build a new list of the SQL properties to map
// NOTE: this method is used because GetOrdinal can throw an exception if column is not found by name
Dictionary<string, int> sqlPropertiesAttributes = new Dictionary<string, int>();
for (int index = 0; index < dataReader.FieldCount; index++)
{
sqlPropertiesAttributes.Add(dataReader.GetName(index), index);
}
while (dataReader.Read())
{
// create a new instance of T
T newT = (T)Activator.CreateInstance(typeof(T));
// get a list of the model properties
var modelProperties = newT.GetType().GetProperties();
// now map the SQL property to the EF property
foreach (var propertyInfo in modelProperties)
{
if (propertyInfo != null && propertyInfo.CanWrite)
{
// determine if the given model property has a different map then the one based on the column attribute
string sqlPropertyToMap = (propertyInfo.GetCustomAttribute<ColumnAttribute>()?.Name ?? propertyInfo.Name);
string sqlPropertyName;
if (modelPropertyName_sqlPropertyName!= null && modelPropertyName_sqlPropertyName.TryGetValue(propertyInfo.Name, out sqlPropertyName))
{
sqlPropertyToMap = sqlPropertyName;
}
// find the SQL value based on the column name or the property name
int columnIndex;
if (sqlPropertiesAttributes.TryGetValue(sqlPropertyToMap, out columnIndex))
{
var sqlValue = dataReader.GetValue(columnIndex);
// ignore this property if it is DBNull
if (Convert.IsDBNull(sqlValue))
{
continue;
}
// HACK: you can't use extension methods without a type at design time. So this is a way to call an extension method through reflection.
var newValue = convertTo.MakeGenericMethod(propertyInfo.PropertyType).Invoke(null, new object[] { sqlValue });
propertyInfo.SetValue(newT, newValue);
}
}
}
listOfT.Add(newT);
}
}
}
}
return listOfT;
}