I've written a piece of code for converting a DataTable object(which is created from an uploaded excel) to a list of custom object(ExcelTemplateRow in my case). While this method works fine when the values are supplied as expected(in terms of data type of the corresponding column), the code breaks and throws the below error when I try to give a random value(and hence the data type changes):
Object of type 'System.String' cannot be converted to type 'System.Nullable`1[System.Double]'
Below is the method for converting DataTable object to list:
public static List<T> ConvertToList<T>(DataTable dt)
{
var columnNames = dt.Columns.Cast<DataColumn>().Select(c => c.ColumnName.ToLower()).ToList();
var trimmedColumnNames = new List<string>();
foreach (var columnName in columnNames)
{
trimmedColumnNames.Add(columnName.Trim().ToLower());
}
var properties = typeof(T).GetProperties();
return dt.AsEnumerable().Select(row => {
var objT = Activator.CreateInstance<T>();
foreach (var property in properties)
{
if (trimmedColumnNames.Contains(property.Name.Trim().ToLower()))
{
try
{
if(row[property.Name] != DBNull.Value)
{
property.SetValue(objT, row[property.Name]);
}
else
{
property.SetValue(objT, null);
}
}
catch (Exception ex)
{
throw ex;
}
}
}
return objT;
}).ToList();
}
My custom object looks somewhat like this:
public class ExcelTemplateRow
{
public string? Country {get; set;}
public double? Year {get; set;}
//....
//....
}
In the excel that I'm uploading, for the Year field, the code works fine when I give proper double values viz 2020, 2021, 2022 etc. but the code breaks when I give something wrong e.g 2023g. It then assumes I'm passing a string and hence the error. I tried by changing the declaration of the Year property to public object? Year {get; set;} but it doesn't help. I want to make the method robust enough to handle such scenarios. Any help and I'd be highly grateful.
There's a few things to consider here, but I'll try to be as terse as possible. When you say:
but the code breaks when I give something wrong e.g 2023g
This means the C# type system is working exactly as intended. A double should never be able to accept the value of "2023g". You probably want to store the year as a string instead. This may involve an intermediate stage of validation, where you import all of your data as a string (ExcelTemplateRow should all be strings in this case).
Then your work is ahead of you to validate the data, and then once you've handled any errors, only then can you think about using types such as Double?. Although, you probably don't want to store your year as a double, an int might be more appropriate. Or maybe it isn't; perhaps you want to store the errors, because that's what a user has entered. Some careful consideration is required here. Don't rush with the type system, let it work for you, thinking about which datatypes to use will help you design the rest of your code.
The issue here is the data coming from the excel, not your code who behave correctly.
Say you try using the T is double? then you double.tryParse(row) and when it fails you take the 4 first caracter of the string 2023g but what will happen if you have another property of type double? but expect only 2 or 3 numbers but the user put some dummy stuff how do you will manage that ? It's impossible.
Fix the Excel not your code ;)
Log the error, send a message to the user to fix the data :)
Related
I'm trying to use System.Reflections to get a DbSet<T> dynamically from its name.
What I've got right now is:
The DbSet name
The DbSet's Type stored on a variable
The issue I'm facing comes out when trying to use the dbcontext.Set<T>() method, since (these are my tries so far):
When I try to assign to <T> my DbSet Type, it throws me the following compilation error:
"XXX is a variable but is used like a type"
If I try with using both the Extension methods that you will find below in my code (which I made in order to try to get an IQueryable<T>), it returns a IQueryable<object>, which unfortunately is not what I am looking for, since of course when I try to manipulate it with further Reflections, it lacks of all the properties that the original class has…
What am I doing wrong? How can I get a DbSet<T>?
My code is the following, but of course, let me know if you need more infos, clarifications or code snippets.
My Controller's Method:
public bool MyMethod (string t, int id, string jsonupdate)
{
string _tableName = t;
Type _type = TypeFinder.FindType(_tableName); //returns the correct type
//FIRST TRY
//throws error: "_type is a variable but is used like a type"
var tableSet = _context.Set<_type>();
//SECOND TRY
//returns me an IQueryable<object>, I need an IQueryable<MyType>
var tableSet2 = _context.Set(_type);
//THIRD TRY
//always returns me am IQueryable<object>, I need an IQueryable<MyType>
var calcInstance = Activator.CreateInstance(_type);
var _tableSet3 = _context.Set2(calcInstance);
//...
}
Class ContextSetExtension
public static class ContextSetExtension
{
public static IQueryable<object> Set(this DbContext _context, Type t)
{
var res= _context.GetType().GetMethod("Set").MakeGenericMethod(t).Invoke(_context, null);
return (IQueryable<object>)res;
}
public static IQueryable<T>Set2<T>(this DbContext _context, T t)
{
var typo = t.GetType();
return (IQueryable<T>)_context.GetType().GetMethod("Set").MakeGenericMethod(typo).Invoke(_context, null);
}
}
EDIT Added TypeFinder's inner code.
In brief, this method does the same of Type.GetType, but searches Type on ALL the generated assemblies
public class TypeFinder
{
public TypeFinder()
{
}
public static Type FindType(string name)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
var result = (from elem in (from app in assemblies
select (from tip in app.GetTypes()
where tip.Name == name.Trim()
select tip).FirstOrDefault())
where elem != null
select elem).FirstOrDefault();
return result;
}
}
UPDATE as requested in the comments, here's the specific case:
In my DB i've got some tables which are really similar each other, so the idea was to create a dynamic table-update method which would be good for every table, just passing to this method the table name, the ID of the row to update and the JSON containing data to update.
So, in brief, I would perform some updates on the table given in input as DbSet type, updating the row with ID==id in input with the data contained inside the JSON, which will be parsed inside an object of type X(the same of dbset)/into a dictionary.
In pseudo-code:
public bool MyMethod (string t, int id, string jsonupdate)
{
string _tableName = t;
Type _type = TypeFinder.FindType(_tableName); //returns the correct type
//THIS DOESN'T WORKS, of course, since as said above:
//<<throws error: "_type is a variable but is used like a type">>
var tableSet = _context.Set<_type>();
//parsing the JSON
var newObj = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonupdate, _type);
//THIS OF COURSE DOESN'T WORKS TOO
//selecting the row to update:
var toUpdate = tableSet.Where(x => x.Id == id).FirstOrDefault();
if(toUpdate!=null)
{
var newProperties = newObj.GetType().GetProperties();
var toUpdateProperties = toUpdate.GetType().GetProperties();
foreach(var item in properties)
{
var temp = toUpdateProperties.Where(p => p.Name==item.Name)
{
//I write it really in briefand fast, without lots of checks.
//I think this is enough, I hope
temp.SetValue(toUpdate, item.GetValue());
}
}
_context.SaveChanges();
}
return false;
}
returns me an IQueryable<object>, I need an IQueryable<MyType>
Well, that will never work. Your IQueryable cannot be of type IQueryable<MyType>because that would mean the compiler would need to know what MyType is and that is not possible, because the whole point of this exercise is to decide that on runtime.
Maybe it's enough to know that those objects are in fact instances of MyType?
If not, I think you have painted yourself into a corner here and you are trying to figure out what paint to use to get out of there. Take a step back, it's probably not a technical problem. Why do you need to do this? Why do you have the conflicting needs of knowing the type at runtime only and knowing it at compile time?
You need to think about your requirements, not about the technical details.
I needed to dynamically load a single record from the database for each type in a list of known types, to print a test email when an admin is editing the template, so I did this:
List<object> args = new List<object>();
//...
//other stuff happens that isn't relevant to the OP, including adding a couple fixed items to args
//...
foreach (Type type in EmailSender.GetParameterTypes())
{
//skip anything already in the list
if (args.Any(a => a.GetType().IsAssignableFrom(type))) continue;
//dynamically get an item from the database for this type, safely assume that 1st column is the PK
string sql = dbContext.Set(type).Sql.Replace("SELECT", "SELECT TOP 1") + " ORDER BY 1 DESC";
var biff = dbContext.Set(type).SqlQuery(sql).AsNoTracking().ToListAsync().Result.First();
args.Add(biff);
}
Caveat: I know at least one record will exist for all entities I'm doing this for, and only one instance of each type may be passed to the email generator (which has a number of Debug.Asserts to test validity of implementation).
If you know the record ID you're looking for, rather than the entire table, you can use dbContext.Set(type).Find(). If you want the entire table of whatever type you've sussed out, you can just do this:
string sql = dbContext.Set(type).Sql; //append a WHERE clause here if needed/feasible, use reflection?
var biff = dbContext.Set(type).SqlQuery(sql).ToListAsync().Result;
Feels a little clunky, but it works. There is strangely no ToList without Async, but I can run synchronously here. In my case, it was essential to turn off Proxy Creation, but you look like you want to maintain a contextful state so you can write back to db. I'm doing a bunch of reflection later, so I don't really care about strong typing such a resulting collection (hence a List<object>). But once you have the collection (even just as object), you should be able to use System.Reflection as you are doing in your UPDATE sample code, since you know the type and can use SetValue with known/given property names in such a manner.
And I'm using .NET Framework, but hopefully this may translate over to .NET Core.
EDIT: tested and working:
public async Task<bool> MyMethod(string _type)
{
Type type = Type.GetType(_type);
var tableSet = _context.Set(type);
var list = await db.ToListAsync();
// do something
}
// pass the full namespace of class
var result = await MyMethod("Namespace.Models.MyClass")
IMPORTANT NOTE: your DbContext need to have the DbSet declared to work!
public class MyContext : DbContext
{
public DbSet<MyClass> MyClasses { get; set; }
}
I spend a lot of time querying a database and then building collections of objects from the query. For performance I tend to use a Datareader and the code looks something like:
while(rdr.Read()){
var myObj = new myObj();
myObj.Id = Int32.Parse(rdr["Id"].ToString();
//more populating of myObj from rdr
myObj.Created = (DateTime)rdr["Created"];
}
For objects like DateTime I simply cast the rdr value to the required class, but this can't be done for value types like int hence the (IMHO) laborious ToString() followed by Int.Parse(...)
Of course there is an alternative:
myObj.Id = rdr.GetInt32(rdr.GetOrdinal("Id"));
which looks cleaner and doesn't involve a call to ToString().
A colleague and I were discussing this today - he suggests that accessing rdr twice in the above code might be less efficient that doing it my old skool way - could anyone confirm or deny this and suggest which of the above is the best way of doing this sort of thing? I would especially welcome answers from #JonSkeet ;-)
I doubt there will be a very appreciable performance difference, but you can avoid the name lookup on every row simply by lifting it out of the loop. This is probably the best you'll be able to achieve:
int idIdx = rdr.GetOrdinal("Id");
int createdIdx = rdr.GetOrdinal("Created");
while(rdr.Read())
{
var myObj = new myObj();
myObj.Id = rdr.GetFieldValue<int>(idIdx);
//more populating of myObj from rdr
myObj.Created = rdr.GetFieldValue<DateTime>(createdIdx);
}
I usually introduce a RecordSet class for this purpose:
public class MyObjRecordSet
{
private readonly IDataReader InnerDataReader;
private readonly int OrdinalId;
private readonly int OrdinalCreated;
public MyObjRecordSet(IDataReader dataReader)
{
this.InnerDataReader = dataReader;
this.OrdinalId = dataReader.GetOrdinal("Id");
this.OrdinalCreated = dataReader.GetOrdinal("Created");
}
public int Id
{
get
{
return this.InnerDataReader.GetInt32(this.OrdinalId);
}
}
public DateTime Created
{
get
{
return this.InnerDataReader.GetDateTime(this.OrdinalCreated);
}
}
public MyObj ToObject()
{
return new MyObj
{
Id = this.Id,
Created = this.Created
};
}
public static IEnumerable<MyObj> ReadAll(IDataReader dataReader)
{
MyObjRecordSet recordSet = new MyObjRecordSet(dataReader);
while (dataReader.Read())
{
yield return recordSet.ToObject();
}
}
}
Usage example:
List<MyObj> myObjects = MyObjRecordSet.ReadAll(rdr).ToList();
This makes the most sense to a reader. Whether it's the most "efficient" (you're literally calling two functions instead of one, it's not going to be as significant as casting, then calling a function). Ideally you should go with the option that looks more readable if it doesn't hurt your performance.
var ordinal = rdr.GetOrdinal("Id");
var id = rdr.GetInt32(ordinal);
myObj.Id = id;
Actually there is are differences in performance in how you use SqlDataReader, but they are somewhere else. Namely the ExecuteReader method accepts the CommandBehavior.SequentialAccess:
Provides a way for the DataReader to handle rows that contain columns with large binary values. Rather than loading the entire row, SequentialAccess enables the DataReader to load data as a stream. You can then use the GetBytes or GetChars method to specify a byte location to start the read operation, and a limited buffer size for the data being returned.
When you specify SequentialAccess, you are required to read from the columns in the order they are returned, although you are not required to read each column. Once you have read past a location in the returned stream of data, data at or before that location can no longer be read from the DataReader. When using the OleDbDataReader, you can reread the current column value until reading past it. When using the SqlDataReader, you can read a column value only once.
If you do not use large binary values then it makes very little difference. Getting a string and parsing is suboptimal, true, is better to get the value with rdr.SqlInt32(column) rather than a GetInt32() because of NULL. But the difference should not be noticeable on most application, unles your app is trully doing nothing else but read huge datasets. Most apps do not behave that way. Focusing on optimising the databse call itself(ie. have the query execute fast) will reap far greater benefits 99.9999% of the times.
For objects like DateTime I simply cast the rdr value to the required class, but this can't be done for value types like int
This isn't true: DateTime is also a value type and both of the following work in the same way, provided the field is of the expected type and is not null:
myObj.Id = (int) rdr["Id"];
myObj.Created = (DateTime)rdr["Created"];
If it's not working for you, perhaps the field you're reading is NULL? Or not of the required type, in which case you need to cast twice. E.g. for a SQL NUMERIC field, you might need:
myObj.Id = (int) (decimal) rdr["Id"];
I am trying to do something like this psuedocode....
//COMPANYTYPES = ENUM
FOR EACH (COMPANY TYPE IN COMPANYTYPES)
{
var result = companies.OfType<type>().
}
However I can't find a way to make not angry. Is it possible to do this? Or does the type need to be hardcoded in at compile time?
edit: I marked an anwser below, but thought I would show my complete WORKING code as well in case its helpful to anyone else:
foreach (CompanyType companyType in Enum.GetValues(typeof (CompanyType)))
{
var temp = GetTypeFromCompanyEnum(companyType);
foreach (var company in companies.Where(temp.IsInstanceOfType))
{
//code here
}
}
I assume that type is of type Type.
You cannot do that using generics; generics are all about compile-time types.
Instead, you can call .Where(type.IsInstanceOfType).
Note that this will also match instances of subtypes.
I have a string user setting and want to select a particular variable with the same name during the startup of my C# Windows app.
e.g.
I have a user setting (string) called UserSelectedInt, which is currently set to 'MyTwo'. (Please note that my variables are actually a lot more complex types than integers, I've just used them as examples.)
public static int MyOne = 12345;
public static int MyTwo = 54321;
public static int MyThree = 33333;
public int myInt = SelectMyVariableUsing(MyApp.Settings.Default.UserSelectedInt)
The user may have selected 'MyTwo' last time they closed the app, so that is the variable I want to select during startup. I hope I'm making sense.
Please can some let me know how would I achieve this?
Thanks
Use a Dictionary<string, int>. That allows you to assign an integer value to a number of strings. If the string is user input, you need to check that it is valid before trying to retrieve the corresponding integer.
Sounds like you are trying to implement the provider pattern. You may find using this is a better mechanism for you to use, especially as you say it is more complex than using ints.
In your code you would reference the particular provider using your MyApp.Settings.Default.UserSelectedInt setting.
I would say architecturally this would be a better mechanism than some of the other suggested answers.
// enumerate your list of property names (perhaps from a file)
var settings = new List<string>();
settings.Add("MyOne");
settings.Add("MyTwo");
settings.Add("MyThree");
var settingMap = new Dictionary<string, int>();
int value = 0;
foreach (var name in settings)
{
try
{
// try to parse the setting as an integer
if (Int32.TryParse((string)Properties.Settings.Default[name], out value))
{
// add map property name to value if successful
settingMap.Add(name, value);
}
else
{
// alert if we were unable to parse the setting
Console.WriteLine(String.Format("The settings property \"{0}\" is not a valid type!", name));
}
}
catch (SettingsPropertyNotFoundException ex)
{
// alert if the setting name could not be found
Console.WriteLine(ex.Message);
}
}
However, if you get to the stage where your variable list is HUGE then I would maybe look at actually accessing the properties file directly via some form of XML parsing.
The simplest way is probably to just use GetField.
Using your example just change the last line to:
var selectedField = MyApp.Settings.Default.UserSelectedInt
public int myInt = (int) GetType().GetField(selectedField).GetValue(this);
If the field or class is static the syntax should be:
var selectedField = MyApp.Settings.Default.UserSelectedInt
public int myInt = (int)typeof(YourClass).GetField(selectedField).GetValue(null);
See http://msdn.microsoft.com/en-us/library/system.reflection.fieldinfo.getvalue.aspx for more details.
Should be possible with Reflection
You could also use a Hashtable.
However, from the looks of it all your really wanting to do is store the value the user last selected hence I would probably just save it in the UserSettings as a string and then on load I would just parse the value.
I hope I am reading this right, but essentially, I feel you want to remember the value user selected last time. Since the application will be shutdown in the meantime, you are going to have to store it somewhere. Maybe in a file.
Too me it sounds more like you should use an enumeration in stead of static variables, consider the following example:
public enum MyVars
{
MyOne = 12345, MyTwo = 54321, MyThree = 33333
}
static void Main(string[] args)
{
Console.WriteLine(String.Format("Name:{0}, Val={1}", MyVars.MyOne.ToString(), (int)MyVars.MyOne ));
Console.ReadKey();
}
, it will output "Name:MyOne, Val=12345", with enumerations you can easily pick out the name of the variable.
Is there anyway I can get the name of class property IntProperty?
public class ClassName
{
public static int IntProperty { get { return 0; } }
}
//something like below but I want to get the string of "IntProperty"
ClassName.IntProperty.GetType().Name
Basically what I want to do is to dynamically save property name string into the database, and later on retrieve it from the database and invoke the property dynamically.
Seems like what I am looking for is similar to duck typing I think.
Thanks!
UPDATED:
This is the actual code. This is more like a workflow kind of thing. But each task is defined as property of a class (class is used to group tasks).
public class ApplicationTask
{
public static Task<string> SendIncompleteNotification
{
get
{
return new Task<string>
(
a => Console.WriteLine("Sample Task")
, "This is a sample task which does nothing."
);
}
}
}
So, the code will be able to retrieve the full name of the class and property something like: namespace.ApplicationTask.SendIncompleteNotification and save this into the database. Later on, the code will read the string and dynamically create the task and pass it into another to execute.
With C#6.0 you can get it by
nameof(ClassName.IntProperty)
I think that the use of the GetProperty method in this case, is redundant, because you need to know the property name to call the method.
You could loop through your properties and extract its name:
foreach (PropertyInfo p in typeof(ClassName).GetProperties())
{
string propertyName = p.Name;
//....
}
The result of ClassName.IntProperty is just an integer value. As soon as it's executed and the result is returned, there's no trace of it having come from IntProperty.
If you're using .NET 3.5 you can use an expression tree instead, usually created via a lambda expression:
Expression<Func<int>> exp = () => ClassName.IntProperty;
You can then compile and execute the expression and separately find out what it's doing (retrieving IntProperty in this case). I'm not really sure whether this is suitable for what you want to do though.
If you do work out how to save the property name in the database, then GetProperty is the way to go on the retrieval front.
Perhaps if you could give more context in the question in terms of how you want to use this, we could help more. You've shown just an expression - if you could show it in terms of where you'd be using it, that would be great.
EDIT: You've expanded the property, but not how it's being called. Do you need to call it directly, rather than just fetching the list of properties using Type.GetProperties and storing the list of property names in the database?
Again, if you could show the code which calls the property, and how you want it to interact with the database, we may be able to make more progress.
Type objectType = this.GetType();
PropertyInfo property = objectType.GetProperty("intProperty");
System.Console.Write(property.Name);
Is this what you need?
You can simply use nameof(ClassName.IntProperty)
It will give you "IntProperty"
I came across this, and it seems very helpful for getting property name. (C++)
#define getVarName(varName,holder) sprintf(holder, "%s", #varName)
int main() {
int var = 100; // just any type of identifier
char name[100]; // it will get the variables name
getVarName(var, name);
puts(name);
return 0;
}
ref: http://zobayer.blogspot.com/2010/05/c-fun-get-variables-name.html