I have an enum like:
public enum AccountStatus : byte
{
Locked = (byte)'L',
Active = (byte)'A',
Deleted = (byte)'D'
}
and would like to to store the char values in the database.
Is there a way in Dapper with type maps or otherwise to define which enum types should be mapped to their char values (for read/update/insert)?
The anonymous type member declarator is also preventing me from casting the property to a char directly in the query:
As mentioned by #TyCobb in the comments, this isn't an issue with storing an Enum as a char, but rather with how anonymous type properties are declared.
Anonymous types can only infer and generate names for "simple property references" where no transformation is done to the property during assignment, such as
new { account.AccountStatus }
As soon as you transform the data or need to rename the property, you must explicitly declare the property name:
new { Status = (char)account.AccountStatus }
In regards to reads, you can map to a char via dapper, then cast as your enum in your select (or transform the data in whatever way is most appropriate):
var result = connection.Query<char>(
"select char column from table where Status = #Status",
new {Status = (char)account.AccountStatus}
).Select(x => (AccountStatus)x).FirstOrDefault();
In addition, Dapper also handles AccountStatus : byte cases natively, so you may be able to directly return the value as your enum:
var result = connection.Query<AccountStatus>(
"select char column from table where Status = #Status",
new {Status = (char)account.AccountStatus}
).FirstOrDefault();
Related
Long time programmer, new to C#. I am in the process of converting a solution from VB.net to C#. This particular function "getdata" returns values from the first row in a sql select. For this example I've simplified the code.
Due to the unknown datatypes being fetched from sql, the "getdata()" parms are objects. VB allows calling a function with any explicit datatype byref parms into objects, so I can send a string or int parm into an object and return it with no issues.
In C#, this method works for passing parms by value. Any type of byref (ref/in/out) the compiler arrors with "cannot convert from ref string to ref object"
What I've tried:
Changing all manner of parm ref/var type (in/out/ref/nothing)
Changing datatypes of variables from explicit to object works but produces lots of issues downstream. ie. I don't want to define everything as an object/I much prefer explicit datatypes.
explicit cast before calling using (object) before the variable name.
Changing everything to Dynamic types works but same issues as object.
My best solution, unfortunately, changes the functionality enough that it's going to cause issues with further solution conversion. I came up with returning the a/b/c variables as an anonymous object that are that set to the actual variables upon return to the calling function.
Is there any way that calling function parms can be explicitly typed and passed to an implicit data type like object?
If not, any better solution than returning anonymous type?
VB code -- working
Private Sub test()
Dim a$, b%, c$
getdata(1, a, b, c)
MsgBox($"a={a}, b={b}, c={c}")
Dim x As DateTime, y As String, z As String
getdata(2, x, y, z)
MsgBox($"x={x}, y={y}, z={z}")
End Sub
Private Sub getdata(opt As Integer, ByRef val0 As Object, ByRef Optional val1 As Object = Nothing, ByRef Optional val2 As Object = Nothing) As Boolean
'the real implementation of this function will accept sql string and return first row of data columns
'since fetched data will be of different types, parms are defined as objects
If opt = 1 Then
val0 = "Apples"
val1 = 2
val2 = "Oranges"
ElseIf opt = 2 Then
val0 = now
val1 = "Dogs"
val2 = "Cats"
End If
End Function
C# code -- compiler error -
I am hand converting VB code to help with the C# learning curve but my last ditch solution was to use a VB->C# converter which is produced here.
private void test()
{
string a = null;
int b = 0;
string c = null;
getdata(1, ref a, ref b, ref c); ************** error occurs here
MessageBox.Show($"a={a}, b={b}, c={c}"); "cannot convert from ref string to ref object"
DateTime x = default(DateTime);
string y = null;
string z = null;
getdata(2, ref x, ref y, ref z); ************** error occurs here
MessageBox.Show($"x={x}, y={y}, z={z}"); "cannot convert from ref string to ref object"
}
private bool getdata(int opt, ref object val0, ref object val1, ref object val2)
{
//real function will accept sql string and return first row of data columns
//since fetched data will be of different types, parms are defined as objects
if (opt == 1)
{
val0 = "Apples";
val1 = 2;
val2 = "Oranges";
}
else if (opt == 2)
{
val0 = DateTime.Now;
val1 = "Dogs";
val2 = "Cats";
}
return true;
}
There are some fundamental things in this method which make me believe you should spend more time refactoring vs direct translation. Restoring type safety is one of them (VB.Net made it easier to hide some poor type safety choices in a file where you have Option Strict Off for a couple modules), but this also REALLY scares me:
//real function will accept sql string
Functions like that tend to cause HUGE security problems, as well as other issues, especially when you also have a bunch of arguments for output values. If you're not well-versed in SQL Injection, NOW is the time to learn about it. You must also provide a way to include input data for the SQL command that is completely separate from the SQL string itself, or you'll eventually find yourself in big trouble.
This code needs some serious refactoring, not just simple conversion!
I suggest refactoring around a method like this:
public class DB
{
private static string ConnectionString {get;} = "connection string here";
private static IEnumerable<IDataRecord> getdata(string sql, Action<SqlParameterCollection> addParameters)
{
using (var cn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
rdr.Close();
}
}
}
}
Notice the method is private; this is because we're not done building the class yet. Once you create this class, and remove the old getdata() method, everywhere that currently calls this method will turn into a compiler error. This is good; it gives you an easy way to find all those places you had poor code of this type.
So now we start looking at the new compiler errors. Each one will represent a place where you used to call getdata(). There's probably other code nearby to build up the SQL string. You want to move each of these sections to a new static method in the DB class.
One of those methods might look something like this:
public static IDataRecord MyNewDataMethod(int ID)
{
string SQL = "SELECT ... WHERE ID = #ID";
return getdata(SQL, p => {
p.Add("#ID", SqlDbType.Int).Value = ID;
}).FirstOrDefault();
}
But we can (and should) take this a step further. Typically, these results will represent objects of some type. After all, they had to come from a table, or at least a set of related tables. If you don't already have a class for each of these things, you probably should. These classes should have static methods named something like FromDataRecord(), which accept an IDataRecord or DataRow as input and return the class type as output. They are Factory methods. And now we update the methods to look more like this:
public static MyObjectType MyNewDataMethod(int MyObjectTypeID)
{
string SQL = "SELECT ... WHERE ID = #ID";
return getdata(SQL, p => {
p.Add("#ID", SqlDbType.Int).Value = MyObjectTypeID;
}).Select(MyObjectType.FromDataRecord).FirstOrDefault();
}
Here's another example that might return several records:
public static IEnumerable<MyObjectType> MyNewDataMethod(string SearchKey)
{
string SQL = "SELECT ... WHERE SearchColumn = #SearchKey + '%'";
return getdata(SQL, p => {
p.Add("#SearchKey", SqlDbType.NVarChar, 80).Value = SearchKey;
}).Select(MyObjectType.FromDataRecord);
}
If you find you have a lot of these methods, you can convert the private getdata() method to protected, put it in it's own class library project in the solution, and use separate public classes in the same project that can still access that method to divide the data access into logical areas.
I agree with Joel's sentiments; throw this code away rather than trying to salvage it. It's garbage.
If you add a reference to the Nuget package Dapper, your life will get a lot easier. With Dapper, you write SQL and it maps to objects for you. It looks something like this:
using(var c = new SqlConnection(connection_string_here){
var person = c.QueryFirst<(string Na, string Ad, int Ag)>("SELECT name, address, age FORM person WHERE id = #id", new { id = 123 });
}
There's a lot going on in this, so i'll unpack it:
The first line just creates a database conenction in a using, so it will be disposed of. You don't need to bother about anything else; Dapper will open the connection, use it, close it
The second line has some parts:
var person = - like Dim x = 1 in VB, var declares a variable that is type-detected by the compiler from whatever type is on the right hand side
c.QueryFirst<(string Na, string Ad, int Ag)> - QueryFirst is a Dapper extension method that runs a select query and pulls the first row. Dapper maps the query columns to the type you give in angle brackets. Here I've given a ValueTuple which is a way to get the C# compiler to "fake" a class for you based on the ValueTuple class. A discussion about how it works is a bit out of scope, but suffice to say when the compiler encounters (string X, string Y, int Z) it transforms behind the scenes into something that you can refer to as an object with those named/typed properties. Suffice to say, when all is done, you'll be able to say person.Na or person.Ad in your code
"SELECT name, address, age FORM person WHERE id = #id" - is a parameterized SQL. It looks up a person with some ID and pulls their data out in that order, name, address, age. The order in this case is important because AFIAWA dapper maps ValueTuples positionally, not by name. This is different to other things (example later) where it does map by name. The tuple has name/address/age, so the query pulls them in the same order
new { id = 123 } - is creating a C# anonymous type, a sort of internal-only compiler generated class (different to a valuetuple) that has no name, but does have a property called id with value 123. Dapper will scan your SQL string looking for parameters, and find one called #id, so it will pull the value 123 out of the supplied anonymous type's id property (name based this time, not positional)
If you have a class Person lying around, as you probably should if you're doing any reasonable amount of database-to-c#-and-back-again work, then the call can look like this:
class Person{
public string Name {get; set;}
public string Address {get; set;}
public int Age {get; set;}
}
...
c.QueryFirst<Person>("SELECT age, name, address FROM ... WHERE id = #i", new { i=123 });
This time we pass a full class Person - Dapper will map the proeprties by name, which is why they're in a different order in the SQL (it could even be SELECT * and dapper will just ignore the 10+ columns in our person table that arent represented by an class property) and it still works. If your SQL names don't match your class names, the simplest thing to do is alias them in the SQL:
c.QueryFirst<Person>("SELECT firstname+' '+lastname as name, ... FROM ... WHERE id = #i", new { i=123 });
I don't think there's an elegant solution - you can keep your 'getdata' method unchanged if you add extra baggage to every method call:
private void test()
{
string a = null;
int b = 0;
string c = null;
object temp_a = a;
object temp_b = b;
object temp_c = c;
getdata(1, ref temp_a, ref temp_b, ref temp_c);
a = (string)temp_a;
b = (int)temp_b;
c = (string)temp_c;
MessageBox.Show($"a={a}, b={b}, c={c}");
}
I ended up with the following
Hashtable gd = getData();
string location = (string)gd["location"];
int locationid = (int)gd["locationid"];
string frutata = (string)gd["frutata"];
where getData() just builds a hashtable of objects with the datareader columns.
My end goal was to create a simple callable function w/o a bunch of code to handle return values.
Dapper seems pretty cool and I will def. check that out.
Here is the enum that I have defined:
enum LogicalChange
{
List = SyntaxKind.List,
TildeToken = SyntaxKind.TildeToken,
ExclamationToken = SyntaxKind.ExclamationToken,
DollarToken = SyntaxKind.DollarToken,
PercentToken = SyntaxKind.PercentToken,
CaretToken = SyntaxKind.CaretToken,
AmpersandToken = SyntaxKind.AmpersandToken,
AsteriskToken = SyntaxKind.AsteriskToken,
MinusToken = SyntaxKind.MinusToken,
PlusToken = SyntaxKind.PlusToken,
EqualsToken = SyntaxKind.EqualsToken
}
I have a set of commands that should execute only if change.After.Parent.Kind() (which returns a SyntaxKind) is defined in the enum LogicalChange.
What I have tried so far is -
Enum.IsDefined(typeof(LogicalChange), change.After.Parent.Kind())
but this generates an exception. I don't want to do string comparison. Is there any other way of achieving this?
It is not a simple name or string comparison, you need to cast it to the Enum Type you are comparing it to. This should not trigger an exception:
if (Enum.IsDefined(typeof(LogicalChange), (LogicalChange)change.After.Parent.Kind()))
{
}
IsDefined method allows to you to send three type as value:
own enum type
int
string
so you can use these ways:
1. Enum.IsDefined(typeof(LogicalChange), (LogicalChange)change.After.Parent.Kind())
2. Enum.IsDefined(typeof(LogicalChange), (int)change.After.Parent.Kind())
3. Enum.IsDefined(typeof(LogicalChange), change.After.Parent.Kind().ToString())
note: way no 3 is correct for you because you choose same name in both Enums, But it's better to not use it.
public ActionResult Votation(int id=0)
{
var events = db.Events_Info_tbl.Where(x => x.is_active == true).FirstOrDefault();
//query the first category
List<CampaignManager_tbl> candidates = new List<CampaignManager_tbl>();
candidates = (from cat in db.Events_Category_tbl
join can in db.Candidates_Info_tbl
on cat.events_category_id equals can.events_category_id
where cat.events_info_id == events.events_info_id
select new CampaignManager_tbl {
events_category_name = cat.events_category_name,
candidates_fullname = can.candidates_fullname,
candidates_info_id = can.candidates_info_id,
vote_no = cat.vote_no.Value,
isSelected = can.isSelected.Value,
events_category_id = cat.events_category_id
}).ToList();
return View(candidates);
}
This code was working before but now I've got this error: The cast to value type 'Boolean' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
What's wrong with this? I didn't change any of my codes before. It just this time I've got an error.
I ran into this error because of a different problem. In my query I am selecting specific columns from an account member and its associated account:
DBContext.Set<AccountMember>()
.Include(am => am.Account)
.Select(am => new
{
am.ID,
am.IsPrimary,
Account = new { am.Account.ID, am.Account.DoNotEmail }
});
Both of my boolean properties, IsPrimary and DoNotEmail are non-nullable so the resulting property type of their properties in the anonymous type is bool. The exception occurs when an AccountMember does not have an Account. Since am.Account is null, the expression am.Account.DoNotEmail returns null. But the anonymous type's DoNotEmail property inferred it was of type bool which cannot be set to null. The solution is to give it a hint that this should be a nullable bool by casting the property to a bool?:
DBContext.Set<AccountMember>()
.Include(am => am.Account)
.Select(am => new
{
am.ID,
am.IsPrimary,
Account = new { am.Account.ID, DoNotEmail = (bool?)am.Account.DoNotEmail }
});
Hapily EF is smart enough to ignore this cast and not blow up trying to convert it to SQL.
I'm going out on a limb here, but I'd guess it has to do with this line:
isSelected = can.isSelected.Value,
If when the two tables are joined, one of them does not return a value for that particular field, it will be null. It appears the type here is a nullable of some type - probably bool?, based on the naming - and that would match your error.
Try to replace it with the following:
isSelected = can.isSelected.HasValue ? can.isSelected.Value : false,
Of course, you can replace the default false here with true, if that makes more sense in your case.
Edit: Note that you should probably do the same for the other column you use in a similar way (I'm assuming this is returning an int?):
vote_no = cat.vote_no.HasValue ? cat.vote_no.Value : 0
Is db.Candidates_Info_tbl.IsSelected a bit field?
If so, the query could likely result in
isSelected = can.isSelected.Value,
with isSelected being null, whereas you probably declared isSelected as bool in CampaignManager_tbl class.
Change the isSelected to bool in the CampaignManager_tbl class and try again.
if your field type is int while some value is null, then you can use case-when and convert to bit like below
SELECT CASE WHEN FieldName IS NOT NULL THEN CONVERT(BIT,FieldName) ELSE CONVERT(BIT,0) END AS NewFieldName
I have a method call like this:
public bool IsAlright(...)
{
var myColumns = // some linq query.ToArray();
var theirColumns = (from row in System.IO.File.ReadAllLines(#"ConfigColumns.txt")
where !string.IsNullOrWhiteSpace(row)
select new { TheColumn = row.ToUpper() }).ToArray();
bool isAlright = // join those two above and see if they have anything in commmon.FirstOrDefault
return isAlright;
}
But I think reading the text file every time is a performance hit and I can do it just once.
So I am thinking of pulling it out but how? it is a "var" and I don't know how to pull out a "var" and define it out of the method scope like a cached object or something?
The var keyword is not a type, it just means the compiler will infer the type based on the type of the right-hand side expression.
The type of your var is an array of an anonymous type. You can't declare a field or method of an anonymous type (unless you use object or dynamic), so you need to declare a type that matches the interface of your anonymous object.
In your case, since you're only storing a string, I would recommend, just returning an array of string instead of using an object:
string[] theirColumns = (from row in System.IO.File.ReadAllLines(#"ConfigColumns.txt")
where !string.IsNullOrWhiteSpace(row)
select row.ToUpper()).ToArray();
You can now pull theirColumns out to a field in your class.
You cannot use var outside of method scope. In your case you can either use string[] instead of custom anonymous object, or create a custom class. Then promote the variable to class field and use it in any other method that you need.
First, Declare a class outside the method.
public class YourClassName
{
public string TheColumn {set;get;}
}
I assume each row is just a string, change the type if it's not
change your theirColumns to
YourClassName[] theirColumns = (from row in System.IO.File.ReadAllLines(#"ConfigColumns.txt")
where !string.IsNullOrWhiteSpace(row)
select new YourClassName { TheColumn = row.ToUpper() }).ToArray();
extract it outside the method.
I have a field in my database table that use to store an enumeration value, e.g.:
create table MyTable (
...
Status tinyint not null,
...
)
and in my C# class I have
public enum TStatus : byte {
Pending = 1
Active = 2,
Inactive = 3,
}
public TStatus MyStatus {
get { return (TStatus)Status; }
set { Status = (byte)value; }
}
now I want to write a Linq query that uses the MyStatus property of MyTable e.g.
var q = MyDataContext.GetTable<MyTable>().Where(t => t.MyStatus == TStatus.Active);
but of course, Linq doesn't know how to interpret MyStatus as SQL.
What do I need to do to MyStatus in order for it to work in LinqToSQL?
Check out this link:
http://dotnet.org.za/hiltong/archive/2008/08/06/using-enums-with-linq-to-sql.aspx
As links die - and at least for me this one did die - here is the important part:
[When adding the column to the entity] by default, the Type will come up as an "int (System.Int32)", but you can change it to the fully-qualified type of the enum (in my case, ConsoleApplication1.CustomerType). BUT, in order to locate it fully, you have to add the global identifier, as follows: global::ConsoleApplication1.CustomerType , so type that as is (but the equivalent for your namespace) into the textbox
Don't have a compiler handy, but I think if you cast your enum to an int, it will work.
So try:
var q = MyDataContext.GetTable().Where(t => t.MyStatus == (int)TStatus.Active);