How to build up anonymous array of parameters - c#

I have a query
string sQuery = string.Format("SELECT {0} FROM vwweb_Orders WHERE CustFID = ?", columns);
that gets executed here
var result = await conn.QueryAsync<Order>(sQuery, new { ID = Custid });
But say that I'm searching based on parameters chosen by the user. I can build up the where clause, but how do I build up the anonymous array?
new { ID = Custid }
I want something like
var params = new {};
if (!String.IsNullOrWhiteSpace(username)) {
params += {username}
}

If you really want to have params like an anonymous type, you can use an ExpandoObject:-
dynamic params = new ExpandoObject();
if (!string.IsNullOrWhiteSpace(username)) {
params.Username = username;
}
Or if you want an array (and you don't know the length ahead of time), use a List<string>:-
var paramlist = new List<string>();
if (!string.IsNullOrWhiteSpace(username)) {
paramlist.Add("username");
}
var params = paramlist.ToArray();
However, if you are constructing the WHERE clause, you will always have a fixed number of parameters in your SQL statement anyway (or you'll have to construct it dynamically too).
One other method you can use when dynamically building a query for filtering is this:-
SELECT *
FROM vwweb_Orders
WHERE 1=1
AND (#custid IS NULL OR CustFID = #custid)
AND (#xyz IS NULL OR XYZ = #xyz)
-- etc
Then supply all the parameters to your QueryAsync call, and if any are null, they'll be skipped in the WHERE clause.

Maybe you can write your query to check for null/empty/non-zero values first otherwise evaluate the real values as follows:
public async Task<List<Order>> Execute(OrderQuery query)
{
var sql = $#"SELECT
...
FROM vwweb_Orders
WHERE #{nameof(query.CustomerId)} <= 0 OR customer_id = #{nameof(query.CustomerId)}
AND ISNULL(#{nameof(query.CustomerName)}, '') = '' OR customer_name = #{nameof(query.CustomerName)}";
return await conn.QueryAsync<Order>(sql, new { query.CustomerId, query.CustomerName});
}
public class OrderQuery
{
public int CustomerId { get; set; }
public string CustomerName { get; set; }
}
public class Order
{
}

Related

Conversion inside a lambda expression - LINQ to Entities does not recognize the method 'Int32 ToInt32(System.String)' method [duplicate]

I am trying to Query Database Context using Linq to Entities and I am getting this error:
LINQ to Entities does not recognize the method 'Int32 Int32(System.String)' method, and this method cannot be translated into a store expression.`
Code:
public IEnumerable<CourseNames> GetCourseName()
{
var course = from o in entities.UniversityCourses
select new CourseNames
{
CourseID = Convert.ToInt32(o.CourseID),
CourseName = o.CourseName,
};
return course.ToList();
}
I tried like this after seeing this
public IEnumerable<CourseNames> GetCourseName()
{
var temp = Convert.ToInt32(o.CourseID);
var course = from o in entities.UniversityCourses
select new CourseNames
{
CourseID = temp,
CourseName = o.CourseName,
};
return course.ToList();
}
But it throws an error:
"The name 'o' does not exist in the current context"
This is my code for the class GetCourseName
namespace IronwoodWeb
{
public class CourseNames
{
public int CourseID { get; set; }
public string CourseName { get; set; }
}
}
public IEnumerable<CourseNames> GetCourseName()
{
var courses = from o in entities.UniversityCourses
select new { o.CourseID, o.CourseName };
return courses.ToList() // now we have in-memory query
.Select(c => new CourseNames()
{
CourseID = Convert.ToInt32(c.CourseID), // OK
CourseName = c.CourseName
});
}
If you dont want to materialize the query (retrieve the data) you can use cast (i.e. (int) o.CourseId). Is converted to SQL CAST AS statement.
You could also bring back the value as a string (as it is apparently stored) and then Convert it after.
The error on 'o' being out of the context is that you are only declaring o in the Linq query and it can only be referenced in that scope.
Explicit conversion is simple and works: (int)o.CourseID
var course = from o in entities.UniversityCourses
select new CourseNames
{
CourseID = (int)o.CourseID,
CourseName = o.CourseName,
};

Insert enum as string using Dapper.Contrib

I've just started using Dapper.Contrib to help me with inserts and gets but since my enums are stored as strings in the database (for several reasons) I'm having trouble with inserts. Dapper works seamlessly with string enums when reading, but inserts will always put the ordinal value into the database.
I've read many proposals to Dapper for that, and quite a few issues opened but didn't find a working solution. My simplified class looks like the following:
public class Person {
public long Id { get; set; }
public string Name { get; set; }
public Gender Gender { get; set; }
}
public enum Gender { Female, Male, TransWoman, TransMan }
I was expecting I could configure Dapper.Contrib to issue Inserts using enum names instead of ordinal values, so that the code bellow would magically work and insert 'Male' in the varchar(20) database field Gender:
void InsertPersonFelipe(SqlConnection conn) {
var person = new Person { Name = "Felipe", Gender = Gender.Male };
conn.Insert(person);
}
Is there a way to add custom mapping for typeof(Gender)?
Or, better yet, does Dapper.Contrib provides a configuration to make it use enum names instead of their ordinal values?
I've written an extension method to handle translating the enum into a string and takes into account the Table and Computed attributes in Dapper.Contrib. You could just as easily take these out if you didn't want to reference Dapper.Contrib.
Usage:
using (var sql = new SqlConnection(_connString))
{
sql.Open();
sql.InsertB(person);
}
Extension method:
public static long InsertB<T>(this SqlConnection sqlConnection, T obj)
{
Dictionary<string, object> propertyValuesMap = new Dictionary<string, object>();
var columns = new StringBuilder();
var values = new StringBuilder();
var tableName = ((TableAttribute)obj.GetType().GetCustomAttribute(typeof(TableAttribute))).Name;
var relevantProperties = obj.GetType().GetProperties().Where(x => !Attribute.IsDefined(x, typeof(ComputedAttribute))).ToList();
for (int i = 0; i < relevantProperties.Count(); i++)
{
object val = null;
var propertyInfo = relevantProperties[i];
if (propertyInfo.PropertyType.IsEnum)
{
val = Enum.GetName(propertyInfo.PropertyType, propertyInfo.GetValue(obj));
}
else
{
val = propertyInfo.GetValue(obj);
}
propertyValuesMap.Add(propertyInfo.Name, val);
var propName = i == relevantProperties.Count() - 1 ? $"{propertyInfo.Name}" : $"{propertyInfo.Name},";
columns.Append(propName);
values.Append($"#{propName}");
}
return sqlConnection.Execute($"Insert Into {tableName} ({columns}) values ({values})", propertyValuesMap);
}
I rewrote Dan's answer to be a little more modern C# and to not try to insert ID (because I had autoincrementing identity columns), as well as take a tablename instead of looking at attribute.
public static long InsertB<T>(this SqlConnection sqlConnection, T obj, string tableName)
{
Dictionary<string, object> propertyValuesMap = new Dictionary<string, object>();
var columnList = new List<String>();
var valueList = new List<String>();
var relevantProperties = obj.GetType().GetProperties().Where(x => !Attribute.IsDefined(x, typeof(ComputedAttribute))).ToList();
foreach (var propertyInfo in relevantProperties)
{
if (propertyInfo.Name.ToLower() == "id") continue; // do not try to insert id
var val = propertyInfo.PropertyType.IsEnum
? Enum.GetName(propertyInfo.PropertyType, propertyInfo.GetValue(obj))
: propertyInfo.GetValue(obj);
propertyValuesMap.Add(propertyInfo.Name, val);
columnList.Add(propertyInfo.Name);
valueList.Add($"#{propertyInfo.Name}");
}
return sqlConnection.Execute($"Insert Into {tableName} ({String.Join(", ", columnList)}) values ({String.Join(", ", valueList)})", propertyValuesMap);
}

Dapper building parameter list

I have this class:
public class Parameters
{
public string UserId {get;set;}
public string OrgId {get;set;}
public string Roles {get;set;}
}
It gets deserialised from a JSON string. So some of the properties are null.
What are the best ways to build up the params list to pass to Dapper.
At the moment my logic for building up the params string to tag on the end of the SQL statement goes like this :
var parameters = string.Empty;
var parametersObj = new { };
if (query.Parameters != null)
{
if (!string.IsNullOrWhiteSpace(query.Parameters.UserId))
{
parameters = string.Format("{0} UserId = #UserId", parameters);
// parametersObj.UserId =
}
if (!string.IsNullOrWhiteSpace(query.Parameters.OrganisationIdentifier))
{
parameters = string.Format("{0}, OrganisationIdentifier = #OrganisationIdentifier", parameters);
}
if (!string.IsNullOrWhiteSpace(query.Parameters.Roles))
{
parameters = string.Format("{0}, Roles = #Roles", parameters);
}
}
var sqlString = string.Format("exec {0} {1}", query.DbObjectName, parameters);
conn.QueryAsync<dynamic>(sqlString, )
As you can see with the parametersObj I was going with the JavaScript way of dynamically building an object. If I did do this with dynamic instead of an object - will it still work?
example:
var parameters = string.Empty;
dynamic parametersObj = new { };
if (query.Parameters != null)
{
if (!string.IsNullOrWhiteSpace(query.Parameters.UserId))
{
parameters = string.Format("{0} UserId = #UserId", parameters);
parametersObj.UserId = query.Parameters.UserId;
}
if (!string.IsNullOrWhiteSpace(query.Parameters.OrganisationIdentifier))
{
parameters = string.Format("{0} OrganisationIdentifier = #OrganisationIdentifier ", parameters);
parametersObj.OrganisationIdentifier= query.Parameters.OrganisationIdentifier;
}
if (!string.IsNullOrWhiteSpace(query.Parameters.Roles))
{
parameters = string.Format("{0} Roles = #Roles", parameters);
parametersObj.Roles= query.Parameters.Roles;
}
}
var sqlString = string.Format("exec {0} {1}", query.DbObjectName, parameters);
conn.QueryAsync<dynamic>(sqlString, parametersObj);
I think the second example will work when you change
dynamic parametersObj = new {};
to
dynamic parametersObj = new ExpandoObject();
and the query to
conn.QueryAsync(sqlString, new
{
UserId = parametersObj.UserId,
...
};
NOTE: filling in the dynamic object like
conn.QueryAsync(sqlString, parametersObj);
will raise the error
Extension methods cannot be dynamically dispatched
You don't need to do anything: just pass your object as the parameters. Dapper will only pass in properties/parameters that it can identify in the query... and even if it passed them all in: it understands null.
The object is fine.
...QueryAsync(sql, query.Parameters)...

Get fields from anonymous type

I have this class:
public class allFields
{
public string EAN { get; set; }
public string title { get; set; }
public string qty { get; set; }
public string price { get; set; }
public DateTime date { get; set; }
}
And a function that return an anonymous type:
public IEnumerable<object> stockEtatQty()
{
List<allFields> afList = new List<allFields>();
var query = from x in ctx.book
where x.qty > 0
select x;
foreach (var item in query)
{
allFields af = new allFields();
af.EAN = item.EAN;
af.title = item.Titre;
af.qty = ""+item.Quantite;
afList.Add(af);
}
var q = from x in afList
select new { EAN=x.EAN, Title=x.title, Quantity=x.qty };
return q; //q is a IEnumerable<'a> where a is new {string EAN, string Title, string Quantity}
}
In my WinForm a use this function as below:
private void QuantityToolStripMenuItem_Click(object sender, EventArgs e)
{
ServiceStock sstock = new ServiceStock();
var q = sstock.stockEtatQty().ToList();// q is a list<object>
string str = "";
foreach (var item in q)
{
str += item + Environment.NewLine;
}
MessageBox.Show(str);
}
The result is:
{ EAN = 1, Title = CSharp Security, Quantity = 970 }
{ EAN = 2, Title = MISC, Quantity = 100 }
...
What I want?
I want not like the result above, but separate each field apart of the item in the loop foreach, e.g get item.EAN, item.Title and item.Quantity.
If there is no solution for my problem I would like to know an alternative,
Thanks for help.
The obvious solution is to create a custom type (let's call it BookInfo) and return a IEnumerable<BookInfo> instead of a IEnumerable<object> (and maybe override ToString if you want to put the formatting into this class itself).
Then you can easily format the output.
public class BookInfo
{
public string EAN {get;set;}
public string Title {get;set;}
public int Quantity {get;set;}
}
public IEnumerable<BookInfo> stockEtatQty()
{
...
var q = from x in afList
select new BookInfo { EAN=x.EAN, Title=x.title, Quantity=x.qty };
return q;
}
private void QuantityToolStripMenuItem_Click(object sender, EventArgs e)
{
ServiceStock sstock = new ServiceStock();
var q = sstock.stockEtatQty();
var message = string.Join(Environment.NewLine,
q.Select(item => String.Format("{0} {1} {2}", item.EAN, item.Title, item.Quantity)));
MessageBox.Show(message);
}
Since the static type information about the object of anonymous type is lost by the time that you exit stockEtatQty() method, you could cast the object to dynamic and access fields like this:
str = string.Join(Environment.NewLine, q.Cast<dynamic>().Select(item =>
string.Format("{0} {1} {2}", item.EAN, item.Title, item.Quantity)
));
The cast to dynamic tells the compiler that EAN, Title, and Quantity need to be resolved at runtime.
Note that I also replaced the foreach loop with a call to string.Join to improve performance: repeated string concatenation creates unnecessary partial string objects, which string.Join avoids. Another solution would be to use StringBuider instead of string concatenation +=.
stockEtatQty is in a project (Service) and QuantityToolStripMenuItem_Click is in another project (View)
Unfortunately, this means that you would not be able to use anonymous types: anonymous types are generated with internal visibility, limiting their use to the assembly in which they are produced. You can use a work-around based on ExpandoObject described in this answer:
var q = afList.Select(x => {
dynamic res = new ExpandoObject();
res.EAN=x.EAN;
res.Title=x.title;
res.Quantity=x.qty;
return res;
});
Create a new class that represents the new object structure and return that.
var q = from x in afList
select new SmallerType { EAN=x.EAN, Title=x.title, Quantity=x.qty };
WinForm Function
foreach (SmallerType item in q)
{
//
}
You can use collection of dynamic objects instead of simple objects as return type of your method:
public IEnumerable<dynamic> stockEtatQty()
Then you will not have IntelliSense but at runtime properties will be found:
foreach (var item in sstock.stockEtatQty())
str += String.Format("{0}", item.EAN) + Environment.NewLine;
But I suggest you to create custom class with EAN, Title and Quantity properties. Or just use your allFields instead of anonymous objects.
Consider also to use StringBuilder for string creation to avoid creating lot of in-memory strings:
var builder = new StringBuilder();
foreach (var item in sstock.stockEtatQty())
builder.AppendFormat("{0}{1}", item.EAN, Environment.NewLine);
MessageBox.Show(builder.ToString());

Learning to use Dapper: throws Invalid cast error

VS2013 c# windows form.
I am learning Dapper and this is my 1st shot at it:
I have a simple class:
public class R
{
public int RID { get; set; }
public int RType { get; set; }
public string CC { get; set; }
public string RI { get; set; }
.
.
.
}
private void _setR(string rID)
{
int r_ID = Convert.ToInt16(requestID);
MY_R = new R();
SqlConnection c = new SqlConnection(Connection string);
c.Open();
var v= c.Query<R>("select RID, RType, CC, RI, .,.,., " +
"from Db_View_R where RID=#r_ID",
new { #r_ID = r_ID }) ;
c.Close();
MY_R = (R)v ; <--------- Invalid cast error here
}
The query is simple: a few columns from a view. Returns only 1 row. Not sure what am I missing here.
Thanks in advance
Extension method Query<T> returns IEnumerable<T>. So you definitely can't assign value of type IEnumerable<T> to variable of type T. You should take only one item from sequence:
MY_R = v.FirstOrDefault(); // or First, Single, SingleOrDefault
Actually with improved naming your code should look like:
var sql = "SELECT RID, RType, CC, RI FROM Db_View_R where RID = #id";
using(var conn = new SqlConnection(ConnectionString))
{
MY_R = conn.Query<R>(sql, new { id = Convert.ToInt16(requestID) })
.FirstOrDefault();
}
I still don't like names like R (it probably should be Request) but its already much more readable and safe (you should wrap connection usage in using statement).
use SingleOrDefault()
var v= c.Query<R>("select RID, RType, CC, RI, .,.,., " +
"from Db_View_R where RID=#r_ID",
new { #r_ID = r_ID }).SingleOrDefault();

Categories

Resources