Re: [ANN] Emit Mapper
От: IT Россия linq2db.com
Дата: 03.01.10 05:07
Оценка: 59 (5)
Здравствуйте, mrTwister, Вы писали:

T>http://emitmapper.codeplex.com


Совсем не плохо.

Там где можно дженерик методы можно было бы переписать следующим образом:

static class MapperImpl<TFrom,TTo>
{
    public static ObjectsMapper<TFrom,TTo> Instance = new ObjectsMapper<TFrom, TTo>(
        new ObjectsMapperManager().GetMapperImpl(
            typeof(TFrom),
            typeof(TTo),
            DefaultMapConfig.Instance
        )
    );
}

public ObjectsMapper<TFrom, TTo> GetMapper<TFrom, TTo>()
{
    return MapperImpl<TFrom,TTo>.Instance;
}

Тогда время на инициализацию и получение мапперов можно свести практически в абсолютный ноль.

Сравнительный тест с BLT конечно же слегка наивен. В тесте BLT тестируется не столько маппинг, сколько время доступа к мапперу. Впрочем, обо всём по порядку.

T>Имхо, заставлять вешать атрибуты — это еще больший моветон. Это примерно как заставлять наследоваться от специального класса. Тем более, что ничто не мешает реализовать требуемую функциональность своими силами на основе Emit Mapper.


Этот подход ведёт в никуда. Пользователям нужна готовая функциональность, а не потенциальная возможность. Это я как собаковод говорю. "ничто не мешает реализовать" в 99.99% случаев означает, что никто ничего реализовывать никогда и не будет. Более того, разработчик инструмента должен держать своё мнение о моветонах при себе. Учить пользователей жизни — это не его работа. Нормальное объяснение отсутсвия какой-либо функциональности в инструменте это: лень, нет времени, технические проблемы, отсутствие выгоды и т.п. "еще больший моветон" можно обсуждать у нас в Философии и желательно вне контекста конкретного инструмента.

T>Как раз как маппер, БЛТ довольно слабый (ценность БЛТ не в маппере). Он не поддерживает даже вложенные объекты, не конфигурируется и т.д. По крайней мере не справился даже с такой простой задачкой:


Как раз как маппер, в реальных сценариях BLT даст фору кому угодно. "Даже вложенные объекты" в BLT не поддерживаются по-умолчанию. Кстати, они не поддерживаются по-умолчанию практически нигде, т.к. это вызывает больше проблем, чем бенефитов. Научить BLT справляться с поставленной задачкой можно, например, следующим образом:

[MapField("str2", "i.str2")]
public class Destination
{
    public class Int
    {
        public string str2;
    }

    public string str1;
    public Int i = new Int();
}

[MapField("str2", "i.str2")]
public class Source
{
    public class Int
    {
        public string str2 = "B1::Int::str2";
    }

    public string str1 = "B1::str1";
    public Int i = new Int();
}

T>Да и производительность у БЛТ маппера оставляет желать лучшего. На простеньком тесте (класс без вложеных объектов) БЛТ оказался в 250 раз медленнее. Вот код теста:

Думаю, любому взрослому и умному человеку, заинтересованному в конечном результате, обязательно захотелось бы найти объснению такому чудовищному отставанию в производительности и разобраться что же происходит на самом деле, а не отмазываться фразами вроде "Дак это проблемы BLT. Он другого интерфейса не предоставляет.". Тем более, что и интерфейс такой у BLT есть и у EM есть интерфейс, который поставил бы библиотеки в одинаковые условия. И тогда 250 раз мгновенно уменьшились бы на 125. Хотя проблема, конечно, не в этом.

Разница в производительности объясняется прежде всего тем, что в тесте EM тестируется только маппер, а в тесте BLT тестируется не столько маппер, сколько способ его получения. API для получения маппера как это происходит в сценарии в BLT попросту отсутствует. Не то что бы это было трудно сделать, но просто за 8 лет существования библиотеки это пока никому не было нужно. Сценариев где нужен маппинг одного объектв в другой не так много, высокопроизводительных сценариев ещё меньше, а когда они реально возникают, то написать код вручную, особенно сегодня при наличии инициализаторов в C# не представляет особой сложности.

В общем, BLT под такие сценарии никогда не оптимизировался. Другое дело вот такие сценарии:

public class A2
{
    public string str1;
    public string str2;
    public string str3;
    public string str4;
    public string str5;
    public string str6;
    public string str7;
    public string str8;
    public string str9;

