This project is read-only.

Object to DataRow

Dec 7, 2009 at 8:16 AM
Edited Dec 7, 2009 at 8:18 AM

How one would create a mapper config that would map correctly a DTO object to a typed DataRow.

The problem I'm facing now, is because DTO has some DataTime? null values, and when maping using regular configurator I get an exception that DataTime must be non-null. Yes, I checked my DataTable it allows null for that field, but when inserting using typed DataRow you get an exception.

The solution here it to use the indexer for dataRow then you can insert null DateTime.

that's what I mean

MyTypedDataRow r = ...
r.MyDateTimeField = dto.MyDateTimeField; // here I get an exception if value is null
however if I use
DataRow r = ...
r["MyDateTimeField"]  = dto.MyDateTimeFiled; //it works ok even for NULLs.

so the question is how to create a mapper that would use string indexer for DataRows.

Thx

P.S. The sample you provide for DataReaderToObjectMapper has a small issue in these lines
 object result = reader[m.Name];

if (result is DBNull)

{

return ValueToWrite.ReturnValue(null);

}

if you have an int? field, then it fails to map with NullValueException. I changed to return ValueToWrite.Skip(); and it works as expected.

{
return ValueToWrite.ReturnValue(nul

 

Dec 7, 2009 at 8:36 AM

 

I created a small function for now that does what I want
public static void DTOToDataRow<T>(T from, DataRow row, IEnumerable<string> skipFields)
    {
      var props = ReflectionUtils
        .GetPublicFieldsAndProperties(typeof(T))
        .Where(m => m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property && ((PropertyInfo)m).GetSetMethod() != null)
        .Where(m => !skipFields.Select(sf => sf.ToUpper()).Contains(m.Name.ToUpper()));
      foreach (PropertyInfo memberInfo in props)
      {
        row[memberInfo.Name] = memberInfo.GetValue(from, null) ?? DBNull.Value;
      }
    }

but I'd like to have this as a regular mapper :)
Please help me to write it.
Thx

The main issue I see in this function is that it doesn't cache the fields definition and collects them every time is called, that's slow, I'm hoping an official mapper is smart enough to cache this.

 

Dec 7, 2009 at 8:40 PM
Edited Dec 7, 2009 at 8:41 PM

Hi Eugen,

The mapper for data rows can be configured in that way (for the latest EmitMapper):

 

 

public class Map2DataRowConfig : MapConfigBase<Map2DataRowConfig>
{
	public override IMappingOperation[] GetMappingOperations(Type from, Type to)
	{
		var objectMembers = ReflectionUtils.GetPublicFieldsAndProperties(from);
		return base.FilterOperations(
			from,
			to,
			objectMembers.Select(
				m => (IMappingOperation)new SrcReadOperation
				{
					Source = new MemberDescriptor(m),
					Setter = (obj, value, state) =>
						{
							((DataRow)obj)[m.Name] = value;
						}
				}
			)
		).ToArray();
	}
}
// Using: 

// Test data object
public class TestDTO
{
	public string field1 = "field1";
	public int field2 = 10;
	public bool field3 = true;
}

[TestMethod]
public void MappingToDataRow_test()
{
	// this is the mapper 
	var mapper = ObjectsMapperManager.DefaultInstance.GetMapper<TestDTO, DataRow>(new Map2DataRowConfig());

	// initialization of test DTO object
	TestDTO testDataObject = new TestDTO
	{
		field1 = "field1",
		field2 = 10,
		field3 = true
	};

	// Initializing of test table. Usual this table is read from database.
	DataTable dt = new DataTable();
	dt.Columns.Add("field1", typeof(string));
	dt.Columns.Add("field2", typeof(int));
	dt.Columns.Add("field3", typeof(bool));
	dt.Rows.Add("", 0, false);
	DataRow dr = dt.Rows[0];

	// Mapping test object to datarow
	mapper.Map(testDataObject, dr);

	// Check if the object is correctly mapped
	Assert.AreEqual("field1", dr["field1"]);
	Assert.AreEqual(10, dr["field2"]);
	Assert.AreEqual(true, dr["field3"]);
}

 

 

Dec 7, 2009 at 8:44 PM

looks good, but I guess we need to check whether the value is NULL and insert DBNUll.Value, cause it will fail if you insert just null.
 see I have such a think

row[memberInfo.Name] = memberInfo.GetValue(from, null) ?? DBNull.Value;


where do I add such a condition in your code.

 

Thx 

Dec 7, 2009 at 8:47 PM

Yes, you are right.

Just replace the string

((DataRow)obj)[m.Name] = value;

with

((DataRow)obj)[m.Name] = value ?? DBNull.Value;

Dec 7, 2009 at 8:51 PM

great, much better

just one more question, say I have an array of 1000 DTO items, is this code ok to use in a loop 1000 times and map 1000 DataRows, or shall I use another approach for collections?

Dec 7, 2009 at 9:02 PM
Edited Dec 7, 2009 at 9:04 PM

In that case I suggest you not to create mapper for each row but create it once beforehand and use the same mapper for each row. For example:

 

// Plenty of objects!
TestDTO [] objects = LoadPlentyOfObjects();

// this is the mapper. It is the only one for all rows.
var mapper = ObjectsMapperManager.DefaultInstance.GetMapper<TestDTO, DataRow>(new Map2DataRowConfig());

DataTable dt = new DataTable();
foreach (var obj in objects)
{
	dt.Rows.Add();

	// We are using the same mapper for all objects
	mapper.Map(obj, dt.Rows[dt.Rows.Count-1]); 
}

 

 

Dec 7, 2009 at 9:06 PM

great thx.

Now the only think I'm missing is the Silverlight port :)

P.S. Yes I'm that guy from http://rsdn.ru who was bugging you the way you ignore column names by using hardcoded strings :)

Dec 7, 2009 at 9:19 PM

Hi :)

I will port it to Silverlight if it is possible at all (I don't know if silverlight allows the Emit library because it can be a security hole from its point of view)