Баг в SqlQuery<T>
От: Wight Россия http://zyan.com.de
Дата: 29.04.09 09:04
Оценка:
Добрый день!
При использовании XML-extensions класс SqlQuery<T> некорректно генерирует запросы. То имя таблицы, то имя колонки берет не из TypeExtensions, а из названия класса или поля/свойства. Вот пример:

// csc /r:BLToolkit.3.dll /d:EXAMPLE1 bug.cs
// csc /r:BLToolkit.3.dll bug.cs

using 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
        //        SomeClass

        using (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_TABLE

        using (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:

db.MappingSchema.Extensions = TypeExtension.GetExtensions(Mappings);
query.Extensions = db.MappingSchema.Extensions;


Это происходит из-за того, что в 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.
Re: Баг в SqlQuery<T>
От: ili Россия  
Дата: 29.04.09 09:25
Оценка:
Здравствуйте, Wight, Вы писали:

W>Это происходит из-за того, что в SqlQueryBase TypeExtensions иногда берутся из db.MappingSchema, а иногда из локальной схемы. Вот, например:


вообще, то все еще веселее. SqlQueryInfo — кешируются, по вот такому забавному ключу:
string       key   = type.FullName + "$" + actionName + "$" + db.DataProvider.UniqueName + "$" + GetTableName(type);


а поле Extensions — хитрое, если не задано локальное, то берется из маппинг схемы.
а MappingSchema — тоже очень хитрая, если она не задана явно, то берется из DbManager, а если и он не задан, то Map.DefaultSchema
ну в общем, с экстеншенами все очень хитро... нужно либо очень внимательно следить за той, какая сейчас MappingSchema либо грузить все в дефолтную...
а вообще, конечно, все это крайне непрозрачно
надо бы побороться как-нить... тока как правильно все это бороть я
Re[2]: Баг в SqlQuery<T>
От: Wight Россия http://zyan.com.de
Дата: 29.04.09 10:19
Оценка:
ili>вообще, то все еще веселее. SqlQueryInfo — кешируются, по вот такому забавному ключу:
ili>
ili>string key = type.FullName + "$" + actionName + "$" + db.DataProvider.UniqueName + "$" + GetTableName(type);
ili>


Насколько я понимаю, это позволяет кешировать запросы, когда тип имеет несколько маппингов на разные таблицы.
Хотя тут было бы правильнее не имя таблицы подставлять, а какой-нибудь хеш используемого маппинга

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);
    ...


ili>а вообще, конечно, все это крайне непрозрачно


Ага. Но зато ж как удобно
Re[3]: Баг в SqlQuery<T>
От: ili Россия  
Дата: 29.04.09 10:55
Оценка:
Здравствуйте, Wight, Вы писали:

W>Ага. Но зато ж как удобно


признаться честно, я не понимаю зачем DataAccessorBase обзавелся своими экстеншенами.
какая-то дислогичность... с одной стороны за весь мапинг отвечает MappingSchema... с другой есть DataAccess который перетягивает на себя кусок ее одеяла... я бы убрал возможность задавать экстеншены отдельно для датааксессоров — хочешь менять правила мапинга, меняй схему, а то какое-то броуновское движение получается...
Re[4]: Баг в SqlQuery<T>
От: Wight Россия http://zyan.com.de
Дата: 29.04.09 11:36
Оценка:
ili>я бы убрал возможность задавать экстеншены отдельно для датааксессоров —
ili>хочешь менять правила мапинга, меняй схему, а то какое-то броуновское движение получается...

Зато можно гибко управлять доступом к TypeExtensions. К примеру:

using (var db = new DbManager())
{
    // общедоступные TypeExtensions
    db.MappingSchema.Extensions = LoadPublicExtensions();

    // UserCode использует схему маппинга из DbManager
    CallUserCode(db);

    // а здесь используются другие TypeExtensions, недоступные для UserCode
    var query = new SqlQuery<SomeSystemClass>();
    query.MappingSchema.Extensions = LoadPrivateExtensions();
    ...

    // снова используется public-схема
    СallAnotherUserCode(db);
}
Re[5]: Баг в SqlQuery<T>
От: ili Россия  
Дата: 29.04.09 12:50
Оценка:
Здравствуйте, Wight, Вы писали:

W>Зато можно гибко управлять доступом к TypeExtensions. К примеру:


вот тут-то мы и влетим (скорее всего) на непонятки с экстеншенами и закэшированными обджект маперами
обджект маперы — кэшируются в схеме при первом их получении (GetObjectMapper(Type)). так вот, экстеншены на них применяются так же при первом GetObjectMapper(Type). так что загрузка экстеншенов на уже пользованные маперы не повлияет (вот я не проверял, если чо ногами не бейте, но скорее всего так и есть).

вообще, в твоем примере, все экстеншены выставляются в Map.DefaultSchema (если мне не изменяет мой уставший мозг), что еще сильнее запутает всю ситуацию
Re[6]: Баг в SqlQuery<T>
От: Wight Россия http://zyan.com.de
Дата: 29.04.09 13:35
Оценка:
ili>вот тут-то мы и влетим (скорее всего) на непонятки с экстеншенами и закэшированными обджект маперами

Значит, реализовано меньше, чем спроектировано

ili>обджект маперы — кэшируются в схеме при первом их получении (GetObjectMapper(Type)).

ili>так вот, экстеншены на них применяются так же при первом GetObjectMapper(Type).

Погоди-ка, первое получение ObjectMapper-а было в той MappingSchema, которая в DbManager.
А обращения к SqlQuery.MappingSchema.GetObjectMapper() еще не было.

ili>так что загрузка экстеншенов на уже пользованные маперы не повлияет

ili>(вот я не проверял, если чо ногами не бейте, но скорее всего так и есть).

Это если подменять Extensions в той схеме, которая уже использовалась.

ili>вообще, в твоем примере, все экстеншены выставляются в Map.DefaultSchema


Разве это правильно?
В таком случае локальная схема в DataAccessorBase действительно бесполезна.
Re[7]: Баг в SqlQuery<T>
От: ili Россия  
Дата: 29.04.09 14:08
Оценка:
Здравствуйте, 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 раз (пользовал именно для того, что бы менять правила мапинга при импорте и синхронизации данных)
Re[6]: Баг в SqlQuery<T>
От: Wight Россия http://zyan.com.de
Дата: 29.04.09 14:15
Оценка:
Я немножко исправил сценарий, добавив отдельную MappingSchema для SqlQuery.
Но все равно не работает:

// csc Sample.cs /r:BLToolkit.3.dll

using 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, недоступные для UserCode
            var 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().
Re[8]: Баг в SqlQuery<T>
От: Wight Россия http://zyan.com.de
Дата: 29.04.09 14:33
Оценка:
W>>Разве это правильно?
W>>В таком случае локальная схема в DataAccessorBase действительно бесполезна.
ili>а все зависит от правил.
ili>создание того же обджект маппера — дело накладное, оттого они и кэшируются.
ili>так, что все правила заточены под скорость работы

Не, тут никаких вопросов Я ж не спорю, что кешировать мапперы надо.
Просто, наверное, генерацию ключей для кеша нужно пересмотреть.
Со скоростью работы в обычной ситуации ничего не изменится, зато будет возможность
подменять TypeExtensions на лету, в той же транзакции, для некоторых запросов.

При использовании SqlQuery даже самый простой сценарий, когда все расширения
берутся из DbManager.MappingSchema.Extensions — не работает:

SELECT
        SOME_VALUE
FROM
        SomeClass


Этот конкретный косяк поправить несложно — в SqlQueryBase все вызовы db.MappingSchema заменить на this.MappingSchema.
Re: Баг в SqlQuery<T>
От: ili Россия  
Дата: 06.05.09 04:36
Оценка:
Здравствуйте, Wight, Вы писали:

W>GetFieldList(om) берет названия свойств из db.MappingSchema.Extensions, а GetTableName(type) — из this.MappingSchema.Extensions. То же самое происходит во всех методах Create*SqlText.


посмотрел я на это дело повнимательней, и все же кажется мне что экстеншенам не место в DataAccessorBase:
1) экстеншены меняют правила маппинга, за которые отвечает MappingSchema, с какой целью вообще экстеншены пихались в DataAccessorBase — мне неясно.
2) MemberMapper-ы кэшируются, опять таки в MappingSchema и подгрузка экстеншенов ну никак не влияет на закэшированые маперы
3) все это плодит некоторую неоднозначность и путаницу. с др. стороны если упаковать все в маппинг схему, то все становится ясно и понятно.

т.е. я бы экстеншены из DataAccess убрал. но эт я... решать проджект овнерам.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.