Generic Method to convert datatable to list - c#

I'm using below code:
public static List<T> ConvertToList1<T>(DataTable dt)
{
var columnNames = dt.Columns.Cast<DataColumn>()
.Select(c => c.ColumnName)
.ToList();
var properties = typeof(T).GetProperties();
return dt.AsEnumerable().Select(row =>
{
T objT1 = Activator.CreateInstance<T>();
foreach (var pro in properties)
{
if (columnNames.Contains(pro.Name))
{
PropertyInfo? pI = objT.GetType().GetProperty(pro.Name);
pro.SetValue(objT, row[pro.Name] == DBNull.Value ? null : Convert.ChangeType(row[pro.Name], pI.PropertyType));
}
}
return objT1;
}).ToList();
}
But I'm getting error for decimal field having null values.
Invalid cast from 'System.Decimal' to 'System.Nullable`1[[System.Decimal, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral
public class SampleModel
{
public Guid Id { get; set; }
public Guid ProjectId { get; set; }
public string? Code { get; set; } = null!;
public string? Description { get; set; }
public decimal? Quantity1 { get; set; }
public decimal? Quantity2 { get; set; }
}
Can anyone suggest how to fix this?

Um, reading again... You mean that a non-null decimal value in the row cannot be assigned to a nullable decimal? Or did you mean a dbnull that cannot be assigned to the nullable decimal?
You can use the DataRow.IsNull to check if the value is dbnull. In that case you just skip it as the nullable already has default null.
// <CODE SNIP />
if (columnNames.Contains(pro.Name))
{
// if the value was null, just skip it.
if(!row.IsNull(pro.Name))
{
PropertyInfo? pI = objT.GetType().GetProperty(pro.Name);
pro.SetValue(objT, Convert.ChangeType(row[pro.Name], pI.PropertyType));
}
}

I've had a play.
The link from #Ryan Wilson was very useful.
In short, where you have a nullable type, you want to cast to the underlying type. Consider:
decimal? target;
decimal source = 42;
target = source;
This is perfectly reasonable code. We can assign a decimal value to a decimal? variable.
Using Nullable.GetUnderlyingType(myType) returns a type or null if myType is not nullable. Extending the above snippet:
var underlying1 = Nullable.GetUnderlyingType(target.GetType());
var underlying2 = Nullable.GetUnderlyingType(source.GetType());
Here, underlying2 is null, and underlying1 is decimal.
To put this into practice, then, we slightly alter the innermost part of your conversion method to become:
PropertyInfo pI = objT1.GetType().GetProperty(pro.Name);
var targetType = Nullable.GetUnderlyingType(pI.PropertyType) ?? pI.PropertyType;
pro.SetValue(objT1, row[pro.Name] == DBNull.Value
? null
: Convert.ChangeType(row[pro.Name], targetType));

Try following :
public static List<SampleModel> ConvertToList1<T>(DataTable dt)
{
List<SampleModel> results = dt.AsEnumerable().Select(x => new SampleModel()
{
Id = x.Field<Guid>("Id"),
ProjectId = x.Field<Guid>("ProjectId"),
Code = x.Field<string>("Code"),
Description = x.Field<string>("Description"),
Quantity1 = x.Field<decimal>("Quantity1"),
Quantity2 = x.Field<decimal>("Quantity1")
}).ToList();
return results;
}

Related

C# Expression - Getdefault Value or convert int? to int