    public int n1;
    public int n2;
    public int n3;
    public int n4;
    public int n5;
    public int n6;
    public int n7;
}

public class B2
{
    public string str1 = "str1";
    public string str2 = "str2";
    public string str3 = "str3";
    public string str4 = "str4";
    public string str5 = "str5";
    public string str6 = "str6";
    public string str7 = "str7";
    public string str8 = "str8";
    public string str9 = "str9";

    public int n1 = 1;
    public long n2 = 2;
    public short n3 = 3;
    public byte n4 = 4;
    public decimal n5 = 5;
    public float n6 = 6;
    public int n7 = 7;

}

static long BenchBLToolkit_List(int mappingsCount)
{
    var list = new List<B2>();

    for (var i = 0; i < mappingsCount; i++)
        list.Add(new B2());

    var sw = new Stopwatch();
    sw.Start();

    var dest = Map.ListToList<A2>(list);

    sw.Stop();
    return sw.ElapsedMilliseconds;
}

static long EmitMapper_List(int mappingsCount)
{
    var list = new List<B2>();

    for (var i = 0; i < mappingsCount; i++)
        list.Add(new B2());

    var sw = new Stopwatch();
    sw.Start();

    var mapper = ObjectsMapperManager.DefaultInstance.GetMapper<B2, A2>();
    var dest   = new List<A2>();

    foreach (var item in list)
        dest.Add(mapper.Map(item));

    sw.Stop();
    return sw.ElapsedMilliseconds;
}

Здесь мы уже имеем разницу не в 250 раз, а около двух, хотя и не в пользу BLT. Оставшаяся разница объясняется тем, что BLT делает больше проверок в рантайм. Например, если изменить приведённый выше код следующим образом, то EM упадёт:

public class B2
{
    ...
    public decimal? n5 = null;
    ....
}

Некоторые из этих проверок можно сократить только генерацией кода под каждый конкретный вариант использования, что практически не имеет смысла если не кешировать каким-то образом сам вариант использования или явно не напрягать пользователя, что бы он его как-то идентифицировал. В приведённых выше тестах EM именно этим и занимается. Это хорошо работает с объектами, но не работает с источниками данных, структура которых становится известной только в момент выполнения. К таким источникам относятся, например, базы данных. Кстати, давайте посмотрим что у нас с базами данных:

static long BLToolkit_DB(int mappingsCount)
{
    DbManager.AddConnectionString("data source=.;initial catalog=Northwind;integrated security=SSPI;");

    var sw = new Stopwatch();
    sw.Start();

    for (var i = 0; i < mappingsCount; i++)
    {
        using (var db = new DbManager())
        {
            var list = db
                .SetCommand("SELECT * FROM Customers")
                .ExecuteList<Customers>();
        }
    }

    sw.Stop();
    return sw.ElapsedMilliseconds;
}

static long EmitMapper_DB(int mappingsCount)
{
    var sw = new Stopwatch();
    sw.Start();

    for (var i = 0; i < mappingsCount; i++)
    {
        using(var con = new SqlConnection("data source=.;initial catalog=Northwind;integrated security=SSPI;"))
        using(var cmd = con.CreateCommand())
        {
            con.Open();

            cmd.Connection  = con;
            cmd.CommandType = System.Data.CommandType.Text;
            cmd.CommandText = "SELECT * FROM Customers";

            using (var reader = cmd.ExecuteReader())
            {
                var list  = reader.ToObjects<Customers>(null).ToList();
            }
        }
    }

    sw.Stop();
    return sw.ElapsedMilliseconds;
}

В этом примере EM уже начинает отставать. Скорее всего reader.ToObjects, взятый из примеров библиотеки не очень хорош. При этом он не очень хорош будучи написанным самим автором библиотеки, а что будет написано, если будет, пользователями, плохо знакомыми как с самой библиотекой, так и с базами данных?

В общем, библиотеку нужно допиливать под реалии. С синтетическими тестами она уже справляется хорошо, т.е. задел есть и надо признать задел неплохой.

Ну и в заключении, вот такие примеры в качестве сценарии использования библиотеки из документации лучше убрать:

public DTOCustomer GetCustomer(Guid customerId)
{
    using (var dc = new DataContext())
    {
        var customer = dc.Customers.Where(c => c.CustomerID == customerId).Single();
        return ObjectsMapperManager.DefaultInstance.GetMapper<Customer, DTOCustomer>().Map(customer);
    }
}

И в самом самом заключении, ObjectsMapperManager — это немного не по-английски.
Если нам не помогут, то мы тоже никого не пощадим.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.