Search code examples
sql-updatecode-firstdapperdapper-contrib

Use Dapper.Contrib Update, with classes that have special helper properties


I am new to Dapper and Dapper.Contrib. There is a class like this and there is a table in database with the same name:

public class Ware
{        
    public int ID { get; set; }
    public string Name { get; set; }
    public short UnitID { get; set; }
    public short TypeID { get; set; }
    public int CableCodeID { get; set; }
    public string Tag1 { get; set; }
    public string Tag2 { get; set; }
    public bool Discontinued { get; set; }
    public decimal Stock { get; set; } //this is not in database. this is helper
    public string UnitCaption { get; set; } //this is not in database. this is helper
    public string TypeCaption { get; set; } //this is not in database. this is helper
    public string FullCaption //this is not in database. this is helper
    {
        get
        {
            return $"{ID} {Name}";
        }
    }
}

I need to Update a whole list of this class to database. I use:

conection.Update(myList); // myList is List<Ware>

But it has an error when it run's:

System.Data.SqlClient.SqlException: 'Invalid column name 'Stock'.'
System.Data.SqlClient.SqlException: 'Invalid column name 'UnitCaption'.'
System.Data.SqlClient.SqlException: 'Invalid column name 'TypeCaption'.'
System.Data.SqlClient.SqlException: 'Invalid column name 'FullCaption'.'

How to fix this?


Solution

  • Copying the code from Dapper.Contrib source code:

    Computed

        [AttributeUsage(AttributeTargets.Property)]
        public class ComputedAttribute : Attribute { }
    

    Usage of Computed in code:

    private static List<PropertyInfo> ComputedPropertiesCache(Type type)
    {
                IEnumerable<PropertyInfo> pi;
                if (ComputedProperties.TryGetValue(type.TypeHandle, out pi))
                {
                    return pi.ToList();
                }
    
                var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList();
    
                ComputedProperties[type.TypeHandle] = computedProperties;
                return computedProperties;
    }
    

    Now Insert<T> and Update<T> has following code:

    var computedProperties = ComputedPropertiesCache(type);
    
    var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
    

    Now allPropertiesExceptKeyAndComputed is processed in the code further.

    WriteAttribute

     public class WriteAttribute : Attribute
     {
        public WriteAttribute(bool write)
        {
           Write = write;
        }
        public bool Write { get; }
    }
    

    Usage of IsWriteable in code:

     private static List<PropertyInfo> TypePropertiesCache(Type type)
     {
        IEnumerable<PropertyInfo> pis;
        if (TypeProperties.TryGetValue(type.TypeHandle, out pis))
        {
           return pis.ToList();
        }
    
        var properties = type.GetProperties().Where(IsWriteable).ToArray();
        TypeProperties[type.TypeHandle] = properties;
        return properties.ToList();
     }
    
     private static bool IsWriteable(PropertyInfo pi)
     {
         var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList();
         if (attributes.Count != 1) return true;
    
         var writeAttribute = (WriteAttribute)attributes[0];
         return writeAttribute.Write;
     }
    

    Now notice the function ComputedPropertiesCache pasted above for Computed it calls the method TypePropertiesCache, thus what would happen is, it will exclude both the properties (Write("false") and Computed), however Write attribute is meant for excluding the Type property from Caching, which is creates using ConcurrentDictionary, Its Computed, which is ideally meant for your use case. Hope it helps as expected.