I have a simple query and Poco that I'm using with Dapper like so:
var jc = this.dbConnection.ExecuteScalar<JcUser>("SELECT loginid as Username,Password,coalesce(CustomerId,0) as CustomerId,TextProfileId,UxProfileId from \"user\" where id = #id", new {id = id});
Poco:
public class JcUser
{
public string UserName { get; set; }
public string Password { get; set; }
public int CustomerId{ get; set; }
public int TextProfileId { get; set; }
public int UxProfileId { get; set; }
}
When this executes it throws an exception with the message
Value is not a convertible object: System.String to JcUser
The stack trace ends up at: at System.Convert.ToType (System.Object value, System.Type conversionType, IFormatProvider provider, Boolean try_target_to_type)
Any ideas why its doing this?
Thanks
UPDATE: Using var jc = this.dbConnection.Query<JcUser>("SELECT loginid as Username,Password,coalesce(CustomerId,0) as CustomerId,TextProfileId,UxProfileId from \"user\" where id = #id", new {id = id}).First(); appears to work. I also realise I'm a moron and ExecuteScalar is only for one value. However, is my update the best way to retrieve only one row?
ExecuteScalar maps to the ADO.NET method of the same name. It returns at most one cell: one grid, one row, one column. As such, it is not intended for use with complex objects, and cannot work correctly in your case as you have multiple columns.
Dapper assumes you would only use that with simple types like int, string etc.
In your case, use:
var jc = this.dbConnection.Query<JcUser>(
sql, args).SingleOrDefault();
If you want to avoid a hidden List<> allocation you could also pass buffered: false.
Related
I am working on connecting to SQL database and setting variables in my C# application to match returned values from a Dapper Query.
I am able to return the correct row information (I used a datagridview to show that i get the correct row, and when i debug i see the right data) but how do I set a program variable to just one of the columns? here is some code showing my process
Connect & run stored procedure:
using (SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["MfgDataCollector"].ToString()))
{
DynamicParameters param = new DynamicParameters();
param.Add("#UserID", txt_userid.Text.Trim());
List<User> userinfo = conn.Query<User>("GetUserInfo", param, commandType: CommandType.StoredProcedure).ToList<User>();
Variables.UserID = txt_userid.Text.Trim();
datagridview1.DataSource = userinfo; //this displays the right information, however I want to store the information as a public variable(like above)
Variables.Userfull = userinfo; //when debugging this shows I have the right information but its all columns of user
User Class:
class User
{
public string UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int AccessLevel { get; set; }
public int FirstUse { get; set; }
public string Password { get; set; }
public int LoginCounter { get; set; }
public string LastLogin { get; set; }
public string FirstLogin { get; set; }
public string UserFullName { get; set; }
}
EDIT
per answer, I have tried to switch the command to executescalar with this code:
var userinfo = conn.ExecuteScalar<User>("GetUserInfo", param, commandType: CommandType.StoredProcedure);
This allows me to set
Variables.UserID = userinfo.UserID;
without red-squiggle lines however during run-time i get an error on the execute scalar of "System.InvalidCastException: 'Invalid cast from 'System.String' to 'Name_Space.User'.'
I have checked the User Class to ensure the data type matches with the Database and i see no problems there, I'm not sure what I'm doing wrong?
If you want value from only one column of only one row, use ExecuteScalar which is similar to ADO.NET ExecuteScalar. It will return object with which you have to deal further.
string sql = "SELECT COL1 FROM Table1 WHERE ID = 1";
//OR
//string sql = "SELECT TOP 1 COL1 FROM Table1";
//OR similar
object colValue = conn.ExecuteScalar(sql, ....);
If matching record not found, return value will be null.
Check Dapper documentation for other generic variants of method:
public static T ExecuteScalar<T>(this IDbConnection cnn, CommandDefinition command);
If you want single column from multiple rows, the way you are doing this now is correct. Just modify your SQL query/Stored Procedure to return the same. Dapper will only map returned column. All other properties in your User will remain unassigned.
If you simply want to assign values to Variables.UserID instead of binding those using DataSource, then just do that like:
Variables.UserID = userinfo.UserID;
Variables.Userfull = userinfo.UserFullName;
//and so on....
So, complete code will be something like below:
using(SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["MfgDataCollector"].ToString()))
{
DynamicParameters param = new DynamicParameters();
param.Add("#UserID", txt_userid.Text.Trim());
User userinfo = conn.Query<User>("GetUserInfo", param, commandType: CommandType.StoredProcedure).ToList<User>().First();
Variables.UserID = userinfo.UserID;
Variables.Userfull = userinfo.UserFullName;
}
Below is my code for entity and a function where I need to map entity TblEmployee from a key value pair.
In foreach loop I am getting values based on keys, what should be the best approach to do it?
public class TblEmployee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
}
public int Create()
{
tblEmployee employee = new tblEmployee();
using (var ctx = new theparkeee_testEntities())
{
foreach (string key in HttpContext.Current.Request.Form.AllKeys)
{
string value = HttpContext.Current.Request.Form[key];
//how to map value from key value pair to entity employee.
}
}
}
You can use System.Reflection to get the Properties of an object by their name with Type.GetProperty(string name). After you got the PropertyInfo, you can use SetValue to assign a value to it.
foreach (string key in HttpContext.Current.Request.Form.AllKeys) {
// note that "value" is a reserved word, do not use it as variable name
string val = HttpContext.Current.Request.Form[key];
var propertyInfo = typeof(TblEmployee).GetProperty(key); // can maybe be moved outside of the loop
if (propertyInfo != null) {
propertyInfo.SetValue(employee, val);
}
}
This will work for string properties. If the property is of another type, you have to find the correct type (again, using reflection) and cast the string value before assigning it.
Note that this is not the correct approach to store data in MVC. You should not work with the Request.Form directly, instead your POST action should accept a ViewModel that can be mapped (e.g. using Automapper) to the DB entity. I.e. let the ASP ModelBinder do its work, instead of reinventing the wheel!
[HttpPost]
public ActionResult Submit(MyViewModel postData) {
var employee = Mapper.Map<TblEmployee>(postData);
_ctx.Employees.Add(employee);
_ctx.SaveChanges();
return new HttpStatusCodeResult((int)HttpStatusCode.OK);
}
So I'm using the C# nuget wrapper around Azure Search. My problem is I have a index of products:
public class ProductDocument
{
[System.ComponentModel.DataAnnotations.Key]
public string Key { get; set; }
[IsSearchable]
public string Sku { get; set; }
[IsSearchable]
public string Name { get; set; }
[IsSearchable]
public string FullDescription { get; set; }
[IsSearchable]
public List<CustomerSkuDocument> CustomerSkus { get; set; }
}
public class CustomerSkuDocument
{
[IsSearchable]
public int AccountId { get; set; }
[IsSearchable]
public string Sku { get; set; }
}
Example data would be:
new Product() { Key= 100,Name="Nail 101",Sku = "CCCCCCCC", CustomerSkus = new List<ProductCustomerSku>()
{
new ProductCustomerSku() {AccountId = 222, CustomerSku = "BBBB"},
new ProductCustomerSku() {AccountId = 333, CustomerSku = "EEEEEEE"}
}
So the problem is around CustomerSkuDocument.
When I Search I need to pass the AccountId in as well as the search term, however the AccountId is only used for when searching the ProductCustomerSkus.
Basically an Account can have different customer skus but it's only associated to that account - I don't want a separate index per account.
So my call would be something like /AccountId=222&term=BBBB which would find the match.
However /AccountId=333&term=BBBB would not find a match.
So I'm calling it like:
SearchParameters sp = new SearchParameters();
sp.SearchMode = SearchMode.Any;
sp.QueryType = QueryType.Full;
DocumentSearchResult<ProductDocument> results =
productIndexClient.Documents.Search<ProductDocument>(term, sp);
Where term is the normal search term, tried it with adding the AccountId but it doesn't work.
Azure Search does not support repeating data structures nested under a property of the outer document. We're working on this (see https://feedback.azure.com/forums/263029-azure-search/suggestions/6670910-modelling-complex-types-in-indexes), but we still have some work to do before we can release that.
Given that, the example you're showing is not probably indexing the nested parts. Can you post the search index definition you're using? While we work in direct support for complex types, you can see your options for approach here: https://learn.microsoft.com/en-us/azure/search/search-howto-complex-data-types
From the above you'll arribe at a index structure that will also guide your query options. If all you need is equality, perhaps you can simply include the accountId and the SKU in the same field and use a collection field so you can have multiple instances. For your query you would issue a search query that requires the accountId and has the rest as optional keywords.
I've started to play with Dapper.Net, and am really loving it so far - however, I have run into one problem.
Say that I have a POCO class like:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName { get { return FirstName + " " + LastName; } }
}
Now, using Dapper.Net and the Dapper.Net extensions, I want to simply load all instances of that data type from the DB by doing this:
string connectionString = CloudConfigurationManager.GetSetting("DBConnection");
using (SqlConnection cn = new SqlConnection(connectionString))
{
cn.Open();
IEnumerable<Types.Person> entities = cn.GetList<Types.Person>();
var forceMe = entities.ToList();
}
This works fine in the Linq setup, but when it hits the line with the .ToList(), which forces the evaluation, it blows up with "invalid column names" on FullName. Thinking that it might respect the Entity Framework DataAnnotations stuff for NotMapped, I tried adding a NotMapped attribute (after adding EF 5 to the project). This didn't work.
So, the question is, how do I tell Dapper.Net that a column isn't to be expected from the DB? Is this a problem with the extensions, trying to map a DB column for everything it sees in the model POCO? Do I need to revert to writing SQL, and explicitly ask for the columns that I want only, or is there a way to get an equivalent to NotMapped on the column?
I think the only way to to ignore certain properties from being mapped is to implement an auto class mapper, where you can specify your custom field mappings. For example:
public class CustomMapper : ClassMapper<Foo>
{
public CustomMapper()
{
Table("FooTable");
Map(f => f.Id).Column("FooId").Key(KeyType.Identity);
Map(f => f.DateOfBirth).Column("BirthDate");
Map(f => f.FirstName).Column("First");
Map(f => f.LastName).Column("Last");
Map(f => f.FullName).Ignore();
Map(f => f.Calculated).ReadOnly();
}
}
public class Foo
{
public int Id { get; set; }
public DateTime DateOfBirth { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get { return string.Format("{0} {1}", FirstName, LastName); }
}
}
In the above example, FullName is being ignored.
The auto-mapper also allows you to adjust table names, in case your POCO class names do not match table names.
Also, keep in mind that you must keep your custom maps in the same assembly as your POCO classes. The library uses reflection to find custom maps and it only scans one assembly.
Hope this helps,
Good luck
I added a RIA Domain Service method to return a simple NameValuePair of two properties from a table (and filtered on a key value).
It compiles fine, but blows up every time without giving a useful error.
What am I missing? (probably something really obvious)
e.g.:
public IQueryable<NameValuePair> GetNameValues(int keyId)
{
// NOTE: I can breakpoint here and the correct keyId is passed
// it blows up on returning from this method
return from p in this.ObjectContext.NameTable
where p.KeyId == keyId
select new NameValuePair(p.NameValue, p.NameType);
}
Simple NameValuePair Code:
public class NameValuePair
{
[Key]
public string Name { get; set; }
public string Value { get; set; }
public NameValuePair()
{
}
public NameValuePair( string name, string value)
{
this.Name = name;
this.Value = value;
}
}
Update:
I tried returning a query on a static list of NameValuePair objects and that works fine (but is not useful).
I tried this here and got the error: base {System.SystemException} = {"Only parameterless constructors and initializers are supported in LINQ to Entities."}
So you have to change it to create the object first, then pass the property values:
public IQueryable<NameValuePair> GetNameValues(int keyId)
{
return from p in this.ObjectContext.NameTable
where p.KeyId == keyId
select new NameValuePair {Name = p.NameValue, Value = p.NameType};
}