Страницы

Поиск по вопросам

Показаны сообщения с ярлыком expressions. Показать все сообщения
Показаны сообщения с ярлыком expressions. Показать все сообщения

понедельник, 30 декабря 2019 г.

Падает производительность при использования предиката для гибкой сортировки в LINQ запросе

#c_sharp #entity_framework #linq #expressions


Пытаюсь сделать LINQ запрос в котором можно менять поле (одно) по которому нужно
производить сортировку. Использование переменной предиката позволяет это

public void GetMyData(string sortFieldName)
{
    Func orderPredicate = null;
    switch (sortFieldName)
    {    
         case "FIELD_STRING":
             orderPredicate = x => x.FIELD_STRING;
         case "FIELD_INT":
             orderPredicate = x => x.FIELD_INT;
         case "FIELD_DATE":
             orderPredicate = x => x.FIELD_DATE;
         default: 
             orderPredicate = x => x.FIELD_DATE;
    }

    var queryResult = db.MyEntity.OrderBy(orderPredicate);

    //...some logic

}


Но резко (в десятки раз) падает производительность по сравнению с прямым заданием
условия сортировки.
Насколько понимаю, должно помочь Expression. Меняю тип переменной

Expression> orderPredicate = null;


Но в рантайме выдаёт исключение


  Не удалось привести тип "System.DateTime" к типу "System.Object". LINQ to Entities
поддерживает только приведение типов-примитивов моделей EDM или типов перечисления.


Как же быть? Ведь, если я буду использовать прямое задание условия

public void GetMyData(string sortFieldName)
{
    var query = db.MyEntity;
    var orderedQuery = query.OrderBy(x => x.FIELD_DATE);
    switch (sortFieldName)
    {    
         case "FIELD_STRING":
             orderedQuery = query.OrderBy(x => x.FIELD_STRING); break;
         case "FIELD_INT":
             orderedQuery = query.OrderBy(x => x.FIELD_INT); break;
         case "FIELD_DATE":
             orderedQuery = query.OrderBy(x => x.FIELD_DATE); break;
    }

    //...some logic

}


, мне будет трудно усложнить запрос дополнительными инструкциями Where, Include и т.д.
    


Ответы

Ответ 1



