Ignoring properties in Dapper - dapper

In Dapper (http://code.google.com/p/dapper-dot-net/), is there a way to ignore properties in the model class, namely when using the Insert extension method?
My model class has a set of computed properties which are not persisted in the associated table.

Well, Dapper has no Insert extension method, that is in dapper.contrib, dapper extensions or dapper rainbow.
Dapper itself allows you to do:
Animal a = new Animal {Age = 10, Family = "Canine"}
// only insert Age
cnn.Execute("insert Animal(Age) values (#Age)", a);
To work around for some of the extension classes you can sometimes do:
cnn.InsertExtension("Animal", new{a.Age});
Regardless, you can always fall back to raw Dapper for your complex filtered inserts.

If you are using Dapper.Contrib, check out this code in SqlMapperExtensions:
https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper.Contrib/SqlMapperExtensions.cs#L54-L66
private static List<PropertyInfo> ComputedPropertiesCache(Type type)
{
//...
var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList();
and https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper.Contrib/SqlMapperExtensions.Async.cs#L147-L165
var computedProperties = ComputedPropertiesCache(type);
var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
//...
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
{
//...
So if you add a ComputedAttribute to your properties on your class, Dapper.Contrib won't try to insert them in to the database! You shouldn't have to worry about getting Dapper to ignore those properties, only Dapper.Contrib. Because if you use select * from tablename in your dapper queries,it will only try to map columns that exist. So you just don't create columns for the properties which you marked as [Computed].

Just add the [Computed] attribute to the properties in question.

If you just want to "hide" a property from your Insert/Update statements then there is one official way to do this in dapper extensions:
using DapperExtensions.Mapper;
public class UserMapper : ClassMapper<User>
{
public UserMapper()
{
base.Map(m => m.IsTrialUser).Ignore();
base.AutoMap();
}
}

I wrote a modified version of the SqlMapperExtensions class and added a string[] parameter ExcludeProperties for excluding columns. Using this class you can make calls like:
// Insert
result = (int)cn.Insert<Animal>(box, new string[] { "Errors", "IsValid" });
// Update
cn.UpdateEntity<Animal>(box, new string[] { "Errors", "IsValid" });
In the statements above, the 'Errors' and 'IsValid' properties I added for will not be included in the SQL statements.
Here's the modified extensions class:
SqlMapperExtensions.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Collections.Concurrent;
using System.Reflection.Emit;
using System.Threading;
using System.Runtime.CompilerServices;
namespace Dapper.Contrib.Extensions
{
public static class SqlMapperExtensions
{
public interface IProxy
{
bool IsDirty { get; set; }
}
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> KeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> TypeProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> GetQueries = new ConcurrentDictionary<RuntimeTypeHandle, string>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> TypeTableName = new ConcurrentDictionary<RuntimeTypeHandle, string>();
private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type)
{
if (KeyProperties.ContainsKey(type.TypeHandle))
{
return KeyProperties[type.TypeHandle];
}
var allProperties = TypePropertiesCache(type);
var keyProperties = allProperties.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)).ToList();
if (keyProperties.Count == 0)
{
var idProp = allProperties.Where(p => p.Name.ToLower() == "id" || p.Name.ToLower() == (type.Name.ToLower() + "id")).FirstOrDefault();
if (idProp != null)
{
keyProperties.Add(idProp);
}
}
KeyProperties[type.TypeHandle] = keyProperties;
return keyProperties;
}
private static IEnumerable<PropertyInfo> TypePropertiesCache(Type type)
{
if (TypeProperties.ContainsKey(type.TypeHandle))
{
return TypeProperties[type.TypeHandle];
}
var properties = type.GetProperties();
TypeProperties[type.TypeHandle] = properties;
return properties;
}
/// <summary>
/// Returns a single entity by a single id from table "Ts". T must be of interface type.
/// Id must be marked with [Key] attribute.
/// Created entity is tracked/intercepted for changes and used by the Update() extension.
/// </summary>
/// <typeparam name="T">Interface type to create and populate</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="id">Id of the entity to get, must be marked with [Key] attribute</param>
/// <returns>Entity of T</returns>
public static T GetEntity<T>(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
string sql;
if (!GetQueries.TryGetValue(type.TypeHandle, out sql))
{
var keys = KeyPropertiesCache(type);
if (keys.Count() > 1)
throw new DataException("Get<T> only supports an entity with a single [Key] property");
if (keys.Count() == 0)
throw new DataException("Get<T> only supports en entity with a [Key] property");
var onlyKey = keys.First();
var name = GetTableName(type);
// TODO: pluralizer
// TODO: query information schema and only select fields that are both in information schema and underlying class / interface
sql = "select * from " + name + " where " + onlyKey.Name + " = #id";
GetQueries[type.TypeHandle] = sql;
}
var dynParms = new DynamicParameters();
dynParms.Add("#id", id);
T obj = null;
if (type.IsInterface)
{
var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary<string, object>;
if (res == null)
return (T)((object)null);
obj = ProxyGenerator.GetInterfaceProxy<T>();
foreach (var property in TypePropertiesCache(type))
{
var val = res[property.Name];
property.SetValue(obj, val, null);
}
((IProxy)obj).IsDirty = false; //reset change tracking and return
}
else
{
obj = connection.Query<T>(sql, dynParms, transaction: transaction, commandTimeout: commandTimeout).FirstOrDefault();
}
return obj;
}
private static string GetTableName(Type type)
{
string name;
if (!TypeTableName.TryGetValue(type.TypeHandle, out name))
{
//name = type.Name + "s";
name = type.Name;
if (type.IsInterface && name.StartsWith("I"))
name = name.Substring(1);
//NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework
var tableattr = type.GetCustomAttributes(false).Where(attr => attr.GetType().Name == "TableAttribute").SingleOrDefault() as
dynamic;
if (tableattr != null)
name = tableattr.Name;
TypeTableName[type.TypeHandle] = name;
}
return name;
}
/// <summary>
/// Inserts an entity into table "Ts" and returns identity id.
/// </summary>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToInsert">Entity to insert</param>
/// <returns>Identity of inserted entity</returns>
public static long Insert<T>(this IDbConnection connection, T entityToInsert, string[] ExcludeProperties = null, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
//using (var tx = connection.BeginTransaction())
//{
var type = typeof(T);
var name = GetTableName(type);
var sb = new StringBuilder(null);
sb.AppendFormat("insert into {0} (", name);
var allProperties = TypePropertiesCache(type);
if (ExcludeProperties != null)
{
List<PropertyInfo> someProperties = allProperties.ToList();
foreach (PropertyInfo prop in allProperties.ToArray())
{
if (ExcludeProperties.Contains(prop.Name))
{
someProperties.Remove(prop);
}
}
allProperties = someProperties.AsEnumerable();
}
var keyProperties = KeyPropertiesCache(type);
for (var i = 0; i < allProperties.Count(); i++)
{
var property = allProperties.ElementAt(i);
if (keyProperties.Contains(property)) continue;
sb.Append(property.Name);
if (i < allProperties.Count() - 1)
sb.Append(", ");
}
sb.Append(") values (");
for (var i = 0; i < allProperties.Count(); i++)
{
var property = allProperties.ElementAt(i);
if (keyProperties.Contains(property)) continue;
sb.AppendFormat("#{0}", property.Name);
if (i < allProperties.Count() - 1)
sb.Append(", ");
}
sb.Append(") ");
connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout);
//NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE
var r = connection.Query("select ##IDENTITY id");
//tx.Commit();
return (int)r.First().id;
//}
}
/// <summary>
/// Updates entity in table "Ts", checks if the entity is modified if the entity is tracked by the Get() extension.
/// </summary>
/// <typeparam name="T">Type to be updated</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToUpdate">Entity to be updated</param>
/// <returns>true if updated, false if not found or not modified (tracked entities)</returns>
public static bool UpdateEntity<T>(this IDbConnection connection, T entityToUpdate, string[] ExcludeProperties = null, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var proxy = entityToUpdate as IProxy;
if (proxy != null)
{
if (!proxy.IsDirty) return false;
}
var type = typeof(T);
var keyProperties = KeyPropertiesCache(type);
if (keyProperties.Count() == 0)
throw new ArgumentException("Entity must have at least one [Key] property");
var name = GetTableName(type);
var sb = new StringBuilder();
sb.AppendFormat("update {0} set ", name);
var allProperties = TypePropertiesCache(type);
if (ExcludeProperties != null)
{
List<PropertyInfo> someProperties = allProperties.ToList();
foreach (PropertyInfo prop in allProperties.ToArray())
{
if (ExcludeProperties.Contains(prop.Name))
{
someProperties.Remove(prop);
}
}
allProperties = someProperties.AsEnumerable();
}
var nonIdProps = allProperties.Where(a => !keyProperties.Contains(a));
for (var i = 0; i < nonIdProps.Count(); i++)
{
var property = nonIdProps.ElementAt(i);
sb.AppendFormat("{0} = #{1}", property.Name, property.Name);
if (i < nonIdProps.Count() - 1)
sb.AppendFormat(", ");
}
sb.Append(" where ");
for (var i = 0; i < keyProperties.Count(); i++)
{
var property = keyProperties.ElementAt(i);
sb.AppendFormat("{0} = #{1}", property.Name, property.Name);
if (i < keyProperties.Count() - 1)
sb.AppendFormat(" and ");
}
var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction);
return updated > 0;
}
/// <summary>
/// Delete entity in table "Ts".
/// </summary>
/// <typeparam name="T">Type of entity</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToDelete">Entity to delete</param>
/// <returns>true if deleted, false if not found</returns>
public static bool Delete<T>(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var keyProperties = KeyPropertiesCache(type);
if (keyProperties.Count() == 0)
throw new ArgumentException("Entity must have at least one [Key] property");
var name = GetTableName(type);
var sb = new StringBuilder();
sb.AppendFormat("delete from {0} where ", name);
for (var i = 0; i < keyProperties.Count(); i++)
{
var property = keyProperties.ElementAt(i);
sb.AppendFormat("{0} = #{1}", property.Name, property.Name);
if (i < keyProperties.Count() - 1)
sb.AppendFormat(" and ");
}
var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction: transaction, commandTimeout: commandTimeout);
return deleted > 0;
}
class ProxyGenerator
{
private static readonly Dictionary<Type, object> TypeCache = new Dictionary<Type, object>();
private static AssemblyBuilder GetAsmBuilder(string name)
{
var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name },
AssemblyBuilderAccess.Run); //NOTE: to save, use RunAndSave
return assemblyBuilder;
}
public static T GetClassProxy<T>()
{
// A class proxy could be implemented if all properties are virtual
// otherwise there is a pretty dangerous case where internal actions will not update dirty tracking
throw new NotImplementedException();
}
public static T GetInterfaceProxy<T>()
{
Type typeOfT = typeof(T);
if (TypeCache.ContainsKey(typeOfT))
{
return (T)TypeCache[typeOfT];
}
var assemblyBuilder = GetAsmBuilder(typeOfT.Name);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter
var interfaceType = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy);
var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(),
TypeAttributes.Public | TypeAttributes.Class);
typeBuilder.AddInterfaceImplementation(typeOfT);
typeBuilder.AddInterfaceImplementation(interfaceType);
//create our _isDirty field, which implements IProxy
var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder);
// Generate a field for each property, which implements the T
foreach (var property in typeof(T).GetProperties())
{
var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute);
CreateProperty<T>(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId);
}
var generatedType = typeBuilder.CreateType();
//assemblyBuilder.Save(name + ".dll"); //NOTE: to save, uncomment
var generatedObject = Activator.CreateInstance(generatedType);
TypeCache.Add(typeOfT, generatedObject);
return (T)generatedObject;
}
private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder)
{
var propType = typeof(bool);
var field = typeBuilder.DefineField("_" + "IsDirty", propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty("IsDirty",
System.Reflection.PropertyAttributes.None,
propType,
new Type[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName |
MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig;
// Define the "get" and "set" accessor methods
var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + "IsDirty",
getSetAttr,
propType,
Type.EmptyTypes);
var currGetIL = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, field);
currGetIL.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + "IsDirty",
getSetAttr,
null,
new Type[] { propType });
var currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ret);
property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
var getMethod = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy).GetMethod("get_" + "IsDirty");
var setMethod = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy).GetMethod("set_" + "IsDirty");
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
return currSetPropMthdBldr;
}
private static void CreateProperty<T>(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity)
{
//Define the field and the property
var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty(propertyName,
System.Reflection.PropertyAttributes.None,
propType,
new Type[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.Virtual |
MethodAttributes.HideBySig;
// Define the "get" and "set" accessor methods
var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName,
getSetAttr,
propType,
Type.EmptyTypes);
var currGetIL = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, field);
currGetIL.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName,
getSetAttr,
null,
new Type[] { propType });
//store value in private field and set the isdirty flag
var currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldc_I4_1);
currSetIL.Emit(OpCodes.Call, setIsDirtyMethod);
currSetIL.Emit(OpCodes.Ret);
//TODO: Should copy all attributes defined by the interface?
if (isIdentity)
{
var keyAttribute = typeof(KeyAttribute);
var myConstructorInfo = keyAttribute.GetConstructor(new Type[] { });
var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, new object[] { });
property.SetCustomAttribute(attributeBuilder);
}
property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
var getMethod = typeof(T).GetMethod("get_" + propertyName);
var setMethod = typeof(T).GetMethod("set_" + propertyName);
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
}
}
}
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : Attribute
{
public TableAttribute(string tableName)
{
Name = tableName;
}
public string Name { get; private set; }
}
}

