Generic dapper QueryMultiple function - c#

I am using asp.net mvc, dapper and MySql stored procedures for my web app.
So far, for each page I have 1-3 stored procedures calls.
I recently found out my hosting only provides 10 parallel connections and so I want to combine my calls into one per page.
I already made the necessary stored procedures in the database, but my problem is using dapper in generic way to get the procedures results.
What I want is to make a generic function that will get:
1) Stored Procedure name.
2) List of types the stored procedure returns. And for each type if it is Single/ToList.
The generic function should result a list of variablese that are the results from the stored procedure.
The example in dapper page shows how to make a non-generic QueryMultiple call:
var sql =
#"
select * from Customers where CustomerId = #id
select * from Orders where CustomerId = #id
select * from Returns where CustomerId = #id";
using (var multi = connection.QueryMultiple(sql, new {id=selectedId}))
{
var customer = multi.Read<Customer>().Single();
var orders = multi.Read<Order>().ToList();
var returns = multi.Read<Return>().ToList();
...
}
Basicly, what I want is to insert the Customer,Order,Return types into a dictionary that will state each of them to be Single or ToList, and then do something like:
var result = new List<dynamic>();
var sql =
#"
select * from Customers where CustomerId = #id
select * from Orders where CustomerId = #id
select * from Returns where CustomerId = #id";
using (var multi = connection.QueryMultiple(sql, new {id=selectedId}))
{
foreach(var item in dictionary)
{
if (item.Value.equals("Single"))
result.Add(multi.Read<item.Key>().Single());
else if (item.Value.equals("ToList"))
result.Add(multi.Read<item.Key>().ToList());
}
}
return result;
My problem is, visual studio says:
The type or namespace name 'item' could not be found
Pointing at 'item' in: multi.Read()
What am I doing wrong?
Is there a better way to implement a generic function that uses dapper's QueryMultiple?

It's an old topic though but thought it might help others. In that case you can use below code to make it work. Pass type in Read function as parameter. You can return dynamic. Using Expando Object you can generate properties dynamically.
I would prefer to create object like below and pass list of object to function which will generate dynamic return type for you.
public class MapItem
{
public Type Type { get; private set; }
public DataRetriveTypeEnum DataRetriveType { get; private set; }
public string PropertyName { get; private set; }
public MapItem(Type type, DataRetriveTypeEnum dataRetriveType, string propertyName)
{
Type = type;
DataRetriveType = dataRetriveType;
PropertyName = propertyName;
}
}
//And then make a reusable function like below
public async Task<dynamic> ExecuteQueryMultipleAsync(IDbConnection con, string spName, object param = null,IEnumerable<MapItem> mapItems = null)
{
var data = new ExpandoObject();
using (var multi = await con.QueryMultipleAsync(spName,param, commandType:CommandType.StoredProcedure))
{
if (mapItems == null) return data;
foreach (var item in mapItems)
{
if (item.DataRetriveType == DataRetriveTypeEnum.FirstOrDefault)
{
var singleItem = multi.Read(item.Type).FirstOrDefault();
((IDictionary<string, object>) data).Add(item.PropertyName, singleItem);
}
if (item.DataRetriveType == DataRetriveTypeEnum.List)
{
var listItem = multi.Read(item.Type).ToList();
((IDictionary<string, object>)data).Add(item.PropertyName, listItem);
}
}
return data;
}
}
Below how you call the above method:
var mapItems = new List<MapItem>()
{
new MapItem(typeof(YourObject1), DataRetriveTypeEnum.FirstOrDefault, "Object1"),
new MapItem(typeof(YourObject2), DataRetriveTypeEnum.List, "Object2")
};
var response = await ExecuteQueryMultipleAsync(name, request, mapItems);
var object1 = response.Result.Object1;
var listOfObject2 = ((List<dynamic>)response.Result.Object2).Cast<YourObject2>();
Hope this helps.
Cheers

I think an alternative approach could be instead of QueryMultiple() method, you can make use of
IDbConnection.ExecuteReader()
extension method from Dapper.SqlMapper which returns IDataReader and you can loop through it and get the result sets by reader.NextResult() and map them to your object.

Related