I have a Class like below
public class GrouppedStockGift
{
public DateTime? TimeCreated { get; set; }
public int? StoreID { get; set; }
public int? UserCreated { get; set; }
public int? WorksID { get; set; }
public int? WorkOrderCode { get; set; }
public decimal? GiftPrice { get; set; }
public decimal? GiftMoney { get; set; }
public int SaleCount { get; set; }
}
I have Create a dynamic expression builder, in some case i need to convert int? to int or getdefaultvalue of int for example
var sumMethodint = typeof(Enumerable).GetMethods()
.Single(x => x.Name == "Sum"
&& x.GetParameters().Count() == 2
&& x.GetParameters()[1]
.ParameterType
.GetGenericArguments()[1] == typeof(int?));
sumMethodint = sumMethodint.MakeGenericMethod(typeof(GrouppedStockGift));
Expression<Func<GrouppedStockGift, int?>> SaleCount = y => y.SaleCount;
var SaleCountExtractor = Expression.Call(sumMethodint, parameter, SaleCount);
bindings.Add(
Expression.Bind(
typeof(GrouppedStockGift).GetProperty("SaleCount"),
SaleCountExtractor));
but when execute last row Exception returned around type mismached
because SaleCount is int but sum method return int?
can any one help me?
You must just change Expression.Call row to
var SaleCountExtractor = Expression.Call(Expression.Call(sumMethodint, parameter, SaleCount), "GetValueOrDefault", Type.EmptyTypes);
in the above code
Expression.Call(exp, "GetValueOrDefault", Type.EmptyTypes);
get default value of int
I assume that problem is in your decision to use the overload of Sum which returns int?. You should use the another overload which gets selecotr of type Func<T, int> and returns int.
var parameter = Expression.Parameter(typeof (IEnumerable<GrouppedStockGift>));
var sumMethodint = typeof(Enumerable).GetMethods()
.Single(x => x.Name == "Sum"
&& x.GetParameters().Count() == 2
&& x.GetParameters()[1]
.ParameterType
.GetGenericArguments()[1] == typeof(int));
sumMethodint = sumMethodint.MakeGenericMethod(typeof(GrouppedStockGift));
Expression<Func<GrouppedStockGift, int>> saleCount = y => y.SaleCount;
var saleCountExtractor = Expression.Call(sumMethodint, parameter, saleCount);
bindings.Add(Expression.Bind(typeof(GrouppedStockGift).GetProperty("SaleCount"),
saleCountExtractor));

c# Write null field to a datatable

