sqlkata / querybuilder

SQL query builder, written in c#, helps you build complex queries easily, supports SqlServer, MySql, PostgreSql, Oracle, Sqlite and Firebird
https://sqlkata.com
MIT License
3.08k stars 498 forks source link

Type attributes cached #623

Closed mucahitimre closed 1 year ago

mucahitimre commented 1 year ago

While examining the properties, getting property attributes every time was causing slowness in large and batch operations, I made it keep the property attributes where it holds the properties.

second call performange; Before: 00:00:00.0000734 After: 00:00:00.0000310

Cricle commented 1 year ago

I think use expression and cache check result will be better.

I changed to

private IEnumerable<KeyValuePair<string, object>> BuildKeyValuePairsFromObject(object data, bool considerKeys = false)
{
    var dictionary = new Dictionary<string, object>();
    var props = CacheDictionaryProperties.GetOrAdd(data.GetType(), type =>
    {
        var propDatas = new List<PropertyData>();
        foreach (var property in type.GetRuntimeProperties())
        {
            if (property.GetCustomAttribute(typeof(IgnoreAttribute)) != null)
            {
                continue;
            }

            var colAttr = property.GetCustomAttribute(typeof(ColumnAttribute)) as ColumnAttribute;

            var name = colAttr?.Name ?? property.Name;

            var hasColAttr = colAttr != null;

            var isKeyAttr = colAttr is KeyAttribute;
            var propData = new PropertyData
            {
                HasColumnAttribute = hasColAttr,
                IsKeyAttribute = isKeyAttr,
                PropertyInfo = property,
                Name = name,
            };
            propData.BuildGetter();
            propDatas.Add(propData);
        }
        return propDatas.ToArray();
    });
    for (int i = 0; i < props.Length; i++)
    {
        var property = props[i];
        var propertyValue = property.Getter(data);

        dictionary.Add(property.Name, propertyValue);

        if (considerKeys && property.HasColumnAttribute)
        {
            if (property.IsKeyAttribute)
            {
                this.Where(property.Name, propertyValue);
            }
        }
    }

    return dictionary;
}
private class PropertyData
{
    public PropertyInfo PropertyInfo;

    public string Name;

    public bool HasColumnAttribute;

    public bool IsKeyAttribute;

    public Func<object,object> Getter;

    public void BuildGetter()
    {
        var par1 = Expression.Parameter(typeof(object));
        Getter = Expression.Lambda<Func<object, object>>(
            Expression.Convert(
                Expression.Property(
                    Expression.Convert(par1, PropertyInfo.DeclaringType),
                        PropertyInfo.Name),typeof(object)), par1).Compile();
    }
}

In net6.0 Release

Test code here

var query = new Query();
var instance = new Installment();
query.BuildKeyValuePairsFromObject(instance);//JIT&Cache
var sw = Stopwatch.GetTimestamp();
for (int i = 0; i < 10000; i++)
{
    query.BuildKeyValuePairsFromObject(instance);
}
var ed = Stopwatch.GetTimestamp();
Console.WriteLine(new TimeSpan(ed - sw));

I try to eliminate repeated operations.

00:00:00.0221297 (Before) 00:00:00.0023049 (After)