Return a list from Table-Valued SQL Function in LINQ?

I have a WCF service using Entity Framework 6 (_Context) which successfully calls a number of functions in my SQL db however, I am having issues calling a table-valued function.
My function takes a single string parameter user and creates a view using a where clause to determine what that user can see. This works as expected e.g:
SELECT * FROM ises.fnarticles ('ANDREW')
I am now trying to call the function from my WCF service and return results to a list:
public List<vwArticle> GetArticlesByAnalyst(string user)
{
return ((IObjectContextAdapter)_Context).ObjectContext.CreateQuery<vwArticle>("SELECT VALUE ISESModel.Store.fnarticles(#userName) FROM {1}",
new ObjectParameter("userName", user)).ToList();
}
I am getting the error:
{"The specified cast from a materialized 'System.Collections.Generic.IEnumerable`1[System.Data.Common.DbDataRecord]' type to the 'IsesServiceModel.vwArticle' type is not valid."}
I can't return a list nor a single vwArticle object for that matter. Same error...
This method is long winded but achieved what I needed. Seems like you can use LINQ to query a tabled value function as though it's a table...
public List<vwArticle> GetArticlesByAnalyst(string user)
{
List<vwArticle> ArticlesByAnyalist = new List<vwArticle>();
var query = from a in _Context.fnarticles(user)
select a;
foreach (var a in query)
{
var article = new vwArticle
{
ArticleId = a.ArticleId,
ArticleTitle = a.ArticleTitle,
Country = a.Country,
userUpdated = a.userUpdated,
userCreated = a.userCreated,
dateCreated = a.dateCreated,
dateUpdated = a.dateUpdated,
versionDate = a.versionDate
};
ArticlesByAnyalist.Add(article);
}
return ArticlesByAnyalist;
}
And then invoke my method passing in the username & querying the list:
public List<vwArticle> GetArticles(string filter)
{
var ArticlesByAnalyst = GetArticlesByAnalyst("GEMMA");
var query = from a in ArticlesByAnalyst
where blah blah
select a;
return query.ToList();
}

Formatting Select Statement Using Dynamic Linq