`I'm having a problem writing a null result to a datatable.
My linq query is returning values with which I'm populating a new instance of a class.
My datatable is being created generically and being populated with a generically created datarow.
What happens is my datatable is being created succesfully, the query runs, but when I hit the VAR statement it fails because one of the decimal fields is null. I cannot change this in the class because then I cannot create the datatable.
I need to change this one line I think to make it accept a null value:
moneyvalue = result.moneyvalue,
This is my table definition:
[Table(Name = "t_sdi_traded_product")]
public class t_sdi_traded_product
{
[Column]
public string deal_position_id;
[Column]
public decimal moneyvalue;
[Column]
public string cost_centre;
}
This is my class
public class traded_product
{
public string Deal { get; set; }
public decimal moneyvalue { get; set; }
public string InvolvedPartyId { get; set; }
}
This is my query
var query =
from result in t_sdi_traded_product_hsbc.AsQueryable()
where result.sdi_control_id == current_control_id
select new traded_product()
{
Deal = result.deal_position_id,
moneyvalue = result.moneyvalue,
InvolvedPartyId = result.involved_party_id
}
Here is how I create my datatable and datarow
public static DataTable CreateDataTable(Type animaltype)
{
DataTable return_Datatable = new DataTable();
foreach (PropertyInfo info in animaltype.GetProperties())
{
return_Datatable.Columns.Add(new DataColumn(info.Name, info.PropertyType));
}
return return_Datatable;
}
public static DataRow makeRow(object input, DataTable table)
{
Type inputtype = input.GetType();
DataRow row = table.NewRow();
foreach (PropertyInfo info in inputtype.GetProperties())
{
row[info.Name] = info.GetValue(input, null);
}
return row;
}
Now as soon as it hits this part of the code after the "var query" I get the problem:
foreach (var results in query)
{
foreach (PropertyInfo result in results.GetType().GetProperties())
{
string name = result.Name;
foreach (PropertyInfo info in used.GetType().GetProperties())
{
if (result.Name == info.Name)
{
try
{
info.SetValue(used, result.GetValue(results, null), null);
}
catch (NoNullAllowedException e)
{
}
finally
{
info.SetValue(used, DBNull.Value, null);
}
//Console.WriteLine("Result {0} matches class {1} and the value is {2}", result.Name, info.Name, result.GetValue(results,null));
}
}
}
tp_table.Rows.Add(used, tp_table);
}
It fails as soon as it hits the foreach, because the value returned from the database for moneyvalue is null.
I cannot change the class piece to decimal? otherwise the CreateDatable method fails because it says DataTable cannot have a nullable value.
I think your issue is in
select new traded_product()
{
Deal = result.deal_position_id,
moneyvalue = result.moneyvalue, <-- here you need some handling for DBNULL.Value
InvolvedPartyId = result.involved_party_id
}
select new traded_product()
{
Deal = result.deal_position_id,
moneyvalue = result.moneyvalue == DBNull.Value ? 0m : result.moneyvalue,
InvolvedPartyId = result.involved_party_id
}
* Update *
Why not construct your datatable using traded_product and as #user65439 mentioned change your DB class (t_sdi_traded_product) to have a nullable column
[Column]
public decimal? moneyvalue;
Then you just have to handle nulls being returned and converting them to 0 for your not-nullable decimal in your traded_product class
If it is allowed to write NULL values to the database you should make your variable types nullable, for example
[Column]
public decimal? moneyvalue;
instead of
[Column]
public decimal moneyvalue;

Can not fake guid properties using FakeO

I am trying to use the library called FakeO (https://github.com/rally25rs/FakeO)
It works fine except when there is a Guid property. Anyone have an idea what I maybe doing wrong ?
Exceptin I get is : Object of type 'System.Int32' cannot be converted to type 'System.Guid'.
here is the code
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Get a single instance of an object");
var gud = Guid.NewGuid();
var obj1 = FakeO.Create.Fake<SampleClass>(
s => s.UniqueId = FakeO.Data.Random<Guid>(),
s => s.Id = FakeO.Number.Next(),
s => s.PhoneNumber = FakeO.Phone.Number(),
s => s.SomeString = FakeO.String.Random(50));
Console.WriteLine(obj1.ToString() + "\n");
IEnumerable<SampleClass> obj2 = FakeO.Create.Fake<SampleClass>(10, s => s.Id = FakeO.Number.Next(),
s => s.PhoneNumber = FakeO.Phone.Number(),
s => s.SomeString = FakeO.String.Random(50));
foreach (var obj in obj2)
Console.WriteLine(obj.ToString());
Console.ReadKey();
}
}
public class SampleClass
{
public int Id { get; set; }
public string SomeString { get; set; }
public string PhoneNumber { get; set; }
public Guid UniqueId { get; set; }
public override string ToString()
{
var output = "ID={0},SomeString ={1},PhoneNumber = {2}";
return String.Format(output, Id, SomeString, PhoneNumber);
}
}
Guid is value type, and the author did not handle unsupported ValueType properly. He returns 0 for all unsupported value types in the Data.Random method, which is not quite nice for any struct type. According to this StackOverflow question, the last lines of Data.Random should be fixed to
if(t.IsValueType)
{
return Activator.CreateInstance(t);
}
return null;
This will return default value for struct type, which is empty Guid in case of Guid type I believe. If you want to support Guid type, you can add it in Data.Random method just before the final check of ValueType:
if (t == typeof(Guid))
return Guid.NewGuid();
I did not test my solution, but it should do.
It looks like you should be using:
FakeO.Distinct.Guid()

How can I deserialize an XML snippet to configure an already existing object?

Here's my scenario, I've got the following class, and I want to have the constructor deserialize some elements of the class. I would really rather NOT use a factory method here.
public abstract class AccessLevelAgentBase : IAccessLevelAgent
{
public List<AccessLevel> AccessLevels { get; set; }
[XmlElement]
public string PasswordPrompt { get; set; }
[XmlElement]
public string GetAccessLevelKeystroke { get; set; }
[XmlElement]
public int Doohicky { get; set;}
public AccessLevelAgentBase(XElement agentElement)
{
// Some Mojo Here to take agentElement and serialize
// from the XML below to set the values of PasswordPrompt,
// GetAccessLevelKeystroke, and Doohicky.
}
}
The XML:
<AccessLevelAgent>
<PasswordPrompt> Password ?: </PasswordPrompt>
<PromptCommand>Ctrl+X</PromptCommand>
<Doohicky>50</Doohicky>
</AccessLevelAgent>
simple way...
public AccessLevelAgentBase(XElement agentElement)
{
this.AccessLevels = (string)agentElement.Element("AccessLevels");
this.GetAccessLevelKeystroke = (string)agentElement.Element("GetAccessLevelKeystroke");
this.Doohicky = (int)agentElement.Element("Doohicky");
}
... not so simple way...
public AccessLevelAgentBase(XElement agentElement)
{
var type = this.GetType();
var props = from prop in type.GetProperties()
let attrib = prop.GetCustomAttributes(typeof(XmlElementAttribute), true)
.OfType<XmlElementAttribute>()
.FirstOrDefault()
where attrib != null
let elementName = string.IsNullOrWhiteSpace(attrib.ElementName)
? prop.Name
: attrib.ElementName
let value = agentElement.Element(elementName)
where value != null
select new
{
Property = prop,
Element = value,
};
foreach (var item in props)
{
var propType = item.Property.PropertyType;
if (propType == typeof(string))
item.Property.SetValue(this, (string)item.Element, null);
else if (propType == typeof(int))
item.Property.SetValue(this, (int)item.Element, null);
else
throw new NotSupportedException();
}
}

Trouble with Nullable Types, DBNulls and Data Rows

I saw this thread on Stack Overflow regarding converting between DBNull and nullable types but I am still confused. I have written code like this with some hack code to deal with nullable types for DateTime and ints that I will show below but it'sa mess and I want to use Nullable types:
DataTable dt = ds.Tables[0];
List<RON> list = (from dr in dt.AsEnumerable()
select new RON
{
RONId = dr[0].ToString(),
StaffNumber = dr[1].ToString(),
CheckInDate = GetCheckInDate(dr[2]),
NonMissionIndicator = dr[3].ToString(),
Comments = dr[4].ToString(),
NonComplyIndicator = dr[5].ToString(),
LOAirport = dr[6].ToString(),
RONAirport = dr[7].ToString(),
PropertyId = GetPropertyId(dr[8]),
PropertyChain = dr[9].ToString(),
PropertyName = dr[10].ToString(),
PropertyStreet = dr[11].ToString(),
PropertyStreet2 = dr[12].ToString(),
PropertyCity = dr[13].ToString(),
PropertyState = dr[14].ToString(),
PropertyPostalCode = dr[15].ToString(),
PropertyPhone = dr[16].ToString(),
FAX = dr[17].ToString(),
PropertyCountry = dr[18].ToString(),
PropertyLongitude = GetPropertyLongitude(dr[19]),
PropertyLatitude = GetPropertyLatitude(dr[20]),
PropertyAirport = dr[21].ToString(),
ReportedBy = dr[22].ToString(),
ReportedDTS= GetReportedByDate(dr[23]),
CanceledBy = dr[24].ToString(),
CanceledDTS = GetCanceledByDate(dr[25])
}).ToList();
return list;
}
//TODO: Change Defaukt date
private static DateTime GetCheckInDate(object checkInDate)
{
return checkInDate == DBNull.Value ? new DateTime(2000, 1, 1, 00, 00, 00) : Convert.ToDateTime(checkInDate);
}
//TODO: Change Defaukt date
private static DateTime GetReportedByDate(object reportedByDate)
{
return reportedByDate == DBNull.Value ? new DateTime(2000, 1, 1, 00, 00, 00) : Convert.ToDateTime(reportedByDate);
}
//TODO: Change Defaukt date
private static DateTime GetCanceledByDate(object canceledByDate)
{
return canceledByDate == DBNull.Value ? new DateTime(2000, 1, 1, 00, 00, 00) : Convert.ToDateTime(canceledByDate);
}
private static Int32 GetPropertyId(object propertyId)
{
return propertyId == DBNull.Value ? 0 : Convert.ToInt32(propertyId);
}
private static double GetPropertyLongitude(object propertyLongitude)
{
return propertyLongitude == DBNull.Value ? 0.0 : Convert.ToDouble(propertyLongitude);
}
private static double GetPropertyLatitude(object propertyLatitude)
{
return propertyLatitude == DBNull.Value ? 0.0 : Convert.ToDouble(propertyLatitude);
}
RON is now defined as:
public class RON
{
public string RONId { get; set; }
public string StaffNumber { get; set; }
public DateTime CheckInDate { get; set; }
public string NonMissionIndicator { get; set; }
public string NonComplyIndicator { get; set; }
public string LOAirport { get; set; }
public string RONAirport { get; set; }
public int PropertyId { get; set; }
public string PropertyChain { get; set; }
public string PropertyName { get; set; }
public string PropertyStreet { get; set; }
public string PropertyStreet2 { get; set; }
public string PropertyCity { get; set; }
public string PropertyState { get; set; }
public string PropertyPostalCode { get; set; }
public string PropertyCountry { get; set; }
public string PropertyPhone { get; set; }
public string FAX { get; set; }
public double PropertyLongitude { get; set; }
public double PropertyLatitude { get; set; }
public string PropertyAirport { get; set; }
public string ReportedBy { get; set; }
public DateTime ReportedDTS { get; set; }
public string CanceledBy { get; set; }
public DateTime CanceledDTS { get; set; }
public string Comments { get; set; }
The database guy tells me that this is the return from Oracle in the DataSet/cursor:
RON_ID NOT NULL VARCHAR2(40)
STAFF_NUM NOT NULL VARCHAR2(12)
CHECKIN_DATE NOT NULL DATE
NONMISSION_IND NOT NULL VARCHAR2(1)
COMMENTS VARCHAR2(4000)
NONCOMPLY_IND VARCHAR2(4000)
PROPERTY_ID NOT NULL NUMBER(38)
PROPERTY_CHAIN VARCHAR2(2)
PROPERTY_NAME VARCHAR2(255)
RON_AIRPORT NOT NULL VARCHAR2(3)
PROPERTY_STREET VARCHAR2(255)
PROPERTY_STREET2 VARCHAR2(255)
PROPERTY_CITY VARCHAR2(255)
PROPERTY_STATE VARCHAR2(3)
PROPERTY_POSTALCODE VARCHAR2(255)
PROPERTY_PHONE VARCHAR2(20)
PROPERTY_FAX VARCHAR2(20)
PROPERTY_COUNTRY VARCHAR2(2)
PROPERTY_LONGITUDE NUMBER
PROPERTY_LATITUDE NUMBER
PROPERTY_AIRPORT VARCHAR2(3)
REPORTED_BY VARCHAR2(50)
REPORTED_DTS NOT NULL DATE
CANCELED_BY VARCHAR2(50)
CANCELED_DTS DATE
How can I declare RON with Nullable Types and how can I do operations with Nullable Dates and such? Do strings have to be checked?
To define a Value Type (such as DateTime) as Nullable...do one of the following:
DateTime? ReportedDTS
OR
Nullable<DateTime> ReportedDTS
Reference types (such as string) are already "nullable".
To get a nullable value out of a DataRow you can use the field extension method:
DateTime? nullableDate = dataRow.Field<DateTime?>("ColumnName");
That will automatically convert DBNull to null.
So for your example you can do something like:
select new RON
{
RONId = dr.Field<string>("RON_ID"),
// snip...
CheckInDate = dr.Field<DateTime?>("CHECKIN_DATE"),
// snip...
};
In reviewing your original question, I see that an error will occur when .ToString() is called on a null value.
This happens when the dataset field has a DBNULL value.
The Best approach is to test for null first before calling any methods on the object.
i.e.
DataTable dt = ds.Tables[0];
List<RON> list = (from dr in dt.AsEnumerable()
select new RON
{
RONId = dr.IsNull("RONid") ? "0" : dr.Field<String>("RONid"),
StaffNumber = dr.IsNull("StaffNumber") ? "0" : dr.Field<String>("StaffNumber"),
... etc,
... etc
}).ToList();
return list;
}
PS:
Probe your dataset for the field values to match. Here, I assumed that your dataset "ColumnName" had the same name as the variable it is being passed to but that is not always the case.
Word of advice, use "ColumnName" instead of index since the index can change when db changes, stored proc or your query changes.
As only value types can be made Nullable, you won't have to do anything special for strings. Primitive types (and DateTime) can be declared nullable by appending a question mark after their type name, like int? NullableNumber { get; set; }.
You may need to compare against DBNull before assigning either a DateTime or null
myNullableDate = (DBNull.Value == list.Field<object>("FOO"))
? null
: (DateTime?)list.Field<DateTime?>("FOO");

Categories