I wrote a lightweight ORM that's an extension of Dapper. It actually does what you need. If you leave off the "Member" attribute of the property, the ORM will exclude it from the Insert.
You can access it at https://www.github.com/ricericebaby/ADOCRUD

Related

Error inserting data from many method return lists to a database in c#

I need help here with part of my code so here it is:
I have 6 methods as you can see below that parse incoming data and then returns it as a list, so my issue is to send that list data to my database table SerialNumber, each method of the lists is a separate field that will fill a database column.
So for example the parse material will fill the database materiallookupcode column and the same for the others.
Here is an image of the database table
Here is the code of all 5 methods that reads the data and then returns it and I need this data send to my database
private List<string> ParseMaterial()
{
var materialList = new List<string>();
foreach (var material in _connection.GetBarcodeList())
{
materialList.Add(material.Substring(10, 5));
}
return materialList;
}
private List<string> ParseLot()
{
var lotList = new List<string>();
var establishmentList = GetEstablishmentCode();
foreach (var lot in _connection.GetBarcodeList())
{
if (establishmentList.Contains("038"))
{
lotList.Add(lot.Substring(28, 6) + _lotEstablishment.LoganSport038Property);
}
if (establishmentList.Contains("072"))
{
lotList.Add(lot.Substring(28, 6) + _lotEstablishment.LouisaCounty072Property);
}
if (establishmentList.Contains("086"))
{
lotList.Add(lot.Substring(28, 6) + _lotEstablishment.Madison086Property);
}
if (establishmentList.Contains("089"))
{
lotList.Add(lot.Substring(28, 6) + _lotEstablishment.Perry089Property);
}
if (establishmentList.Contains("069"))
{
lotList.Add(lot.Substring(28, 6) + _lotEstablishment.StormLake069Property);
}
if (establishmentList.Contains("088"))
{
lotList.Add(lot.Substring(28, 6) + _lotEstablishment.Waterloo088Property);
}
if (establishmentList.Contains("265"))
{
lotList.Add(lot.Substring(28, 6) + _lotEstablishment.GoodLetsVille265Property);
}
if (establishmentList.Contains("087"))
{
lotList.Add(lot.Substring(28, 6) + _lotEstablishment.CouncilBluffs087Property);
}
if (establishmentList.Contains("064"))
{
lotList.Add(lot.Substring(28, 6) + _lotEstablishment.Sherman064Property);
}
}
return lotList;
}
private List<string> ParseSerialNumber()
{
var serialNumberList = new List<string>();
foreach (var serialNumber in _connection.GetBarcodeList())
{
serialNumberList.Add(serialNumber.Substring(36, 10));
}
return serialNumberList;
}
public List<string> ParseNetWeight()
{
var netWeightList = new List<string>();
foreach (var netWeight in _connection.GetBarcodeList())
{
netWeightList.Add(netWeight.Substring(22, 4));
}
return netWeightList;
}
public List<string> ParseGrossWeight()
{
var grossWeightList = new List<string>();
foreach (var grossWeight in _connection.GetBarcodeList())
{
grossWeightList.Add(grossWeight.Substring(22, 4));
}
return grossWeightList;
}
public List<string> FullBarcode()
{
var receiveFullBarcodeList = new List<string>();
foreach (var fullBarcode in _connection.GetBarcodeList())
{
receiveFullBarcodeList.Add(fullBarcode);
}
return receiveFullBarcodeList;
}
public List<string> GetEstablishmentCode()
{
var establishmentList = new List<string>();
foreach (var establishmentCode in _connection.GetBarcodeList())
{
establishmentList.Add(establishmentCode.Substring(36, 3));
}
return establishmentList;
}
The issue is here the button when clicking will read all 5 methods and send it to the database, I am sure the part where I making the list of variables to a string and the separator part is wrong so I need how is the correct way to add those list to each column of the database
private async void btn_SubmitData_Click(object sender, EventArgs e)
{
// parse list methodss
var materialList = ParseMaterial();
var lotList = ParseLot();
var netWeightList = ParseNetWeight();
var grossWeightList = ParseGrossWeight();
var serialNumberList = ParseSerialNumber();
var fullSerialNumberList = FullBarcode();
var material = "";
var lot = "";
var net = "";
var gross = "";
var serial = "";
var fullSerial = "";
var currentUser = _currentUser.GetCurrentUsernameOnApp();
var licensePlateId = GetLicensePlateIds();
for (var i = 0; i < _connection.GetBarcodeList().Count; i++)
{
material = materialList[i];
lot = lotList[i];
net = netWeightList[i];
gross = grossWeightList[i];
serial = serialNumberList[i];
fullSerial = fullSerialNumberList[i];
}
// database table and columns
var serialNumbersInsert = new List<SerialNumber>
{
new SerialNumber
{
SerialNumberLookupCode = serial,
NetWeight = Convert.ToDecimal(net) / 100,
GrossWeight = Convert.ToDecimal(gross) / 100,
LotLookupCode = lot,
MaterialLookupCode = material,
FullSerialNumberLookupCode = fullSerial,
CreatedSysDateTime = DateTime.Now,
ModifiedSysDateTime = DateTime.Now,
CreatedSysUser = currentUser,
ModifiedSysUser = currentUser,
LicensePlateId = licensePlateId
}
};
// insert to the database
foreach (var list in serialNumbersInsert)
{
_unitOfWork.SerialNumbers.Add(list);
}
await _unitOfWork.Complete();
}
Here is the SerialNumber domain class that represents a database table using a code first migration
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BarcodeReceivingApp.Core.Domain
{
// domain class, represents a database table in sql server using code
// first migration
public class SerialNumber
{
public int Id { get; set; }
public int LicensePlateId { get; set; }
public string FullSerialNumberLookupCode { get; set; }
public string SerialNumberLookupCode { get; set; }
public decimal NetWeight { get; set; }
public decimal GrossWeight { get; set; }
public string LotLookupCode { get; set; }
public string MaterialLookupCode { get; set; }
public DateTime CreatedSysDateTime { get; set; }
public DateTime ModifiedSysDateTime { get; set; }
public string CreatedSysUser { get; set; }
public string ModifiedSysUser { get; set; }
}
}
I search other places but could not find a good solution so far, so any help is appreciate it.
I was able to resolve my question, what I did is to assign all lists in a loop and then assign them to each column in the database.
But I am still searching for a better and more clean way to this solution
private async void btn_SubmitData_Click(object sender, EventArgs e)
{
// parse list methods - represents each field of the database column
var materialList = ParseMaterial();
var lotList = ParseLot();
var netWeightList = ParseNetWeight();
var grossWeightList = ParseGrossWeight();
var serialNumberList = ParseSerialNumber();
var fullSerialNumberList = FullBarcode();
var currentUser = _currentUser.GetCurrentUsernameOnApp();
var licensePlateId = GetLicensePlateIds();
for (var i = 0; i < _connection.GetBarcodeList().Count; i++)
{
var serialNumbersInsert = new List<SerialNumber>
{
new SerialNumber
{
SerialNumberLookupCode = materialList[i],
NetWeight = Convert.ToDecimal(netWeightList[i]) / 100,
GrossWeight = Convert.ToDecimal(grossWeightList[i]) / 100,
LotLookupCode = lotList[i],
MaterialLookupCode = materialList[i],
FullSerialNumberLookupCode = fullSerialNumberList[i],
CreatedSysDateTime = DateTime.Now,
ModifiedSysDateTime = DateTime.Now,
CreatedSysUser = currentUser,
ModifiedSysUser = currentUser,
LicensePlateId = licensePlateId
}
};
foreach (var list in serialNumbersInsert)
{
_unitOfWork.SerialNumbers.Add(list);
}
await _unitOfWork.Complete();
}
}

