Dapper. Map to SQL Column with spaces in column names - c#

I've managed to get something up and running today as small sandbox/POC project, but have seemed to bump my head on one issue...
Question:
Is there a way to get dapper to map to SQL column names with spaces in them.
I have something to this effect as my result set.
For example:
SELECT 001 AS [Col 1],
901 AS [Col 2],
00454345345345435349 AS [Col 3],
03453453453454353458 AS [Col 4]
FROM [Some Schema].[Some Table]
And my class would look like this
public class ClassA
{
public string Col1 { get; set; }
public string Col2 { get; set; }
///... etc
}
My implementation looks like this at the moment
public Tuple<IList<TClass>, IList<TClass2>> QueryMultiple<TClass, TClass2>(object parameters)
{
List<TClass> output1;
List<TClass2> output2;
using (var data = this.Connection.QueryMultiple(this.GlobalParameter.RpcProcedureName, parameters, CommandType.StoredProcedure))
{
output1 = data.Read<TClass>().ToList();
output2 = data.Read<TClass2>().ToList();
}
var result = new Tuple<IList<TClass>, IList<TClass2>>(output1, output2);
return result;
}
Note: The SQL cant be modified in any way.
Currently I'm going through the dapper code, and my only foreseeable solution is to add some code to "persuade" the column comparison, but not having much luck so far.
I've seen on StackOverflow that there are things like dapper extensions, but I'm hoping I can get this done without adding an extention, if not. I'll take whatever is quickest to implement.

There's a nuget package Dapper.FluentMap that allows you to add column name mappings (including spaces). It's similar to EntityFramework.
// Entity class.
public class Customer
{
public string Name { get; set; }
}
// Mapper class.
public class CustomerMapper : EntityMap<Customer>
{
public CustomerMapper()
{
Map(p => p.Name).ToColumn("Customer Name");
}
}
// Initialise like so -
FluentMapper.Initialize(a => a.AddMap(new CustomerMapper()));
see https://github.com/henkmollema/Dapper-FluentMap for more.