Проблема в том, что когда вы пишите вот так: Expression> orderPredicate; orderPredicate = x => x.FIELD_DATE; Компилятор делает вот так: var _param = Expression.Parameter(typeof(MyEntity), "x"); orderPredicate = Expression.Lambda>( Expression.Convert( Expression.Property(_param, "FIELD_DATE"), typeof(object) ), _param ); Вот на этот самый Expression.Convert EF и ругается. Для того чтобы не было преобразования - возвращаемый тип делегата обязан совпадать с типом свойства, что в свою очередь означает что общую переменную orderPredicate вы использовать не можете. Теперь как строить такие запросы. А строятся они очень просто - через накопление IQueryable: IQueryable q = db.MyEntity; switch (sortFieldName) { case "FIELD_STRING": q = q.OrderBy(x => x.FIELD_STRING); case "FIELD_INT": q = q.OrderBy(x => x.FIELD_INT); case "FIELD_DATE": q = q.OrderBy(x => x.FIELD_DATE); default: q = q.OrderBy(x => x.FIELD_DATE); } Никакого усложнения тут нет, полученный запрос можно точно также дополнять другими условиями: q = q.Where(x => x.Foo > 42); PS поскольку у вас в параметре sortFieldName передается имя свойства, построение выражения можно еще немного упростить. Да, класс Queryable не дает нам вызвать OrderBy с неизвестным заранее именем свойства - но никто не мешает "раскрыть" этот метод и работать непосредственно с IQueryable: IQueryable q = db.MyEntity; var entityParam = Expression.Parameter(typeof(MyEntity)); var propExpr = Expression.Property(entityParam, sortFieldName) q = q.Provider.CreateQuery( Expression.Call(typeof(Queryable), "OrderBy", new [] { entityParam.Type, propExpr.Type }, // Типы-параметры для метода Queryable.OrderBy<,> q.Expression, Expression.Quote(Expression.Lambda(propExpr, entityParam)) ) ); Также вместо того чтобы делать это вручную - можно использовать библиотеку System.Linq.Dynamic, скачав ее из nuget: q = q.OrderBy(sortFieldName);

пятница, 27 декабря 2019 г.

Entity Framework ORA-00932: inconsistent datatypes: “'expected CLOB got CHAR”

#c_sharp #oracle #entity_framework #expressions


Для доступа к БД Oracle из MVC ASP.Net приложения используется библиотека Oracle.ManagedDataAccess.EntityFramework
6.122.1.0 (это самая свежая версия библиотеки из NuGet на 14 ноября 2017)

protected override Expression> getSelector()
{   
    return m => new
    {
        ID = m.ID,
        NAME = m.Name,
        LONGSTRING = "Bla-bla-bla-bla...some thousands characters..." + 
                      m.ID + "bla-bla...bla" 
    };
}

protected override ProblemMethod()
{
    var result = db.MyEntity.Select(getSelector()).ToList();
}


Проблема заключается в том, что в LONGSTRING собирается очень длинная строка (тысячи
символов), и вызов Select выбрасывает исключение


  ORA-00932: inconsistent datatypes: "'expected CLOB got CHAR"


Так уж строиться архитектура, что мне нужно получить именно Expression для вызова
некоторых более сложных Select-ов в моём классе.
Как побороть или обойти проблему? Как вариант обхода проблемы, можно заставить EF
выполнить Select не в СУБД, а на клиенте. Но как это сделать?

PS: Этот же вопрос на английском.

Update

Уместно было привести структуру MyEntity 

 CREATE TABLE MyEntity (ID NUMBER(10), Name VARCHAR2(100));

    


Ответы

Ответ 1



Перевод https://stackoverflow.com/a/47226720/5574962 Если вы хотите выполнить Select на клиенте (т.е. загрузить все MyEntity и фильтровать затем на клиенте) var result = db.MyEntity.ToList().AsQueryable().Select(getSelector()).ToList(); Первый ToList() загрузит все сущности из базы данных. AsQueryable() позволит использовать Expression, в последующих методах.

Ответ 2



Это известная проблема при работе с LOB'ами в EF. Ходят слухи, что она исправлена в последней версии Oracle.ManagedDataAccess.dll. Можешь обновить и попробовать. Также ты можешь вручную поправить код, заменив обращение к полю из выборки на явный вызов GetOracleClob.

пятница, 20 декабря 2019 г.

Python. Чем на самом деле является print?

#python #python_2x #print #expressions


Почему функция/оператор/набор символов (или что оно на самом деле) print не может
работать в выражениях?

Python 2.7.9 (default, Jun 29 2016, 13:08:31) 
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print('hahaha')
hahaha
>>> 0 or print('hahaha')
  File "", line 1
    0 or print('hahaha')
             ^
SyntaxError: invalid syntax
>>> def test(): print('hahaha')
... 
>>> 0 or test()
hahaha
>>> 

    


Ответы

Ответ 1



В Python 2.X print является ключевым словом (вроде return во многих языках), по этому после него выражение не обязательно должно быть заключено в скобки. И оно не является функцией и не может возвращать значения (даже None). По этому оно не может быть частью логического выражения: 0 or return("hahaha") В Pyton 3.X это фнукция. По этому там требуется ее аргументы заключать в скобки, и она возвращает None (который в логическом контексте действует как False), по этому такое логическое выражение не вызовет ошибок.

воскресенье, 1 декабря 2019 г.

C#, Как скопировать поля объекта в другой объект, создав для этого expression

#c_sharp #net #рефлексия #expressions


Хочу написать функцию, которая будет копировать одинаковые свойства из одного объекта
в другой, но при этом чтобы она работала быстро.
Вот что есть:

static class Copy
{
    private static readonly ConcurrentDictionary> PropertiesDictionaries
        = new ConcurrentDictionary>();

    public static void Сopyfields(object source, object target)
    {
        var sourceType = source.GetType();
        var targetType = target.GetType();
        var sourceProperties = GetProperties(sourceType);
        var targetPropertyes = GetProperties(targetType);

        foreach (var targetProperty in targetPropertyes)
        {
            if (sourceProperties.TryGetValue(targetProperty.Key, out var sourceProperty))
            {
                targetProperty.Value.SetValue(target, sourceProperty.GetValue(source));
            }
        }
    }

    private static ConcurrentDictionary GetProperties(Type objType)
    {
        if (!PropertiesDictionaries.TryGetValue(objType, out var propertiesInfoDictionary))
        {
            var infos = objType.GetProperties();
            propertiesInfoDictionary = new ConcurrentDictionary();
            foreach (var propertyInfo in infos)
            {
                propertiesInfoDictionary.GetOrAdd(propertyInfo.Name, propertyInfo);
            }
            PropertiesDictionaries.GetOrAdd(objType, propertiesInfoDictionary);
        }
        return propertiesInfoDictionary;
    }
}


Как мне переписать эту функцию, чтобы создавать expression и использовать рефлексию
только 1 раз для каждой комбинации типов?
    


Ответы

Ответ 1



Вот вам простейший вариант: class MapperFactory { public static Action CreateMapper() { var sourceType = typeof(P); var targetType = typeof(Q); var sourceProperties = GetVisibleProperties(sourceType); var targetProperties = GetVisibleProperties(targetType); var fromVar = Expression.Variable(sourceType, "from"); var toVar = Expression.Variable(targetType, "to"); Expression CreateCopyProperty(string name) { try { return Expression.Assign( Expression.Property(toVar, targetProperties[name]), Expression.Property(fromVar, sourceProperties[name])); } catch (Exception ex) { throw new ArgumentException($"Невозможно скопировать свойство {name}", ex); } } var commonProperties = sourceProperties.Keys.Intersect(targetProperties.Keys) .OrderBy(n => n); var assignExpressions = commonProperties.Select(CreateCopyProperty); var assignment = Expression.Block(assignExpressions); var lambda = Expression.Lambda>(assignment, fromVar, toVar); return lambda.Compile(); } static Dictionary GetVisibleProperties(Type type) { IEnumerable GetTypeChain(Type t) => t == null ? Enumerable.Empty() : GetTypeChain(t.BaseType).Prepend(t); Dictionary result = new Dictionary(); foreach (var t in GetTypeChain(type)) { var properties = t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); foreach (var prop in properties) { if (!result.ContainsKey(prop.Name)) result.Add(prop.Name, prop); } } return result; } } Он не проверяет доступность, не заглядывает в private/protected, не проверяет совместимость типов. Если свойства с одинаковыми именами не могут быть скопированы, произойдёт исключение при создании маппера. Если целевое свойство недоступно для записи, произойдёт исключение при создании маппера. Не имеющие пары свойства молча игнорируются. Если свойство базового класса перекрыто по имени свойством производного класса, базовое свойство игнорируется (а что ещё можно делать?). Статические свойства игнорируются (это кажется правильным). Свойства, являющиеся явными реализациями интерфейса, игнорируются (потому что я не придумал, что можно с ними делать). Пользоваться так: class From { public int A { get; set; } public string B { get; set; } } class To { public int A { get; set; } public string C { get; set; } } class Program { static void Main(string[] args) { Action map = MapperFactory.CreateMapper(); var from = new From() { A = 15, B = "hello world" }; var to = new To(); map(from, to); Console.WriteLine(to.A); // 15 } } Варианты, которые правильно работают и не падают: interface I { int X { get; set; } } class FromBase { public string A { get; set; } public int B { get; set; } } class From : FromBase, I { public new int A { get; set; } static public string C { get; set; } int I.X { get; set; } } class To { public int A { get; set; } public string C { get; set; } } Объяснение. Давайте для начала разберём функцию GetVisibleProperties. Нам нужно получить список всех свойств. Обычно это делается при помощи простого type.GetProperties(), но что делать в случае, когда у нас есть несколько свойств с одним именем? Такое может быть, если свойство из базового класса перекрыть при помощи new, как в последнем примере. Как получить только самое последнее свойство? Для этого давайте сделаем такой трюк: пройдём по цепочке базовых типов от нашего типа до object, и будет добавлять на каждом шаге только свойства, которые определяются в этом типе, и только те, имя которых ещё не встречалось у нас. Для этого нам нужно получить цепочку базовых типов. Это проще всего сделать рекурсивно: к цепочке для базового класса добавить в начало наш собственный тип. Получается просто: IEnumerable GetTypeChain(Type t) => t == null ? Enumerable.Empty() : GetTypeChain(t.BaseType).Prepend(t); Окей, дальше мы походим по этой цепочке, и для каждого типа получаем только открытые свойства (BindingFlags.Public), только нестатические (BindingFlags.Instance), и только определённые прямо в этом типе, а не унаследованные (BindingFlags.DeclaredOnly). Для каждого из них мы проверяем, есть ли уже свойство с таким именем, и добавляем новое свойство в результат только если свойства с таким же именем ещё не было. С GetVisibleProperties всё. Теперь, главная функция: CreateMapper. Мы передаём типы как обобщённые параметры, чтобы можно было возвращать типизированный делегат Action. Для начала, мы получаем свойства с помощью уже разобранной функции GetVisibleProperties. Далее, мы берём пересечение множества имён: это те имена свойств, которые есть обоих типах (commonProperties). Я ещё сортирую их по алфивиту, чтобы порядок был одинаковым. Мы идём через System.Linq.Expressions, которые позволяют конструировать выражения (типа Expression) в коде. Это стандартный приём для подобной кодогенерации, у нас много ответов на сайте, использующих эту технику. Итак, вспомогательная функция CreateCopyProperty. Она создаёт аналог выражения to.Prop1 = from.Prop1;. Мы предварительно определяем Expression-переменные fromVar и toVar, соответствующие «реальным» переменным типов P и Q с именами from и to. Expression.Property(toVar, targetProperties[name]) — это полный аналог to.Prop1, где свойство Prop1 определено через PropertyInfo (это необходимо для случая, когда у нас могут быть перекрытые имена). Expression.Assign создаёт, понятно, код для присвоения. Создание выражения может бросить исключение если, например, свойства несовместимых типов, или у целевого свойства нету сеттера. Можно было бы и не ловить это исключение, но тогда непонятно было бы, какое именно свойство «виновато», поэтому я добавил блок try/catch, и в тексте нового исключения упоминаю имя проблемного свойства. Окей, дальше. У нас есть набор имён свойств (commonProperties), при помощи Select из него создаётся набор Expression'ов. Этот набор нужно упаковать в одно при помощи Expression.Block, который создаёт аналог блока из фигурных скобок: { to.Prop1 = from.Prop1; to.Prop2 = from.Prop2; ... } Теперь из этого блока делаем лямбду: (from, to) => { to.Prop1 = from.Prop1; to.Prop2 = from.Prop2; ... } при помощи Expression.Lambda. Финальный трюк состоит в вызове Compile, который Expression компилирует в реальную функцию, которую мы и возвращаем пользователю.

среда, 12 декабря 2018 г.

Падает производительность при использования предиката для гибкой сортировки в LINQ запросе

Пытаюсь сделать LINQ запрос в котором можно менять поле (одно) по которому нужно производить сортировку. Использование переменной предиката позволяет это
public void GetMyData(string sortFieldName) { Func orderPredicate = null; switch (sortFieldName) { case "FIELD_STRING": orderPredicate = x => x.FIELD_STRING; case "FIELD_INT": orderPredicate = x => x.FIELD_INT; case "FIELD_DATE": orderPredicate = x => x.FIELD_DATE; default: orderPredicate = x => x.FIELD_DATE; }
var queryResult = db.MyEntity.OrderBy(orderPredicate);
//...some logic
}
Но резко (в десятки раз) падает производительность по сравнению с прямым заданием условия сортировки. Насколько понимаю, должно помочь Expression. Меняю тип переменной
Expression> orderPredicate = null;
Но в рантайме выдаёт исключение
Не удалось привести тип "System.DateTime" к типу "System.Object". LINQ to Entities поддерживает только приведение типов-примитивов моделей EDM или типов перечисления.
Как же быть? Ведь, если я буду использовать прямое задание условия
public void GetMyData(string sortFieldName) { var query = db.MyEntity; var orderedQuery = query.OrderBy(x => x.FIELD_DATE); switch (sortFieldName) { case "FIELD_STRING": orderedQuery = query.OrderBy(x => x.FIELD_STRING); break; case "FIELD_INT": orderedQuery = query.OrderBy(x => x.FIELD_INT); break; case "FIELD_DATE": orderedQuery = query.OrderBy(x => x.FIELD_DATE); break; }
//...some logic
}
, мне будет трудно усложнить запрос дополнительными инструкциями Where, Include и т.д.


Ответ

Проблема в том, что когда вы пишите вот так:
Expression> orderPredicate; orderPredicate = x => x.FIELD_DATE;
Компилятор делает вот так:
var _param = Expression.Parameter(typeof(MyEntity), "x"); orderPredicate = Expression.Lambda>( Expression.Convert( Expression.Property(_param, "FIELD_DATE"), typeof(object) ), _param );
Вот на этот самый Expression.Convert EF и ругается. Для того чтобы не было преобразования - возвращаемый тип делегата обязан совпадать с типом свойства, что в свою очередь означает что общую переменную orderPredicate вы использовать не можете.

Теперь как строить такие запросы. А строятся они очень просто - через накопление IQueryable
IQueryable q = db.MyEntity; switch (sortFieldName) { case "FIELD_STRING": q = q.OrderBy(x => x.FIELD_STRING); case "FIELD_INT": q = q.OrderBy(x => x.FIELD_INT); case "FIELD_DATE": q = q.OrderBy(x => x.FIELD_DATE); default: q = q.OrderBy(x => x.FIELD_DATE); }
Никакого усложнения тут нет, полученный запрос можно точно также дополнять другими условиями:
q = q.Where(x => x.Foo > 42);

PS поскольку у вас в параметре sortFieldName передается имя свойства, построение выражения можно еще немного упростить. Да, класс Queryable не дает нам вызвать OrderBy с неизвестным заранее именем свойства - но никто не мешает "раскрыть" этот метод и работать непосредственно с IQueryable
IQueryable q = db.MyEntity;
var entityParam = Expression.Parameter(typeof(MyEntity)); var propExpr = Expression.Property(entityParam, sortFieldName) q = q.Provider.CreateQuery( Expression.Call(typeof(Queryable), "OrderBy", new [] { entityParam.Type, propExpr.Type }, // Типы-параметры для метода Queryable.OrderBy<,> q.Expression, Expression.Quote(Expression.Lambda(propExpr, entityParam)) ) );
Также вместо того чтобы делать это вручную - можно использовать библиотеку System.Linq.Dynamic, скачав ее из nuget:
q = q.OrderBy(sortFieldName);

пятница, 9 ноября 2018 г.

Python. Чем на самом деле является print?

Почему функция/оператор/набор символов (или что оно на самом деле) print не может работать в выражениях?
Python 2.7.9 (default, Jun 29 2016, 13:08:31) [GCC 4.9.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> print('hahaha') hahaha >>> 0 or print('hahaha') File "", line 1 0 or print('hahaha') ^ SyntaxError: invalid syntax >>> def test(): print('hahaha') ... >>> 0 or test() hahaha >>>


Ответ

В Python 2.X print является ключевым словом (вроде return во многих языках), по этому после него выражение не обязательно должно быть заключено в скобки. И оно не является функцией и не может возвращать значения (даже None). По этому оно не может быть частью логического выражения:
0 or return("hahaha")
В Pyton 3.X это фнукция. По этому там требуется ее аргументы заключать в скобки, и она возвращает None (который в логическом контексте действует как False), по этому такое логическое выражение не вызовет ошибок.

пятница, 5 октября 2018 г.

C#, Как скопировать поля объекта в другой объект, создав для этого expression

Хочу написать функцию, которая будет копировать одинаковые свойства из одного объекта в другой, но при этом чтобы она работала быстро. Вот что есть:
static class Copy { private static readonly ConcurrentDictionary> PropertiesDictionaries = new ConcurrentDictionary>();
public static void Сopyfields(object source, object target) { var sourceType = source.GetType(); var targetType = target.GetType(); var sourceProperties = GetProperties(sourceType); var targetPropertyes = GetProperties(targetType);
foreach (var targetProperty in targetPropertyes) { if (sourceProperties.TryGetValue(targetProperty.Key, out var sourceProperty)) { targetProperty.Value.SetValue(target, sourceProperty.GetValue(source)); } } }
private static ConcurrentDictionary GetProperties(Type objType) { if (!PropertiesDictionaries.TryGetValue(objType, out var propertiesInfoDictionary)) { var infos = objType.GetProperties(); propertiesInfoDictionary = new ConcurrentDictionary(); foreach (var propertyInfo in infos) { propertiesInfoDictionary.GetOrAdd(propertyInfo.Name, propertyInfo); } PropertiesDictionaries.GetOrAdd(objType, propertiesInfoDictionary); } return propertiesInfoDictionary; } }
Как мне переписать эту функцию, чтобы создавать expression и использовать рефлексию только 1 раз для каждой комбинации типов?


Ответ

Вот вам простейший вариант:
class MapperFactory { public static Action CreateMapper() { var sourceType = typeof(P); var targetType = typeof(Q);
var sourceProperties = GetVisibleProperties(sourceType); var targetProperties = GetVisibleProperties(targetType);
var fromVar = Expression.Variable(sourceType, "from"); var toVar = Expression.Variable(targetType, "to");
Expression CreateCopyProperty(string name) { try { return Expression.Assign( Expression.Property(toVar, targetProperties[name]), Expression.Property(fromVar, sourceProperties[name])); } catch (Exception ex) { throw new ArgumentException($"Невозможно скопировать свойство {name}", ex); } }
var commonProperties = sourceProperties.Keys.Intersect(targetProperties.Keys) .OrderBy(n => n); var assignExpressions = commonProperties.Select(CreateCopyProperty); var assignment = Expression.Block(assignExpressions); var lambda = Expression.Lambda>(assignment, fromVar, toVar); return lambda.Compile(); }
static Dictionary GetVisibleProperties(Type type) { IEnumerable GetTypeChain(Type t) => t == null ? Enumerable.Empty() : GetTypeChain(t.BaseType).Prepend(t);
Dictionary result = new Dictionary(); foreach (var t in GetTypeChain(type)) { var properties = t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); foreach (var prop in properties) { if (!result.ContainsKey(prop.Name)) result.Add(prop.Name, prop); } } return result; } }
Он не проверяет доступность, не заглядывает в private/protected, не проверяет совместимость типов. Если свойства с одинаковыми именами не могут быть скопированы, произойдёт исключение при создании маппера. Если целевое свойство недоступно для записи, произойдёт исключение при создании маппера. Не имеющие пары свойства молча игнорируются. Если свойство базового класса перекрыто по имени свойством производного класса, базовое свойство игнорируется (а что ещё можно делать?). Статические свойства игнорируются (это кажется правильным). Свойства, являющиеся явными реализациями интерфейса, игнорируются (потому что я не придумал, что можно с ними делать).
Пользоваться так:
class From { public int A { get; set; } public string B { get; set; } }
class To { public int A { get; set; } public string C { get; set; } }
class Program { static void Main(string[] args) { Action map = MapperFactory.CreateMapper(); var from = new From() { A = 15, B = "hello world" }; var to = new To(); map(from, to); Console.WriteLine(to.A); // 15 } }

Варианты, которые правильно работают и не падают:
interface I { int X { get; set; } }
class FromBase { public string A { get; set; } public int B { get; set; } }
class From : FromBase, I { public new int A { get; set; } static public string C { get; set; } int I.X { get; set; } }
class To { public int A { get; set; } public string C { get; set; } }

Объяснение.
Давайте для начала разберём функцию GetVisibleProperties. Нам нужно получить список всех свойств. Обычно это делается при помощи простого type.GetProperties(), но что делать в случае, когда у нас есть несколько свойств с одним именем? Такое может быть, если свойство из базового класса перекрыть при помощи new, как в последнем примере. Как получить только самое последнее свойство?
Для этого давайте сделаем такой трюк: пройдём по цепочке базовых типов от нашего типа до object, и будет добавлять на каждом шаге только свойства, которые определяются в этом типе, и только те, имя которых ещё не встречалось у нас. Для этого нам нужно получить цепочку базовых типов. Это проще всего сделать рекурсивно: к цепочке для базового класса добавить в начало наш собственный тип. Получается просто:
IEnumerable GetTypeChain(Type t) => t == null ? Enumerable.Empty() : GetTypeChain(t.BaseType).Prepend(t);
Окей, дальше мы походим по этой цепочке, и для каждого типа получаем только открытые свойства (BindingFlags.Public), только нестатические (BindingFlags.Instance), и только определённые прямо в этом типе, а не унаследованные (BindingFlags.DeclaredOnly). Для каждого из них мы проверяем, есть ли уже свойство с таким именем, и добавляем новое свойство в результат только если свойства с таким же именем ещё не было.
С GetVisibleProperties всё. Теперь, главная функция: CreateMapper
Мы передаём типы как обобщённые параметры, чтобы можно было возвращать типизированный делегат Action. Для начала, мы получаем свойства с помощью уже разобранной функции GetVisibleProperties. Далее, мы берём пересечение множества имён: это те имена свойств, которые есть обоих типах (commonProperties). Я ещё сортирую их по алфивиту, чтобы порядок был одинаковым.
Мы идём через System.Linq.Expressions, которые позволяют конструировать выражения (типа Expression) в коде. Это стандартный приём для подобной кодогенерации, у нас много ответов на сайте, использующих эту технику.
Итак, вспомогательная функция CreateCopyProperty. Она создаёт аналог выражения to.Prop1 = from.Prop1;. Мы предварительно определяем Expression-переменные fromVar и toVar, соответствующие «реальным» переменным типов P и Q с именами from и to. Expression.Property(toVar, targetProperties[name]) — это полный аналог to.Prop1, где свойство Prop1 определено через PropertyInfo (это необходимо для случая, когда у нас могут быть перекрытые имена). Expression.Assign создаёт, понятно, код для присвоения. Создание выражения может бросить исключение если, например, свойства несовместимых типов, или у целевого свойства нету сеттера. Можно было бы и не ловить это исключение, но тогда непонятно было бы, какое именно свойство «виновато», поэтому я добавил блок try/catch, и в тексте нового исключения упоминаю имя проблемного свойства.
Окей, дальше. У нас есть набор имён свойств (commonProperties), при помощи Select из него создаётся набор Expression'ов. Этот набор нужно упаковать в одно при помощи Expression.Block, который создаёт аналог блока из фигурных скобок:
{ to.Prop1 = from.Prop1; to.Prop2 = from.Prop2; ... }
Теперь из этого блока делаем лямбду:
(from, to) => { to.Prop1 = from.Prop1; to.Prop2 = from.Prop2; ... }
при помощи Expression.Lambda
Финальный трюк состоит в вызове Compile, который Expression компилирует в реальную функцию, которую мы и возвращаем пользователю.