How to restrict the number of a blocktype used in contentarea?

I have a block type which I am using on a specific content area on a specific page. is there any way that I can validate(on page level or contentarea level) that block is not used more than once?
Here's a sample validation attribute class that should help. I am working on a "Validation Rules" nuget package that I thought could include this. I only included the "Min by object type" rule but will add more before it's released.
Class:
using EPiServer;
using EPiServer.Core;
using EPiServer.ServiceLocation;
using System;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
namespace eGandalf.Epi.Validation.Lists
{
/// <summary>
/// Detects whether the minimum required items of a specific type within a ContentArea condition has been met. Only supports items that can be loaded by IContentLoader. Supports type inheritance.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = true)]
public class MinimumOfTypeAttribute : ValidationAttribute
{
public int Limit { get; }
public Type ObjectType { get; }
public MinimumOfTypeAttribute(int limit, Type t)
{
Limit = limit;
ObjectType = t;
}
public override bool IsValid(object value)
{
if (value == null && Limit > 0) return false;
var area = value as ContentArea;
if (area != null) return ValidateContentArea(area);
throw new TypeMismatchException("Minimum of type only works with ContentArea properties.");
}
private bool ValidateContentArea(ContentArea area)
{
if (area?.Items?.Count < Limit) return false;
var typeCount = 0;
foreach (var item in area.Items)
{
if (CanLoadContentByType(item.ContentLink))
{
typeCount++;
// Return as soon as the validation is true.
if (typeCount >= Limit) return true;
}
}
return false;
}
private bool CanLoadContentByType(ContentReference reference)
{
var loader = ServiceLocator.Current.GetInstance<IContentLoader>();
var loaderType = loader.GetType();
MethodInfo getMethod = loaderType.GetMethod("Get", new Type[] { typeof(ContentReference) });
MethodInfo genericGet = getMethod.MakeGenericMethod(new[] { ObjectType });
try
{
var content = genericGet.Invoke(loader, new object[] { reference });
return content != null;
}
catch (Exception ex)
{
return false;
}
}
public override string FormatErrorMessage(string name)
{
return $"ContentArea {name} must include at least {Limit} items of type {ObjectType.Name}";
}
}
}
Sample application on a content area:
[MinimumOfType(1, typeof(RssReaderBlock))]
public virtual ContentArea RelatedContentArea { get; set; }
Result in editor view when invalid (prevents publish):
Nothing built-in, but you can easily hook up to the SavingContent or PublishingContent events and validate content before it's saved/published.
Examples here and there.