I've been looking into this for quite some time now and cannot figure out a resolution. I Originally Tried formatting A dynamic linq Statement as you can see here in this post
I declared a class:
public class DynamicHelper
{
public string FormattedLink(string DisplayText, string ID)
{
return "" + DisplayText + "";
}
public string FormattedLink(string DisplayText, int ID)
{
return "" + DisplayText + "";
}
}
After I inserted a new type in DynamicLinq into the predefinedTypes
,typeof(DynamicHelper) //around line 635
I have a program which is attempting to Invoke the FormattedLink inside of a dynamic linq select:
using (var Model = new MK3Entities())
{
DynamicHelper Dh = new DynamicHelper();
var TOrigin = (Model.Titles.Where("ID > 19632")
.Select("new(ID, #0.FormattedLink(ExtTitleID, ID) as ExtTitleID )", Dh) as System.Collections.IEnumerable)
.Cast<dynamic>().Take(10).ToList();
Console.ReadKey();
}
When I execute this program I get a runtime exception "LINQ to Entities does not recognize the method 'System.String FormattedLink(System.String, Int32)' method, and this method cannot be translated into a store expression."
Any Ideas on how to fix this... I just need simple formatting from Dynamic Select.
The error message is pretty self explanatory. The database doesn't know how to translate that method into SQL. You need to fetch the information that the method needs in your database query and then call that function on the results, rather than in the query.
I'm not sure why you need it to be dynamic, it seems the solution you present is very overly complicated. I would write it as:
using (var Model = new MK3Entities())
{
DynamicHelper Dh = new DynamicHelper();
var TOrigin = Model.Titles
.Where("ID > 19632")
.Select(t => new { ID = t.ID, ExtTitleID = t.ExtTitleId })
.Take(10)
.ToList() // Execute SQL Statement
.Select(t => new {ID = t.ID, Link = nh.FormattedLink(ExtTitleID, ID)})
.ToList();
Console.ReadKey();
}
I'm returning an List<anonymous'1> object instead of a dynamic object (because I've never had the need for dynamic objects) so you can adjust it accordingly.
I just solved similiar problem few hours back.
YOu need ToList() that works with Dynamic linq. Check out this thread: Can't find property or field on a dynamic object
Just copy paste those to your project, and later:
var TOrigin = (Model.Titles.Where("ID > 19632")
.ToAnonymousList()
.Select("new(ID, #0.FormattedLink(ExtTitleID, ID) as
ExtTitleID )", Dh) as System.Collections.IEnumerable);

How to determine database table field and get the value with Entity Framework

I'm looking for a method to getdatabase table's field with variable thing.
I wrote a stupid and unworking method to explain what I need:
using (var dbContext = new db_ReadyEngine_MSSQL())
{
string nameOfField = "UserName";
var table = dbContext.tbl_User;
foreach (var x in table)
{
string fieldValue = x.nameOfField;
}
}
Here, I'm trying to determining column name which it nameOfField...
You may call data from DataTable by using name of column, as example:
Object o = dataTable.Rows[0][nameOfField];
try this:
List<string>values = new List<string>();
using (var dbContext = new db_ReadyEngine_MSSQL())
{
values = (from s in dbContext.tbl_User select s.Username).ToList();
}
return values
Assuming I am reading your question correctly, you want to get the value of a column, whose name is only known at runtime?
If so, have a look at the code below. It will pull the properties from the object type, search for the one that matches the nameOfField value, and then pull attempt to pull a value from it.
foreach (var x in table)
{
var fieldValue = x.GetType().GetProperties().Where(a => a.Name == nameOfField).Select(p => p.GetValue(x, null)).FirstOrDefault();
}
U can use Reflection to get value of Property using its String Name
using (var dbContext = new db_ReadyEngine_MSSQL())
{
string nameOfField = "UserName";
var table = dbContext.tbl_User;
foreach (var x in table)
{
string fieldValue = typeof(x).GetProperty(nameOfField ).GetValue(x, null) as string;
}
}
You can use Entity SQL for this without typing the query itself:
IEnumerable<object> GetFieldValues<T>(DbContext context, string fieldName)
where T : class
{
var oc = ((IObjectContextAdapter)context).ObjectContext;
ObjectQuery<T> q = oc.CreateObjectSet<T>();
return q.Select("it." + fieldName)
.AsEnumerable()
.Select(x => x[0]);
}
The trick is that an ObjectSet (the predecessor, sort of, or DbSet) can easily be cast to an ObjectQuery, which is the base of Entity SQL. By default, the command text uses "it" as alias for the table in the query, so if you want the value of one specific field, you must prefix it by the alias, and there you go.
The Select returns a DbDataRecord. The first value from this record is returned.
The advantage of this method over others is that it only queries the requested field from the database.
Of course, if you know the type of the field in question up front, you can make a strong-typed version of this method.

Searching Generic Class Field Names

I have a method which returns a generic list of something.
I want to be able to pass this method a string value which will represent one of the fields in the class's name, and based on this I want to order the data by this field. I want to do this in a different way other than a switch.
For example;
private void GetList()
{
var list = GetResearchStocks("Sedol");
}
private List<Stocks> GetResearchStocks(string orderBy = "")
{
var currentResearchStockList = _reports.GetZeusData("tblResearchStocks");
var researchStocklist = currentResearchStockList.AsEnumerable().ToList();
_zeusResearchStocks = researchStocklist.Select(item => new ZEUS_ResearchStocks
{
Sedol = item[0].ToString(),
StockName = item[1].ToString(),
}
).ToList();
if (orderBy != "")
{
return _zeusResearchStocks.OrderBy(o=>o.) ?????? < What to do here?
}
return _zeusResearchStocks;
}
You may either use Dynamic Linq library (DLinq) or you may just write an extension method using reflection to use string variables in order statements.
For the second option, refer to this example.

Dynamic query using LINQ to SQL

I need to figure out if it is possible to dynamically build a query with LINQ, dynamically selecting the table in which to perform the query.
This is an example of what I would do:
//Not working,just for example
public List<dynamic> _getGenericList(String tableName)
{
var l = from a in db.//I need to use here tableName
select a;
return l.ToList<dynamic>();
}
Is there a way to make this possible?
If the query is this simple you can dynamically create a standard sql statement and execute it, this is the most simplest way without using processor heavy reflection and complex code?
var query = "SELECT * FROM " + tableName;
var res = context.ExecuteQuery<dynamic>(query).ToList();
I've found a way to do it, but I'm not sure if I'd use this code. If you have a DataContext that contains two tables:
PrimaryTable
ID,
FirstValue,
SecondValue
SecondaryTable
ID,
FirstSecondaryValue
You could use the following DataHelper class:
class DataHelper
{
public MyDatabaseDataContext db = new MyDatabaseDataContext();
List<dynamic> GetDynamicList<T>() where T : class
{
System.Data.Linq.Table<T> table = db.GetTable<T>();
var result = from a in table select a;
return result.ToList<dynamic>();
}
public List<dynamic> GetWhatIWant(string tableName)
{
Type myClass = Type.GetType("DynamicLinqToSql." + tableName);
MethodInfo method = typeof(DataHelper).GetMethod("GetDynamicList", BindingFlags.NonPublic | BindingFlags.Instance);
method = method.MakeGenericMethod(myClass);
return (List<dynamic>)method.Invoke(this, null);
}
}
Then you can create an instance of your DataHelper and call the GetWhatIWant method, passing in the table name.
var dataHelper = new DataHelper();
List<dynamic> myFirstList = dataHelper.GetWhatIWant("PrimaryTable");
for (int i = 0; i < 5 && i < myFirstList.Count; i++)
{
System.Console.WriteLine(String.Format("{0} - {1}", myFirstList[i].FirstValue.ToString(), myFirstList[i].SecondValue.ToString()));
}
List<dynamic> mySecondList = dataHelper.GetWhatIWant("SecondaryTable");
for (int i = 0; i < 5 && i < mySecondList.Count; i++)
{
System.Console.WriteLine(mySecondList[i].FirstSecondaryValue.ToString());
}
System.Console.ReadKey();
I know this is old, but if you are here looking for answers like I was, then maybe this will help. I'm using a .NET ObjectContext directly instead of a DataContext data source. If you are using the DataContext version then you can simply (I hope) use queryResults = myGlobalContext.ExecuteQuery<dbGenericData>(query).ToList(); instead and I'm pretty sure it will work the same way.
Your tables will be a lot easier to work with if you have standards in naming and design like
the ID field for the table is always X type (INT, GUID, etc)
the ID field is always named tableNameID, the 'table name' with ID tagged on.
etc,
This will allow you to easily build the ID field by simply appending the 'ID' string onto the table name and will allow you to use a reliable CAST, if needed.
Speaking of CAST you will notice one in the query string. You will need to modify the use of the SQL string using CAST, changing field lengths like my nvarChar(50) example, etc, to overcome getting various TYPES of data from your database.
Final note: In the query string you will see I use the 'AS' key word to cast the DB field to a new name. I cast the 'tableIDField' into the name 'id' and I cast the 'requestedField' into the name 'dbData'. This allows the system to match up the renamed fields from the DB into the STRUCT object container we dump the data into. This allows you to construct generic containers to hold the data returned without having to worry about matching up with the DB field names.
I'm not a guru at this stuff, but I hope this helps somebody out.
private void testMethod(string requestedField, string tableName)
{
var tableIDField = tableName + "ID";
var query = "select " + tableIDField + " as id, CAST(" + requestedField + "as nvarchar(50)) as dbData from " + tableName;
List<dbGenericData> queryResults = null;
try
{
queryResults = myGlobalContext.ExecuteStoreQuery<dbGenericData>(query).ToList();
}
catch (Exception ex)
{
//Simply ignore any exceptions.
//These will need examined to determine best solution to unexpected results.
}
}
private struct dbGenericData
{
public dbGenericData(int id, string dbData)
{
this = new dbGenericData();
ID = id;
DBData = dbData;
}
public int ID { get; set; }
public string DBData { get; set; }
}
you can Generic Method and use db.Set<T> that return a DbSet Based on T
var esql = "select t from TypeName as t"
var q = db.CreateQuery(esql);
Use entity sql for linq to sql, http://esql.codeplex.com

Categories

Resources