One option here would be to go via the dynamic / non-generic API, and then fetch the values out via the IDictionary<string,object> API per row, but that might be a bit tedious.
As an alternative, you can create a custom mapper, and tell dapper about it; for example:
SqlMapper.SetTypeMap(typeof(ClassA), new RemoveSpacesMap());
with:
class RemoveSpacesMap : Dapper.SqlMapper.ITypeMap
{
System.Reflection.ConstructorInfo SqlMapper.ITypeMap.FindConstructor(string[] names, Type[] types)
{
return null;
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo constructor, string columnName)
{
return null;
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetMember(string columnName)
{
var prop = typeof(ClassA).GetProperty(columnName.Replace(" ", ""));
return prop == null ? null : new PropertyMemberMap(columnName, prop);
}
class PropertyMemberMap : Dapper.SqlMapper.IMemberMap
{
private string columnName;
private PropertyInfo property;
public PropertyMemberMap(string columnName, PropertyInfo property)
{
this.columnName = columnName;
this.property = property;
}
string SqlMapper.IMemberMap.ColumnName
{
get { throw new NotImplementedException(); }
}
System.Reflection.FieldInfo SqlMapper.IMemberMap.Field
{
get { return null; }
}
Type SqlMapper.IMemberMap.MemberType
{
get { return property.PropertyType; }
}
System.Reflection.ParameterInfo SqlMapper.IMemberMap.Parameter
{
get { return null; }
}
System.Reflection.PropertyInfo SqlMapper.IMemberMap.Property
{
get { return property; }
}
}
}

I had a similar problem when trying to get mapped results from a call to the system sp_spaceused procedure. Marc's code didn't quite work for me as it complained about not being able to find a default constructor. I also made my version generic so it could theoretically be re-used. This may not be the fastest performing piece of code, but it works for me and in our situation these calls are made infrequently.
class TitleCaseMap<T> : SqlMapper.ITypeMap where T: new()
{
ConstructorInfo SqlMapper.ITypeMap.FindConstructor(string[] names, Type[] types)
{
return typeof(T).GetConstructor(Type.EmptyTypes);
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
return null;
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetMember(string columnName)
{
string reformattedColumnName = string.Empty;
foreach (string word in columnName.Replace("_", " ").Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
{
reformattedColumnName += char.ToUpper(word[0]) + word.Substring(1).ToLower();
}
var prop = typeof(T).GetProperty(reformattedColumnName);
return prop == null ? null : new PropertyMemberMap(prop);
}
class PropertyMemberMap : SqlMapper.IMemberMap
{
private readonly PropertyInfo _property;
public PropertyMemberMap(PropertyInfo property)
{
_property = property;
}
string SqlMapper.IMemberMap.ColumnName
{
get { throw new NotImplementedException(); }
}
FieldInfo SqlMapper.IMemberMap.Field
{
get { return null; }
}
Type SqlMapper.IMemberMap.MemberType
{
get { return _property.PropertyType; }
}
ParameterInfo SqlMapper.IMemberMap.Parameter
{
get { return null; }
}
PropertyInfo SqlMapper.IMemberMap.Property
{
get { return _property; }
}
}
}

I know this is an old question nevertheless i faced the same problem in my last project, so i just created an own mapper using attributes.
I defined an attribute class called ColumnNameAttribute.cs
using System;
namespace DapperHelper.Attributes
{
[System.AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
sealed class ColumNameAttribute : Attribute
{
private string _columName;
public string ColumnName
{
get { return _columName; }
set { _columName = value; }
}
public ColumNameAttribute(string columnName)
{
_columName = columnName;
}
}
}
After defining the attribute, i implemeted a dynamic mapper that uses the Query method from Dapper but works as the Query<T>:
using Dapper;
using DapperHelper.Attributes;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Web.Routing;
namespace DapperHelper.Tools
{
public class DynamicMapper<T> :IDisposable where T : class, new()
{
private readonly Dictionary<string, PropertyInfo> _propertiesMap;
public DynamicMapper()
{
_propertiesMap = new Dictionary<string, PropertyInfo>();
PropertyInfo[] propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo propertyInfo in propertyInfos)
{
if (propertyInfo.GetCustomAttribute(typeof(ColumNameAttribute)) is ColumNameAttribute columNameAttribute)
{
_propertiesMap.Add(columNameAttribute.ColumnName, propertyInfo);
}
else
{
_propertiesMap.Add(propertyInfo.Name, propertyInfo);
}
}
}
public List<T> QueryDynamic(IDbConnection dbConnection, string sqlQuery)
{
List<dynamic> results = dbConnection.Query(sqlQuery).ToList();
List<T> output = new List<T>();
foreach (dynamic dynObj in results)
{
output.Add(AssignPropertyValues(dynObj));
}
return output;
}
private T AssignPropertyValues(dynamic dynamicObject)
{
T output = new T();
RouteValueDictionary dynamicObjProps = new RouteValueDictionary(dynamicObject);
foreach (var propName in dynamicObjProps.Keys)
{
if (_propertiesMap.TryGetValue(propName, out PropertyInfo propertyMapped)
&& dynamicObjProps.TryGetValue(propName, out object value))
{
propertyMapped.SetValue(output, value);
}
}
return output;
}
public void Dispose()
{
_propertiesMap.Clear();
}
}
}
To use it, you have to refer to your Model class and define the attribute:
using DapperHelper.Attributes;
namespace Testing
{
public class Sample
{
public int SomeColumnData { get; set; }
[ColumnName("Your Column Name")]
public string SpecialColumn{ get; set; }
}
}
and then you can implement something like this:
DynamicMapper<Sample> mapper = new DynamicMapper<Sample>();
List<Sample> samples = mapper.QueryDynamic(connection, "SELECT * FROM Samples");
I hope it can help someone looking for an alternative.

Related

Dynamically convert property to Type

Is it possible to simplify this logic, Is there generic way to do it.
The code finds marked attributes and parses it according to the attribute type.
Please suggest some way to optimize this code, all the data type of Product class will be string, I'm getting product input as xml directly converting serialized data to a class with decimal,int,float will not give proper error message, If there is list of item it throws error in xml we wont know which row has caused the error.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace TestSolution
{
public interface ICustomParser
{
bool Parse(string input);
}
public class DecimalParserAttribute : Attribute, ICustomParser
{
public bool Parse(string input)
{
if (input == null) return false;
decimal decimalValue;
return decimal.TryParse(input, out decimalValue);
}
}
public class intParserAttribute : Attribute, ICustomParser
{
public bool Parse(string input)
{
if (input == null) return false;
int intValue;
return int.TryParse(input, out intValue);
}
}
public class Product
{
[DecimalParser]
public string Weight { get; set; }
[intParser]
public string NoOfItems { get; set; }
[intParser]
public string GRodes { get; set; }
[intParser]
public string WRodes { get; set; }
}
class Program
{
static void Main(string[] args)
{
var sb = Validate(new Product() { NoOfItems = "1", GRodes = "4", Weight = "5", WRodes = "23" });
Console.WriteLine(sb);
sb = Validate(new Product() { NoOfItems = "1", GRodes = "4w", Weight = "5", WRodes = "23" });
Console.WriteLine(sb);
Console.ReadKey();
}
private static string Validate(Product product)
{
var sb = new StringBuilder();
foreach (var property in product.GetType().GetProperties())
{
var value = Convert.ToString(property.GetValue(product, null));
var sel = property.GetAttribute<ICustomParser>();
if (sel == null) continue;
var parserinstance = (ICustomParser)Activator.CreateInstance(sel.GetType());
if (parserinstance.Parse(value)) continue;
sb.AppendLine(string.Format("{0} Has invalid value", property.Name));
}
return sb.ToString();
}
}
public static class Extensions
{
public static T GetAttribute<T>(this PropertyInfo property)
{
return (T)property.GetCustomAttributes(false).Where(s => s is T).FirstOrDefault();
}
}
}
If you only have one type (Product), it probably isn't worth it - just write the validation code explicitly without all the fancy stuff. If, however, you have multiple types to inspect (perhaps known only at runtime):
It really depends how fast it needs to be. How often does this run? If it is periodic, then there isn't a vast amount to do - the main change would be to just cast the parser directly:
var parserInstance = (ICustomParser)sel;
(it already is the attribute type)
If it is critical path, then there is a lot you can do to beef it up, but you get into the realm of metaprogramming - which is essentially what most tools like serializers and ORMs do to reduce runtime reflection. If you're not familiar with hacking IL at runtime, I would recommend looking at a tool like "Sigil" (available on nuget) that makes it hard to get wrong (or at least: tells you what you've done wrong). Essentially, you can inspect the data structure and then emit the IL that matches what it would look like if you were doing it all in explicit code; for example, emitting IL that looks kinda like:
static readonly DecimalParserAttribute _decimal = new DecimalParserAttribute();
public static void Validate(Product product) {
var sb = new StringBuilder();
if(!_decimal.Parse(product.Weight)) {
sb.Append(...);
}
// ... etc
...,
}

How to get the base class name of a class via Roslyn?

I am using Roslyn and I have the following class:
var source = #"
using System;
class MyClass : MyBaseClass {
static void Main(string[] args) {
Console.WriteLine(""Hello, World!"");
}
}";
// Parsing
SyntaxTree tree = CSharpSyntaxTree.ParseText(source);
// This uses an internal function (working)
// That gets the first node of type `SimpleBaseTypeSyntax`
SimpleBaseTypeSyntax simpleBaseType = GetNBaseClassNode(tree);
Getting the base class name
I successfully get access to node SimpleBaseTypeSyntax which contains what I need. In fact, if I use the syntax explorer, I get:
Node IdentifierToken has everything I need has its Text, Value and ValueText properties are "MyBaseClass"!
However, while in the syntax explorer I can see all these values, I cannot access them programmatically.
So I try retrieving the node programmatically:
IdentifierNameSyntax identifierNode =
simpleBaseType.ChildNodes().OfType<IdentifierNameSyntax>().First();
SyntaxToken identifier = simpleBaseType.Identifier;
string name = identifier.Text;
But name is empty string. Same as identifier.Value and identifier.ValueText.
What am I doing wrong? Maybe I am doing wrong, so how would you retrieve the base class name?
Another attempt: Using the semantic model
I started thinking that I need the semantic model for this type of information:
IdentifierNameSyntax identifierNode =
simpleBaseType .ChildNodes().OfType<IdentifierNameSyntax>().First();
SemanticModel semanticModel =
CSharpCompilation.Create("Class")
.AddReferences(MetadataReference.CreateFromFile(
typeof(object).Assembly.Location))
.AddSyntaxTrees(tree).GetSemanticModel(tree);
SymbolInfo symbolInfo = this.semanticModel.GetSymbolInfo(identifierNode);
string name = symbolInfo.Symbol.Name;
This throws exception as symbolInfo.Symbol is null.
I actually don't know why you can't pass the BaseTypeSyntax to the semantic model via GetSymbolInfo(), but it's also returning null for me with no errors.
Anyways, here's an approach that works:
var tree = CSharpSyntaxTree.ParseText(#"
using System;
class MyBaseClass
{
}
class MyClass : MyBaseClass {
static void Main(string[] args) {
Console.WriteLine(""Hello, World!"");
}
}");
var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { tree }, references: new[] { Mscorlib });
var model = compilation.GetSemanticModel(tree);
var myClass = tree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().Last();
var myClassSymbol = model.GetDeclaredSymbol(myClass) as ITypeSymbol;
var baseTypeName = myClassSymbol.BaseType.Name;
You'll want to use the semantic model here because you won't be able to reliably tell if you're dealing with an interface or a base type at the syntax level.
I can see that you're trying to build an analyzer with the Roslyn API.
You do know that there are other ways to test your analyzer logic? Using unit test files instead of directly having the source inside the analyzer.
Using this idea, you entirely build your analyzer with the template provided by Visual Studio, where you must inherit from DiagnosticAnalyzer and you create your analysis code logic.
For your situation, you should look at ClassDeclaration and easily access the BaseTypes property inside the Node.
public bool SomeTriedDiagnosticMethod(SyntaxNodeAnalysisContext nodeContext)
{
var classDeclarationNode = nodeContext.Node as ClassDeclarationSyntax;
if (classDeclarationNode == null) return false;
var baseType = classDeclarationNode.BaseList.Types.FirstOrDefault(); // Better use this in all situations to be sure code won't break
var nameOfFirstBaseType = baseType.Type.ToString();
return nameOfFirstBaseType == "BaseType";
}
protected static bool IsWebPage(SyntaxNodeAnalysisContext context, ClassDeclarationSyntax classDeclaration)
{
INamedTypeSymbol iSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclaration) as INamedTypeSymbol;
INamedTypeSymbol symbolBaseType = iSymbol?.BaseType;
while (symbolBaseType != null)
{
if (symbolBaseType.ToString() == "System.Web.UI.Page")
return true;
symbolBaseType = symbolBaseType.BaseType;
}
return false;
}
This is my helper class that finds all properties, class name, class namespace and base classes.
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace MyProject
{
public class CsharpClass
{
public string Name { get; set; }
public string Namespace { get; set; }
public List<CsharpProperty> Properties { get; set; }
public List<string> BaseClasses { get; set; }
public class CsharpProperty
{
public string Name { get; set; }
public string Type { get; set; }
public CsharpProperty(string name, string type)
{
Name = name;
Type = type;
}
}
public CsharpClass()
{
Properties = new List<CsharpProperty>();
BaseClasses = new List<string>();
}
}
public static class CsharpClassParser
{
public static CsharpClass Parse(string content)
{
var csharpClass = new CsharpClass();
var tree = CSharpSyntaxTree.ParseText(content);
var members = tree.GetRoot().DescendantNodes().OfType<MemberDeclarationSyntax>();
foreach (var member in members)
{
if (member is PropertyDeclarationSyntax property)
{
csharpClass.Properties.Add(new CsharpClass.CsharpProperty(
property.Identifier.ValueText, property.Type.ToString()));
}
if (member is NamespaceDeclarationSyntax namespaceDeclaration)
{
csharpClass.Namespace = namespaceDeclaration.Name.ToString();
}
if (member is ClassDeclarationSyntax classDeclaration)
{
csharpClass.Name = classDeclaration.Identifier.ValueText;
csharpClass.BaseClasses = GetBaseClasses(classDeclaration).ToList();
}
//if (member is MethodDeclarationSyntax method)
//{
// Console.WriteLine("Method: " + method.Identifier.ValueText);
//}
}
return csharpClass;
}
private static IEnumerable<string> GetBaseClasses(ClassDeclarationSyntax classDeclaration)
{
if (classDeclaration == null)
{
return null;
}
if (classDeclaration.BaseList == null)
{
return null;
}
return classDeclaration.BaseList.Types.Select(x => x.Type.ToString());
}
}
}
Usage:
const string content = #"
namespace Acme.Airlines.AirCraft
{
public class AirCraft
{
public virtual string Name { get; set; }
public virtual int Code { get; set; }
public AirCraft()
{
}
}
}";
var csharpClass = CsharpClassParser.Parse(content);
Console.WriteLine(csharpClass.Name);
Console.WriteLine(csharpClass.Namespace);
Console.WriteLine(csharpClass.Properties.Count);
Console.WriteLine(csharpClass.BaseClasses.Count);

Mapping a SharePoint list item to a C# class

Say I have:
public class SPListItem
{
public override object this[string fieldName]
{
get
{
return this.GetValue(fieldName);
}
set
{
this.SetValue(fieldName, value, !this.HasExternalDataSource);
}
}
}
public class Bar
{
public int Prop1 { get; set; }
public int Prop2 { get; set; }
public int Prop3 { get; set; }
}
is there any way I can do:
var fooInst = new SPListItem();
Bar barInst = (Bar)fooInst // or maybe Bar.FromFoo(Foo f) if handling the cast is not possible
and then have:
barInst.Prop1 give me the equivalent of:
fooInst["Prop"];
Without implementing the getters and setters for every property in Bar?
Aaaaaand, here we go. This class generates entities from your lists.
From: https://justsharepointthings.wordpress.com/2015/09/10/sharepoint-generate-c-poco-classes-from-an-existing-definition/
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.SharePoint;
namespace Code_Generation {
/// <summary>
/// Generates List object entities from a site connection.
/// </summary>
public class SPListPocoGenerator {
string parentDir = "GeneratedListPOCO/";
string hiddenDir = "GeneratedListPOCO/HiddenLists/";
private class PropertyString {
private string _propStr;
public PropertyString(string propStr) {
_propStr = propStr;
_properties = new Dictionary < string, string > ();
}
private Dictionary < string, string > _properties;
public string this[string key] {
get {
return _properties.ContainsKey(key) ? _properties[key] : string.Empty;
}
set {
if (_properties.ContainsKey(key)) {
_properties[key] = value;
} else {
_properties.Add(key, value);
}
}
}
/// <summary>
/// Replaces properties in the format {{propertyName}} in the source string with values from KeyValuePairPropertiesDictionarysupplied dictionary.nce you've set a property it's replaced in the string and you
/// </summary>
/// <param name="originalStr"></param>
/// <param name="keyValuePairPropertiesDictionary"></param>
/// <returns></returns>
public override string ToString() {
string modifiedStr = _propStr;
foreach(var keyvaluePair in _properties) {
modifiedStr = modifiedStr.Replace("{{" + keyvaluePair.Key + "}}", keyvaluePair.Value);
}
return modifiedStr;
}
}
public string _classDefinitionStr = #
"
using System;
using Microsoft.SharePoint;
public class {{EntityName}}
{
private SPListItem listItem;
public {{EntityName}}_InternalProperties InternalProperties
{
get; private set;
}
public {{EntityName}}(SPListItem li)
{
this.listItem = li;
this.InternalProperties = new {{EntityName}}_InternalProperties(this.listItem);
}
{{PropertySections}}
public class {{EntityName}}_InternalProperties
{
private SPListItem listItem;
public {{EntityName}}_InternalProperties(SPListItem li)
{
this.listItem = li;
}
{{HiddenPropertySections}}
{{InternalPropertySections}}
}
}";
private const string _propertySectionStr = "\n\n\t" + #
"public {{PropertyType}} {{PropertyName}}
{ get { return listItem[Guid.Parse("
"{{PropertyId}}"
")] as {{PropertyType}}; }
set { listItem[Guid.Parse("
"{{PropertyId}}"
")] = value; }}";
/// <summary>
/// Gets string identifying the field type
/// </summary>
/// <param name="field"></param>
/// <returns></returns>
private string GetSafeTypeName(SPField field) {
if (field.FieldValueType == null) {
return "object"; //Not going to try to parse it further, this is enough.
}
var type = field.FieldValueType;
if (type.IsValueType) {
return type.FullName + "?";
}
return type.FullName;
}
public void GenerateForWeb(SPWeb web) {
var blackList = new[] {
"Documents", "Form Templates", "Site Assets", "Site Pages", "Style Library"
};
Directory.CreateDirectory(parentDir);
Directory.CreateDirectory(hiddenDir);
foreach(SPList list in web.Lists) {
PropertyString _classDefinition = new PropertyString(_classDefinitionStr);
string entityName = "SPL_" + list.Title.Replace(" ", "");
_classDefinition["EntityName"] = entityName;
foreach(SPField field in list.Fields) {
PropertyString propertySection = new PropertyString(_propertySectionStr);
propertySection["PropertyType"] = GetSafeTypeName(field); //field.FieldValueType.FullName; -> Returning Null often. Thanks, SharePoint!
propertySection["PropertyName"] = field.EntityPropertyName.Replace("_x0020_", "_");
propertySection["PropertyId"] = field.Id.ToString();
if (SPBuiltInFieldId.Contains(field.Id)) _classDefinition["InternalPropertySections"] += propertySection;
else if (field.Hidden) _classDefinition["HiddenPropertySections"] += propertySection;
else _classDefinition["PropertySections"] += propertySection;
}
if (list.Hidden || blackList.Contains(list.Title)) {
File.WriteAllText(hiddenDir + entityName + ".cs", _classDefinition.ToString());
} else {
File.WriteAllText(parentDir + entityName + ".cs", _classDefinition.ToString());
}
}
}
}
}
For my former employer I implemented a DAO pattern for SharePoint. Unfortunately I was not allowed to take the code with me or publish it... I used annotations together with reflection to solve the issues with different names, optional fields, type casting etc. I also wrote a generator for the DTO-objects. But to be honest, it was a quite big effort for something that LINQ might solve in your case. Or writing your classes by hand, or writing a code generator for the getters and setters - it all depends on the size of your project(s).
Before implementing my own DAO I had a quite bad experience with LINQ to SQL, especially when columns were renamed, added or removed I didn't like the behaviour, I also had performance issues using it. That's why I prefered my own DAO pattern, but for easy tasks LINQ might be enough. My experience with LINQ also might be outdated, it was about 7 years ago ;)
You could use an ExpandoObject, which is in the System.Dynamic namespace. Try something like this (untested):
public class SPListItemPropertyMapper
{
private dynamic _expandoObject;
public SPListItemPropertyMapper(SPListItem listItem)
{
_expandoObject = new ExpandoObject();
foreach (SPField field in listItem.Fields)
{
_expandoObject.Add(field.InternalName, listItem.GetFormattedValue(field.InternalName));
}
}
public dynamic FieldValues
{
get { return _expandoObject; }
}
}
Usage:
SPListItem listItem; //your list item here
var propertyMapper = new SPListItemPropertyMapper(listItem);
var title = propertyMapper.FieldValues.Title;
var editor = propertyMapper.FieldValues.Editor;
var created = propertyMapper.FieldValues.Created;
etc. You should consider extending the foreach loop by more logic, to return values based on the field type instead of just using GetFormattedValue.

Serialize a class that uses reflection to fill its properties

I have the following code:
[Serializable]
public class CustomClass
{
public CustomClass()
{
this.Init();
}
public void Init()
{
foreach (PropertyInfo p in this.GetType().GetProperties())
{
DescriptionAttribute da = null;
DefaultValueAttribute dv = null;
foreach (Attribute attr in p.GetCustomAttributes(true))
{
if (attr is DescriptionAttribute)
{
da = (DescriptionAttribute) attr;
}
if (attr is DefaultValueAttribute)
{
dv = (DefaultValueAttribute) attr;
}
}
UInt32 value = 0;
if (da != null && !String.IsNullOrEmpty(da.Description))
{
value = Factory.Instance.SelectByCode(da.Description, 3);
}
if (dv != null && value == 0)
{
value = (UInt32) dv.Value;
}
p.SetValue(this, value, null);
}
}
private UInt32 name;
[Description("name")]
[DefaultValue(41)]
public UInt32 Name
{
get { return this.name; }
set { this.name = value; }
}
(30 more properties)
}
Now the weird thing is: when I try to serialize this class I will get an empty node CustomClass!
<CustomClass />
And when I remove Init from the constructor it works as expected! I will get the full xml representation of the class but ofcourse without values (all with value 0).
<CustomClass>
<Name>0</Name>
...
</CustomClass>
Also, when I comment out the body of Init, I will get the same as above (the one with default values)
I've tried it with a public method, with a Helper class everything, but it does not work. That is, instead of the expected:
<CustomClass>
<Name>15</Name>
...
</CustomClass>
I will get
<CustomClass />
It seems when I use reflection in this class, serialization is not possible.
Or to summarize: when I call Init or when I fill my properties with reflection -> Serialization fails, when I remove this code part -> Serialization works but of course without my values.
Is this true? And does somebody know an alternative for my solution?
It should automatically get something from the database based on the Description and when this returns nothing it falls back to the DefaultValue...
PS1: I am using the XmlSerializer
PS2: When I set a breakpoint before the serialization, I can see that all the properties are filled with the good values (like 71, 72 etc).
Now the weird thing is: when I try to serialize this class I will get an empty node CustomClass!
XmlSerializer uses DefaultValue to decide which values to serialize - if it matches the default value, it doesn't store it. This approach is consistent with similar models such as data-binding / model-binding.
Frankly, I would say that in this case both DefaultValueAttribute and DescriptionAttribute are poor choices. Write your own - perhaps EavInitAttribute - then use something like:
[EavInit(41, "name")]
public uint Name {get;set;}
Note that there are other ways of controlling this conditional serialization - you could write a method like:
public bool ShouldSerializeName() { return true; }
which will also work to convince it to write the value (this is another pattern recognised by various serialization and data-binding APIs) - but frankly this is even more work (it is per-property, and needs to be public, so it makes a mess of the API).
Finally, I would say that hitting the database multiple times (once per property) for every new object construction is very expensive - especially since many of those values are likely to be assigned values in a moment anyway (so looking them up is wasted effort). I would put a lot of thought into making this both "lazy" and "cached" if it was me.
An example of a lazy and "sparse" implementation:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Xml.Serialization;
static class Program
{
static void Main()
{
var obj = new CustomClass();
Console.WriteLine(obj.Name);
// show it working via XmlSerializer
new XmlSerializer(obj.GetType()).Serialize(Console.Out, obj);
}
}
public class CustomClass : EavBase
{
[EavInit(42, "name")]
public uint Name
{
get { return GetEav(); }
set { SetEav(value); }
}
}
public abstract class EavBase
{
private Dictionary<string, uint> values;
protected uint GetEav([CallerMemberName] string propertyName = null)
{
if (values == null) values = new Dictionary<string, uint>();
uint value;
if (!values.TryGetValue(propertyName, out value))
{
value = 0;
var prop = GetType().GetProperty(propertyName);
if (prop != null)
{
var attrib = (EavInitAttribute)Attribute.GetCustomAttribute(
prop, typeof(EavInitAttribute));
if (attrib != null)
{
value = attrib.DefaultValue;
if (!string.IsNullOrEmpty(attrib.Key))
{
value = LookupDefaultValueFromDatabase(attrib.Key);
}
}
}
values.Add(propertyName, value);
}
return value;
}
protected void SetEav(uint value, [CallerMemberName] string propertyName = null)
{
(values ?? (values = new Dictionary<string, uint>()))[propertyName] = value;
}
private static uint LookupDefaultValueFromDatabase(string key)
{
// TODO: real code here
switch (key)
{
case "name":
return 7;
default:
return 234;
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
protected class EavInitAttribute : Attribute
{
public uint DefaultValue { get; private set; }
public string Key { get; private set; }
public EavInitAttribute(uint defaultValue) : this(defaultValue, "") { }
public EavInitAttribute(string key) : this(0, key) { }
public EavInitAttribute(uint defaultValue, string key)
{
DefaultValue = defaultValue;
Key = key ?? "";
}
}
}

Simple question: Reflections in C#

I am learning the reflections concepts in c#. I have a class like this
public class pdfClass
{
public List<AttributeProperties> TopA { get; set; }
public List<AttributeProperties> TopB { get; set; }
public List<AttributeProperties> TopC { get; set; }
}
In another class I would like to extract the values from the list. I have stupid ways to do it like
public void ExtractValue (pdfClass incomingpdfClass, string type)
{
switch (type)
{
case "TopA":
foreach (var listitem in incomingPdfClass.TopA)
{...}
breaks;
case "TopB":
foreach (var listitem in incomingPdfClass.TopB)
{...}
breaks;
...
}
}
The operations in the foreach loops are similar. How can I do this in a clear way by using reflections?
public void ExtractValue(pdfClass incomingpdfClass, string type)
{
PropertyInfo pinfo = typeof(pdfClass).GetProperty("Top" + type);
var yourList = pinfo.GetValue(incomingpdfClass);
foreach (var listitem in yourList)
{ ... }
}
This is how you should do this using reflection. However, you should note that my code differs from yours in the fact that you are writing code that isn't clear nor would it compile. AS
public class ExtractValue (pdfClass incomingpdfClass, string type)
is non valid C# syntax if that is supposed to be a function as per my example this will work for you
Or if this is supposed to happen in the Constructor for the class it should look as follows
public class ExtractValue
{
public ExtractValue(pdfClass incomingpdfClass, string type)
{
PropertyInfo pinfo = typeof(pdfClass).GetProperty("Top" + type);
var yourList = pinfo.GetValue(incomingpdfClass);
foreach (var listitem in yourList)
{ ... }
}
}
var property = this.GetType().GetProperty(type);
foreach (var item in (List<AttributeProperties>)property.GetValue(this, null))
{
}
If you have instance of pdfClass you do not need to use reflection for accessing lists.
I would suggest to decouple type from strategy itself by persisting such a dictionary:
IDictionary<string, Func<pdfClass, AttributeProperties, bool>> strategy;
Once add relations like
strategy.Add("TopA", (pdf, item) =>
{
return pdf.TopA.IndexOf(item) >= 0;
});
and use like
string itemType = "TopA";
if (strategy.ContainsKey(itemType) )
{
bool found = strategy[itemType](incommingPdfClass, listItem);
}

Categories

Resources