Reference a scalar property from a string value

I have a combobox that contains a list of scalar properties of an entity. I need to do a search on the property specified by the user in the combobox.
How do I reference the property when I have it as a string?
Something similar to DBSet<> for entity.
Example:
if (order.Firstname != null && order.Firstname.ToLower().Contains(textBoxSearch.Text.ToLower()))
In the above example, I need to replace Firstname with Surname or any other property, at runtime, depending on what the user selected.
You can always use reflection, for your case you'll need something along these lines:
static void Main(string[] args)
{
var entity = new Entity {
Height = 172,
FirstName = "Foo",
Birthday = new DateTime(1, 1, 1995)
};
var firstName = GetEntityProperty<string>(entity, "FirstName");
}
public static T GetEntityProperty<T>(object entity, string propertyName)
{
var type = entity.GetType();
var property = type.GetProperty(propertyName);
return (T)property.GetValue(entity);
}
Provided solution works, but it is not strongly-typed, so it is sensible to property renaming. A strongly-typed approach can be used, but it requires some setup. However, part of the setup is so generic that it can be reused:
public class Order
{
public String OrderNo { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String Misc { get; set; }
}
// clearly define properties that allow search
public enum OrderSearchableProp
{
OrderNo = 1,
FirstName = 2,
LastName = 3
}
class Program
{
// strongly-type requires an explicit mapping between property and its expression within the order object
static Dictionary<OrderSearchableProp, Expression<Func<Order, String>>> OrderSearchableMap = new Dictionary<OrderSearchableProp, Expression<Func<Order, string>>>()
{
{ OrderSearchableProp.OrderNo, o => o.OrderNo },
{ OrderSearchableProp.FirstName, o => o.FirstName },
{ OrderSearchableProp.LastName, o => o.LastName },
};
// this gets a PropertyInfo based from an Expression. It should be placed into a generic utility class
// credit goes to Daniel - http://stackoverflow.com/a/17116267/2780791
public static PropertyInfo GetPropertyFromExpression<T, TProp>(Expression<Func<T, TProp>> GetPropertyLambda)
{
MemberExpression Exp = null;
//this line is necessary, because sometimes the expression comes in as Convert(originalexpression)
if (GetPropertyLambda.Body is UnaryExpression)
{
var UnExp = (UnaryExpression)GetPropertyLambda.Body;
if (UnExp.Operand is MemberExpression)
{
Exp = (MemberExpression)UnExp.Operand;
}
else
throw new ArgumentException();
}
else if (GetPropertyLambda.Body is MemberExpression)
{
Exp = (MemberExpression)GetPropertyLambda.Body;
}
else
{
throw new ArgumentException();
}
return (PropertyInfo)Exp.Member;
}
public static IList<Order> getFilteredOrders(int propFilterValue, IList<Order> orders, String needle)
{
var filterValue = (OrderSearchableProp)propFilterValue;
var filterProp = OrderSearchableMap[filterValue];
var lowNeedle = needle?.ToLower() ?? String.Empty;
return orders.Where(o =>
{
var propInfo = GetPropertyFromExpression<Order, String>(filterProp);
var propValue = (String) propInfo.GetValue(o) ?? String.Empty;
return propValue.ToLower().Contains(lowNeedle);
}).ToList();
}
static void Main(string[] args)
{
// can be used to populate the combo items
// otherwise, not used in this example
OrderSearchableProp[] enumValues = (OrderSearchableProp[])Enum.GetValues(typeof(OrderSearchableProp));
// test orders
var orderList = new List<Order>()
{
new Order() { OrderNo = "1234ABC", FirstName = "George", LastName = "Taylor", Misc = "Extra information"},
new Order() { OrderNo = "AB10", FirstName = "Anonymous", LastName = "X", Misc = "No comment"}
};
// test OrderNo search
var searchProp = (int) OrderSearchableProp.OrderNo;
var foundOrders = getFilteredOrders(searchProp, orderList, "ABC");
// test FirstName search
searchProp = (int)OrderSearchableProp.FirstName;
foundOrders = getFilteredOrders(searchProp, orderList, "a");
// test LastName search with empty string
searchProp = (int)OrderSearchableProp.LastName;
foundOrders = getFilteredOrders(searchProp, orderList, "");
// empty return
searchProp = (int)OrderSearchableProp.OrderNo;
foundOrders = getFilteredOrders(searchProp, orderList, null);
}
}

BindingList<> (master) with a composed BindingList<> (child) reference

I have a situation where a BindingList<> represents a collection of POCOs that have sub-collections of similar nature, Here is a sample code of two such POCOs and their respective lists:
The DirectoryTypePoco
public class DirectoryTypePoco : IBasePoco
{
public DirectoryTypePoco()
{
}
public DirectoryTypePoco(Int16 directoryTypeId, String directoryImplementation, String directoryDescription, DirectoryDefinitionPocoList directoryDefinition)
{
DirectoryTypeId = directoryTypeId;
DirectoryImplementation = directoryImplementation;
DirectoryDescription = directoryDescription;
DirectoryDefinition = directoryDefinition;
}
public Int16 DirectoryTypeId { get; set; }
public String DirectoryImplementation { get; set; }
public String DirectoryDescription { get; set; }
public DirectoryDefinitionPocoList DirectoryDefinition { get; set; }
public object GenerateEntity(GenericRepository repository, params object[] parameters)
{
var lastMaxEntityId = repository.GetQuery<DirectoryType>().Select(select => #select.DirectoryTypeId).DefaultIfEmpty().Max();
var newEntity = new DirectoryType
{
DirectoryTypeId = (short)(lastMaxEntityId + 1),
DirectoryImplementation = this.DirectoryImplementation,
DirectoryDescription = this.DirectoryDescription
};
return newEntity;
}
}
And the BindingList<DirectoryTypePoco>:
public class DirectoryTypePocoList : BindingList<DirectoryTypePoco>
{
public DirectoryTypePocoList()
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryType>();
foreach (var r in query)
{
Add(new DirectoryTypePoco(r.DirectoryTypeId, r.DirectoryImplementation, r.DirectoryDescription, new DirectoryDefinitionPocoList(r.DirectoryTypeId)));
}
}
}
public DirectoryTypePocoList(short directoryTypeId)
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryType>(where => where.DirectoryTypeId == directoryTypeId);
foreach (var r in query)
{
Add(new DirectoryTypePoco(r.DirectoryTypeId, r.DirectoryImplementation, r.DirectoryDescription, new DirectoryDefinitionPocoList(r.DirectoryTypeId)));
}
}
}
}
The second object: DirectoryDefinitionPoco
public class DirectoryDefinitionPoco : IBasePoco
{
public DirectoryDefinitionPoco()
{
}
public DirectoryDefinitionPoco(Int16 directoryTypeId, Byte parameterId, String parameterName, String parameterValidation, Boolean encryptionRequired, PocoChangeType changeType = PocoChangeType.None)
{
DirectoryTypeId = directoryTypeId;
ParameterId = parameterId;
ParameterName = parameterName;
ParameterDescription = parameterName;
ParameterRequired = false;
ParameterValidation = parameterValidation;
EncryptionRequired = encryptionRequired;
}
public Int16 DirectoryTypeId { get; set; }
public Byte ParameterId { get; set; }
public String ParameterName { get; set; }
public String ParameterDescription { get; set; }
public String ParameterValidation { get; set; }
public Boolean ParameterRequired { get; set; }
public Boolean EncryptionRequired { get; set; }
public object GenerateEntity(GenericRepository repository, params object[] parameters)
{
var masterId = (short) parameters[0];
var lastMaxEntityId = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == masterId).Select(select => #select.ParameterId).DefaultIfEmpty().Max();
var newEntity = new DirectoryDefinition
{
DirectoryTypeId = (short)parameters[0],
ParameterId = (byte)(lastMaxEntityId + 1),
ParameterName = this.ParameterName,
ParameterDescription = this.ParameterDescription,
ParameterValidation = this.ParameterValidation,
ParameterRequired = this.ParameterRequired,
EncryptionRequired = this.EncryptionRequired
};
return newEntity;
}
}
And BindingList<DirectoryDefinitionPoco>:
public class DirectoryDefinitionPocoList : BindingList<DirectoryDefinitionPoco>
{
public DirectoryDefinitionPocoList(short directoryTypeId)
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == directoryTypeId);
foreach (var r in query)
{
Add(new DirectoryDefinitionPoco(r.DirectoryTypeId, r.ParameterId, r.ParameterName, r.ParameterValidation, r.EncryptionRequired));
}
}
}
public List<DirectoryDefinition> GetSourceQuery()
{
List<DirectoryDefinition> result;
using (var repository = new GenericRepository(new PWRDbContext()))
{
result = repository.GetQuery<DirectoryDefinition>().ToList();
}
return result;
}
public List<DirectoryDefinition> GetSourceQuery(short directoryTypeId)
{
List<DirectoryDefinition> result;
using (var repository = new GenericRepository(new PWRDbContext()))
{
result = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == directoryTypeId).ToList();
}
return result;
}
}
On the form, I load the data into the grid through a BindingSource component. The child rows are added properly and the data is valid.
Here is the issue: I'm able to add new DirectoryTypePoco but when try to add a DirectoryDefinitionPoco, in the code, the the DirectoryDefinitionPocoobject that I get has a zero for it's parent object. In the above picture, the Test5.dll234 is a DirectoryTypePoco with DirectoryTypeId = 8 and all child under it are ok except the new one I create. What am I suppose to do to make sure I have Master-Child relation in this case?
Ok. It seems that there are two thing I should have noticed in my design.
The individual child Poco needs to know the parent Poco through a reference.
The DevExpress Grid has methods that allow for retrieving the attached data to a parent row while in the child view' particular row.
The first part is straightforwards: add a new property in the child poco of parent poco type.
This however, in my case, doesn't solve my issue as when I visually add a new row on the grid, the default constructor is invoked and it takes no parameters and hence the parent poco reference will remain NULL and the Ids (numeric) will be defaulted to 0
The second point helped fix my issue completely. I was able to conjure up an extension method for the XtraGrid's GridView as follows:
public static class DevExpressGridHelper
{
public static IBasePoco GetPocoFromSelectedRow(this BaseView view)
{
return (IBasePoco)view.GetRow(((GridView)view).FocusedRowHandle);
}
public static IBasePoco GetParentPocoFromSelectedRow(this GridView view)
{
if (view.ParentView !=null)
{
// return (IBasePoco)(view.ParentView).GetRow(((GridView)(view.ParentView)).FocusedRowHandle);
return (IBasePoco)((GridView)view.ParentView).GetFocusedRow();
}
return null;
}
}
And used it as follows:
private void GridMain_Level_1_RowUpdated(object sender, RowObjectEventArgs e)
{
var view = sender as GridView;
if (view == null)
{
return;
}
var pocoObject = e.Row as DirectoryDefinitionPoco;
if (pocoObject == null)
{
return;
}
var parentPocoObject = view.GetParentPocoFromSelectedRow();
if (parentPocoObject == null)
{
return;
}
if (view.IsNewItemRow(e.RowHandle))
{
Create(pocoObject, parentPocoObject);
}
else
{
Update(pocoObject);
}
}

