Здравствуйте, 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);
}
}
И в самом самом заключении, Object
sMapperManager — это немного не по-английски.