Добрый день!
При использовании XML-extensions класс SqlQuery<T> некорректно генерирует запросы. То имя таблицы, то имя колонки берет не из TypeExtensions, а из названия класса или поля/свойства. Вот пример:
// csc /r:BLToolkit.3.dll /d:EXAMPLE1 bug.cs
// csc /r:BLToolkit.3.dll bug.csusing System;
using System.IO;
using System.Text;
using BLToolkit.Data;
using BLToolkit.DataAccess;
using BLToolkit.Data.DataProvider;
using BLToolkit.Mapping;
using BLToolkit.Reflection.Extension;
public class Sample
{
public class SomeClass
{
public int SomeValue { get; set; }
}
static void Main()
{
// для провайдера MsSql баг проявляется немного иначе
DbManager.AddDataProvider(typeof(OdpDataProvider));
DbManager.AddConnectionString("Odp", "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=DBHost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE)));User Id=TestUser;Password=TestPassword;");
// Должно быть:
//
// SELECT
// SOME_VALUE
// FROM
// SOME_TABLE#if EXAMPLE1
// SELECT
// SOME_VALUE
// FROM
// SomeClassusing (var db = new DbManager())
{
db.MappingSchema.Extensions = TypeExtension.GetExtensions(Mappings);
var query = new SqlQuery<SomeClass>();
Console.WriteLine(query.GetSqlQueryInfo(db, "SelectAll").QueryText);
}
#else// SELECT
// SomeValue
// FROM
// SOME_TABLEusing (var db = new DbManager())
{
var query = new SqlQuery<SomeClass>();
query.Extensions = TypeExtension.GetExtensions(Mappings);
Console.WriteLine(query.GetSqlQueryInfo(db, "SelectAll").QueryText);
}
#endif
}
static Stream Mappings
{
get
{
return new MemoryStream(Encoding.UTF8.GetBytes(@"<?xml version='1.0' encoding='utf-8' ?>
<Types xmlns='urn:schemas-bltoolkit-net:typeext' xmlns:da='urn:schemas-bltoolkit-net:dataaccess'>
<Type Name='SomeClass' da:TableName='SOME_TABLE'>
<Member Name='SomeValue' MapField='SOME_VALUE' />
</Type>
</Types>"));
}
}
}
Правильного поведения можно добиться только если загрузить TypeExtensions одновременно в SqlQuery и в DbManager:
Это происходит из-за того, что в SqlQueryBase TypeExtensions иногда берутся из db.MappingSchema, а иногда из локальной схемы. Вот, например:
protected SqlQueryInfo CreateSelectAllSqlText(DbManager db, Type type)
{
ObjectMapper om = db.MappingSchema.GetObjectMapper(type);
StringBuilder sb = new StringBuilder();
SqlQueryInfo query = new SqlQueryInfo(om);
sb.Append("SELECT\n");
foreach (MemberMapper mm in GetFieldList(om))
sb.AppendFormat("\t{0},\n",
db.DataProvider.Convert(mm.Name, ConvertType.NameToQueryField));
sb.Remove(sb.Length - 2, 1);
sb.AppendFormat("FROM\n\t{0}",
db.DataProvider.Convert(GetTableName(type), ConvertType.NameToQueryTable));
query.QueryText = sb.ToString();
return query;
}
GetFieldList(om) берет названия свойств из db.MappingSchema.Extensions, а GetTableName(type) — из this.MappingSchema.Extensions. То же самое происходит во всех методах Create*SqlText.
Здравствуйте, Wight, Вы писали:
W>Это происходит из-за того, что в SqlQueryBase TypeExtensions иногда берутся из db.MappingSchema, а иногда из локальной схемы. Вот, например:
вообще, то все еще веселее. SqlQueryInfo — кешируются, по вот такому забавному ключу:
а поле Extensions — хитрое, если не задано локальное, то берется из маппинг схемы.
а MappingSchema — тоже очень хитрая, если она не задана явно, то берется из DbManager, а если и он не задан, то Map.DefaultSchema
ну в общем, с экстеншенами все очень хитро... нужно либо очень внимательно следить за той, какая сейчас MappingSchema либо грузить все в дефолтную...
а вообще, конечно, все это крайне непрозрачно
надо бы побороться как-нить... тока как правильно все это бороть я
Насколько я понимаю, это позволяет кешировать запросы, когда тип имеет несколько маппингов на разные таблицы.
Хотя тут было бы правильнее не имя таблицы подставлять, а какой-нибудь хеш используемого маппинга
ili>а MappingSchema — тоже очень хитрая, если она не задана явно, то берется из DbManager, а если и он не задан, то Map.DefaultSchema
Все правильно, нельзя сразу брать db.MappingSchema, надо сначала попробовать локальную схему.
А SqlQueryBase сразу лезет в DbManager. В данном случае:
protected SqlQueryInfo Create...SqlText(DbManager db, Type type)
{
// было: ObjectMapper om = db.MappingSchema.GetObjectMapper(type);
ObjectMapper om = MappingSchema.GetObjectMapper(type);
...
Здравствуйте, Wight, Вы писали:
W>Ага. Но зато ж как удобно
признаться честно, я не понимаю зачем DataAccessorBase обзавелся своими экстеншенами.
какая-то дислогичность... с одной стороны за весь мапинг отвечает MappingSchema... с другой есть DataAccess который перетягивает на себя кусок ее одеяла... я бы убрал возможность задавать экстеншены отдельно для датааксессоров — хочешь менять правила мапинга, меняй схему, а то какое-то броуновское движение получается...
ili>я бы убрал возможность задавать экстеншены отдельно для датааксессоров — ili>хочешь менять правила мапинга, меняй схему, а то какое-то броуновское движение получается...
Зато можно гибко управлять доступом к TypeExtensions. К примеру:
using (var db = new DbManager())
{
// общедоступные TypeExtensions
db.MappingSchema.Extensions = LoadPublicExtensions();
// UserCode использует схему маппинга из DbManager
CallUserCode(db);
// а здесь используются другие TypeExtensions, недоступные для UserCodevar query = new SqlQuery<SomeSystemClass>();
query.MappingSchema.Extensions = LoadPrivateExtensions();
...
// снова используется public-схема
СallAnotherUserCode(db);
}
Здравствуйте, Wight, Вы писали:
W>Зато можно гибко управлять доступом к TypeExtensions. К примеру:
вот тут-то мы и влетим (скорее всего) на непонятки с экстеншенами и закэшированными обджект маперами
обджект маперы — кэшируются в схеме при первом их получении (GetObjectMapper(Type)). так вот, экстеншены на них применяются так же при первом GetObjectMapper(Type). так что загрузка экстеншенов на уже пользованные маперы не повлияет (вот я не проверял, если чо ногами не бейте, но скорее всего так и есть).
вообще, в твоем примере, все экстеншены выставляются в Map.DefaultSchema (если мне не изменяет мой уставший мозг), что еще сильнее запутает всю ситуацию
ili>вот тут-то мы и влетим (скорее всего) на непонятки с экстеншенами и закэшированными обджект маперами
Значит, реализовано меньше, чем спроектировано
ili>обджект маперы — кэшируются в схеме при первом их получении (GetObjectMapper(Type)). ili>так вот, экстеншены на них применяются так же при первом GetObjectMapper(Type).
Погоди-ка, первое получение ObjectMapper-а было в той MappingSchema, которая в DbManager.
А обращения к SqlQuery.MappingSchema.GetObjectMapper() еще не было.
ili>так что загрузка экстеншенов на уже пользованные маперы не повлияет ili>(вот я не проверял, если чо ногами не бейте, но скорее всего так и есть).
Это если подменять Extensions в той схеме, которая уже использовалась.
ili>вообще, в твоем примере, все экстеншены выставляются в Map.DefaultSchema
Разве это правильно?
В таком случае локальная схема в DataAccessorBase действительно бесполезна.
Здравствуйте, Wight, Вы писали:
ili>>вообще, в твоем примере, все экстеншены выставляются в Map.DefaultSchema
W>Разве это правильно? W>В таком случае локальная схема в DataAccessorBase действительно бесполезна.
а все зависит от правил.
создание того же обджект маппера — дело накладное, оттого они и кэшируются.
в большинстве случаев плодить схемы смысла нет, правила не меняются. а там где меняются, можно и ручками поставить.
с др. стороны, если они меняются постоянно, то, как вариант можно нарисовать своего наследника от датапровайдера и кормить ДбМанагеру всегда новую схему.
сейчас же работа у ДбМанагера с маппинг схемой выглядит так:
private MappingSchema _mappingSchema = Map.DefaultSchema;
public MappingSchema MappingSchema
{
[DebuggerStepThrough]
get { return _mappingSchema; }
set { _mappingSchema = value ?? Map.DefaultSchema; }
}
в конструкторе же вызывается InitDbManager(this) от дата провайдера, который по умолчанию реализован так, а схема у него, опять таки, по умолчанию null:
public virtual void InitDbManager(DbManager dbManager)
{
MappingSchema schema = MappingSchema;
if (schema != null)
dbManager.MappingSchema = schema;
}
так, что все правила заточены под скорость работы
мне, если честно, пользовать экстеншены понадобилось 1 раз (пользовал именно для того, что бы менять правила мапинга при импорте и синхронизации данных)
Я немножко исправил сценарий, добавив отдельную MappingSchema для SqlQuery.
Но все равно не работает:
// csc Sample.cs /r:BLToolkit.3.dllusing System;
using System.IO;
using System.Text;
using BLToolkit.Data;
using BLToolkit.Data.DataProvider;
using BLToolkit.DataAccess;
using BLToolkit.Mapping;
using BLToolkit.Reflection;
using BLToolkit.Reflection.Extension;
public class SomeClass
{
public int SomeValue { get; set; }
}
static class Program
{
static void Main()
{
DbManager.AddDataProvider(typeof(OdpDataProvider));
DbManager.AddConnectionString("Odp", "...");
using (var db = new DbManager())
{
// общедоступные TypeExtensions
db.MappingSchema.Extensions = TypeExtension.GetExtensions(PublicMappings);
// UserCode использует схему из DbManager
CallUserCode(db);
// а здесь используются другие TypeExtensions, недоступные для UserCodevar query = new SqlQuery<SomeClass>
{
MappingSchema = new MappingSchema
{
Extensions = TypeExtension.GetExtensions(PrivateMappings)
}
};
// Должно быть:
// SELECT
// SOME_SYSTEM_VALUE
// FROM
// SOME_SYSTEM_TABLE
Console.WriteLine("-- private mappings:");
Console.WriteLine("{0}\n", query.GetSqlQueryInfo(db, "SelectAll").QueryText);
// снова используется public-схема
CallUserCode(db);
}
}
static void CallUserCode(DbManager db)
{
// Должно быть:
// SELECT
// SOME_VALUE
// FROM
// SOME_TABLE
Console.WriteLine("-- public mappings:");
var query = new SqlQuery<SomeClass>();
Console.WriteLine("{0}\n", query.GetSqlQueryInfo(db, "SelectAll").QueryText);
}
static Stream PublicMappings
{
get
{
return new MemoryStream(Encoding.UTF8.GetBytes(@"<?xml version='1.0' encoding='utf-8' ?>
<Types xmlns='urn:schemas-bltoolkit-net:typeext' xmlns:da='urn:schemas-bltoolkit-net:dataaccess'>
<Type Name='SomeClass' da:TableName='SOME_TABLE'>
<Member Name='SomeValue' MapField='SOME_VALUE' />
</Type>
</Types>"));
}
}
static Stream PrivateMappings
{
get
{
return new MemoryStream(Encoding.UTF8.GetBytes(@"<?xml version='1.0' encoding='utf-8' ?>
<Types xmlns='urn:schemas-bltoolkit-net:typeext' xmlns:da='urn:schemas-bltoolkit-net:dataaccess'>
<Type Name='SomeClass' da:TableName='SOME_SYSTEM_TABLE'>
<Member Name='SomeValue' MapField='SOME_SYSTEM_VALUE' />
</Type>
</Types>"));
}
}
}
Генерируется такой SQL:
-- public mappings:SELECT
SOME_VALUE
FROM
SomeClass
-- private mappings:SELECT
SOME_VALUE
FROM
SOME_SYSTEM_TABLE
-- public mappings:SELECT
SOME_VALUE
FROM
SomeClass
Все три запроса неправильные. Первый и третий — из-за бага, описанного в первом сообщении топика.
Второй — вероятно, из-за кеширования ObjectMapper-а после первого вызова GetSqlQueryInfo().
W>>Разве это правильно? W>>В таком случае локальная схема в DataAccessorBase действительно бесполезна. ili>а все зависит от правил. ili>создание того же обджект маппера — дело накладное, оттого они и кэшируются. ili>так, что все правила заточены под скорость работы
Не, тут никаких вопросов Я ж не спорю, что кешировать мапперы надо.
Просто, наверное, генерацию ключей для кеша нужно пересмотреть.
Со скоростью работы в обычной ситуации ничего не изменится, зато будет возможность
подменять TypeExtensions на лету, в той же транзакции, для некоторых запросов.
При использовании SqlQuery даже самый простой сценарий, когда все расширения
берутся из DbManager.MappingSchema.Extensions — не работает:
SELECT
SOME_VALUE
FROM
SomeClass
Этот конкретный косяк поправить несложно — в SqlQueryBase все вызовы db.MappingSchema заменить на this.MappingSchema.
Здравствуйте, Wight, Вы писали:
W>GetFieldList(om) берет названия свойств из db.MappingSchema.Extensions, а GetTableName(type) — из this.MappingSchema.Extensions. То же самое происходит во всех методах Create*SqlText.
посмотрел я на это дело повнимательней, и все же кажется мне что экстеншенам не место в DataAccessorBase:
1) экстеншены меняют правила маппинга, за которые отвечает MappingSchema, с какой целью вообще экстеншены пихались в DataAccessorBase — мне неясно.
2) MemberMapper-ы кэшируются, опять таки в MappingSchema и подгрузка экстеншенов ну никак не влияет на закэшированые маперы
3) все это плодит некоторую неоднозначность и путаницу. с др. стороны если упаковать все в маппинг схему, то все становится ясно и понятно.
т.е. я бы экстеншены из DataAccess убрал. но эт я... решать проджект овнерам.