Custom binding to a property in XAML / WPF

If for example I have a view model class like
class ViewModel {
Data Data { get; set;}
}
and
class Data : IClonable {
public int Value0 {get; private set;}
public int Value1 {get; private set;}
Data SetValue0(int value){
var r = (Data) this.Clone();
r.Value0 = value;
return r;
}
Data SetValue1(int value){
var r = (Data) this.Clone();
r.Value1 = value;
return r;
}
}
in my XAML, with an instance of ViewModel as the DataContext, I would like to two way bind my text boxes like so
<TextBox Text="{Binding Data.Value0}"/>
<TextBox Text="{Binding Data.Value1}"/>
now obviously this doesn't work. What I need to explain to the binding is that to set the subproperties I have to call the method Set${propName} and then replace the entire property from the root of the path.
Note the actual implementation I have of the immutable object pattern is much more complex than above but a solution for the above pattern I could extrapolate for my more complex setup. Is there anything I can provide to the binding to make it perform what I want?
For information my actual immutable object pattern allows things like
var newValue = oldValue.Set(p=>p.Prop1.Prop2.Prop3.Prop4, "xxx");
I now have in my XAML code
<c:EditForLength Grid.Column="2" Value="{rx:ImmutableBinding Data.Peak}"/>
This becomes ImmutableBinding is a markup extension that returns a binding to a proxy object so you could imagine that the above is rewritten as
<c:EditForLength Grid.Column="2" Value="{Binding Value, ValidatesOnNotifyDataErrors=True}"/>
with the binding direct to the proxy object and the proxy object shadowing the real data and the validation errors. My code for the proxy and immutable binding object is
Note the code uses ReactiveUI calls and some of my own custom code but the general pattern of building a custom binding using a proxy to mediate validation errors should be clear. Also the code is probably leaking memory and I'll be checking that soon.
[MarkupExtensionReturnType(typeof(object))]
public class ImmutableBinding : MarkupExtension
{
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
public ImmutableBinding(PropertyPath path) { Path = path; }
/// <summary>
/// Returns a custom binding that inserts a proxy object between
/// the view model and the binding that maps immutable persistent
/// writes to the DTO.
/// </summary>
/// <param name="provider"></param>
/// <returns></returns>
override public object ProvideValue( IServiceProvider provider )
{
var pvt = provider as IProvideValueTarget;
if ( pvt == null )
{
return null;
}
var frameworkElement = pvt.TargetObject as FrameworkElement;
if ( frameworkElement == null )
{
return this;
}
if ( frameworkElement.DataContext == null )
{
return "";
}
var proxy = new Proxy();
var binding = new Binding()
{
Source = proxy,
Path = new PropertyPath("Value"),
Mode = BindingMode.TwoWay,
ValidatesOnDataErrors = true
};
var path = Path.Path.Split('.');
var head = path.First();
var tail = path.Skip(1)
.ToList();
var data = frameworkElement.DataContext as ValidatingReactiveObject;
if (data == null)
return null;
data.ErrorsChanged += (s, e) =>
{
if ( data.Errors.ContainsKey(Path.Path) )
{
proxy.Errors["Value"] = data.Errors[Path.Path];
}
else
{
proxy.Errors.Clear();
}
proxy.RaiseValueErrorChanged();
};
var subscription = data
.WhenAnyDynamic(path, change => change.Value )
.Subscribe(value => proxy.Value = value);
proxy
.WhenAnyValue(p => p.Value)
.Skip(1)
.DistinctUntilChanged()
.Subscribe
(value =>
{
var old = data.GetType()
.GetProperty(head)
.GetValue(data) as Immutable;
if (old == null) throw new NullReferenceException("old");
var #new = old.Set(tail, value);
data.GetType()
.GetProperty(head)
.SetValue(data, #new);
});
binding.ValidatesOnNotifyDataErrors = true;
return binding.ProvideValue(provider);
}
}
and the proxy object. Note that ValidatingReactiveObject implements INotifyDataErrorInfo
public class Proxy : ValidatingReactiveObject<Proxy>
{
object _Value;
public object Value
{
get { return _Value; }
set { this.ValidateRaiseAndSetIfChanged(ref _Value, value); }
}
public Proxy() { Value = 0.0; }
public void RaiseValueErrorChanged()
{
RaiseErrorChanged("Value");
RaiseErrorChanged("Error");
OnPropertyChanged("Error");
}
}

Resources