Локализация и linq
От: BlackEric http://black-eric.lj.ru
Дата: 01.02.18 21:36
Оценка:
Есть бд имеющая кучу локализованных справочников вида

create table Visit_Reason
(
ID int identity(1,1) not null primary key,
Reason_RU nvarchar(256) not null,
Reason_UA nvarchar(256) not null,
Reason_EN nvarchar(256) not null,
NeedComment bit default 0,
IsDeleted bit default 0,
CreationDate datetime default getdate()
)


Данные из бд биндятся к различным WInForms контролам через linq:

private void BindingVisitReasonCombo()
        {
            var result = (from p in db.Visit_Reason
                          where p.IsDeleted == false
                          select new 
                          {
                              p.ID,
                              Reason = p.Reason_RU
                          }).ToList();

            cBVisitReason.DataSource = result;

            cBVisitReason.ValueMember = "ID";
            cBVisitReason.DisplayMember = "Reason";

            cBVisitReason.Refresh();
        }


Вопрос:
как мне в зависимости от локали выбранной в приложении подставлять соответствующее поле из бд: Reason = p.Reason_RU, FR, EN и т.д.?
https://github.com/BlackEric001
Re: Локализация и linq
От: Shmj Ниоткуда  
Дата: 02.02.18 00:08
Оценка:
Здравствуйте, BlackEric, Вы писали:

BE>Вопрос:

BE>как мне в зависимости от локали выбранной в приложении подставлять соответствующее поле из бд: Reason = p.Reason_RU, FR, EN и т.д.?

По тупому делаете 3 разных варианта запроса (общую часть запроса выделить) и в зависимости от локали исполняете нужный вам запрос.

Еще можно все переводы вынести в отдельную таблицу, тогда локаль добавить в сам запрос.
Re: Локализация и linq
От: Ромашка Украина  
Дата: 02.02.18 01:02
Оценка:
Здравствуйте, BlackEric, Вы писали:
BE>Вопрос:
BE>как мне в зависимости от локали выбранной в приложении подставлять соответствующее поле из бд: Reason = p.Reason_RU, FR, EN и т.д.?

Для того, чтобы такая схема работала, нужно выносить названия в отдельную таблицу. Вроде такого:
create table Visit_Reason
(
    ID int identity(1,1) not null primary key,
    NeedComment bit default 0,
    IsDeleted bit default 0,
    CreationDate datetime default getdate()
)
create table Visit_Reason_Names
(
    ID int not null,
    Locale char(5) not null,
    Name varchar(256) not null
)


private void BindingVisitReasonCombo()
{
    var result = (from p in db.Visit_Reason
        join l in Visit_Reason_Names on p.ID equals n.ID
        where p.IsDeleted == false && l.Locale == CultureInfo.CurrentCulture.Name
        select new 
        {
            p.ID,
            Reason = l.Name
        }).ToList();

    cBVisitReason.DataSource = result;

    cBVisitReason.ValueMember = "ID";
    cBVisitReason.DisplayMember = "Reason";

    cBVisitReason.Refresh();
}


PK на вторую таблицу сам добавь — мне лень синтаксис вспоминать.


Всё, что нас не убивает, ещё горько об этом пожалеет.
Re[2]: Локализация и linq
От: vorona  
Дата: 02.02.18 04:07
Оценка:
Здравствуйте, BlackEric, Вы писали:

Сделать extension method который вызывает ExpressionVisitor и изменяет MemberExpression Reason_RU на нужный
Отредактировано 02.02.2018 10:27 vorona . Предыдущая версия .
Re: Локализация и linq
От: Danchik Украина  
Дата: 02.02.18 04:15
Оценка:
Здравствуйте, BlackEric, Вы писали:

[skip]

Вопрос странный, так как сам по себе linq не имеет отношение к базе данных.
Но потелепатируем
Re: Локализация и linq
От: nikda  
Дата: 02.02.18 06:08
Оценка: +2
Здравствуйте, BlackEric, Вы писали:

BE>Вопрос:

BE>как мне в зависимости от локали выбранной в приложении подставлять соответствующее поле из бд: Reason = p.Reason_RU, FR, EN и т.д.?


class ReasonItem
{
    public static bool isRu = true;
    ID
    ReasonRu
    ReasonEn      

    DisplayReason
    {
        get{
            return isRu ? ReasonRu : ReasonEn // Примерно так
        }
    }
}

var result = (from p in db.Visit_Reason
                          where p.IsDeleted == false
                          select new ReasonItem
                          {
                              p.ID,
                              ReasonRu = p.Reason_RU,
                              ReasonEn = p.Reason_En,
                          }).ToList();

cBVisitReason.ValueMember = "ID";
cBVisitReason.DisplayMember = "DisplayReason";
Re[2]: Локализация и linq
От: Danchik Украина  
Дата: 02.02.18 14:47
Оценка: +1
Здравствуйте, nikda, Вы писали:

[Skip]

Вот не надо так писать. Будет у него 10 локализаций и все будет тянуться с сервера. Зачем?
Re: Локализация и linq
От: karbofos42 Россия  
Дата: 02.02.18 15:00
Оценка: +1
Здравствуйте, BlackEric, Вы писали:

BE>Вопрос:

BE>как мне в зависимости от локали выбранной в приложении подставлять соответствующее поле из бд: Reason = p.Reason_RU, FR, EN и т.д.?

Да хоть такую наркоманию можно сделать:
    public class Class1
    {
        public int Id { get; set; }
        public string Name_Ru { get; set; }
        public string Name_En { get; set; }
        public string Name_De { get; set; }
    }

    public enum Lang
    {
        Ru,
        En,
        De
    }

    public static class ExtClass
    {
        public static TValue LocalizedValue<TObj, TValue>(this TObj obj, Expression<Func<TObj, TValue>> property, Lang lang)
        {
            if (property.Body is MemberExpression member)
            {
                if (member.Member.MemberType == System.Reflection.MemberTypes.Property)
                {
                    var name = member.Member.Name;
                    var locName = Regex.Replace(name, @"_.+$", $"_{lang.ToString()}");
                    return (TValue)typeof(TObj).GetProperty(locName).GetValue(obj);
                }
            }
            throw new Exception();
        }
    }

и потом как-то так использовать:
            Class1 c1 = new Class1 { Name_De = "Deutsch", Name_En = "English", Name_Ru = "Русский" };
            Console.WriteLine($"Английский: {c1.LocalizedValue(c => c.Name_Ru, Lang.En)}");
            Console.WriteLine($"Немецкий: {c1.LocalizedValue(c => c.Name_En, Lang.De)}");
            Console.WriteLine($"Русский: {c1.LocalizedValue(c => c.Name_De, Lang.Ru)}");

или просто для всего делать ViewModel'и, а не анонимные типы использовать. И соответственно настраивать маппинг локализованных значений.
Re: Локализация и linq
От: BlackEric http://black-eric.lj.ru
Дата: 02.02.18 20:50
Оценка:
Поскольку структуру бд менять нельзя, а красивого решения без недостатков я так и не увидел, то сделал в лоб:

private void BindingVisitReasonCombo()
        {
            dynamic result;
            switch (cultureInfo.Name)
            {
                case "uk-UA":
                    result = (from p in db.Visit_Reason
                                  where p.IsDeleted == false
                                  select new
                                  {
                                      p.ID,
                                      Reason = p.Reason_UA
                                  }).ToList();
                    break;
                case "en-US":
                    result = (from p in db.Visit_Reason
                                  where p.IsDeleted == false
                                  select new
                                  {
                                      p.ID,
                                      Reason = p.Reason_EN
                                  }).ToList();
                    break;
                default:
                    result = (from p in db.Visit_Reason
                                  where p.IsDeleted == false
                                  select new
                                  {
                                      p.ID,
                                      Reason = p.Reason_RU
                                  }).ToList();
                    break;
            }

            cBVisitReason.DataSource = result;

            cBVisitReason.ValueMember = "ID";
            cBVisitReason.DisplayMember = "Reason";

            cBVisitReason.Refresh();
        }
https://github.com/BlackEric001
Re: Локализация и linq
От: MozgC США http://nightcoder.livejournal.com
Дата: 02.02.18 21:24
Оценка:
Какой linq-to-database provider используется?
Re[2]: Локализация и linq
От: karbofos42 Россия  
Дата: 03.02.18 16:59
Оценка: -1
Здравствуйте, BlackEric, Вы писали:

BE>Поскольку структуру бд менять нельзя, а красивого решения без недостатков я так и не увидел, то сделал в лоб:


динамик нужен, чтобы медленнее работало? Просто как object же можно result объявить.
Дублирование запроса чтобы наверняка где-то накосячить?
Мне кажется, что как-то так немного получше будет:

BE>
BE>private void BindingVisitReasonCombo()
BE>        {
BE>            Func<Visit_Reason,string> locReason;
BE>            switch (cultureInfo.Name)
BE>            {
BE>                case "uk-UA":
BE>                    locReason = (p) => p.Reason_UA;
BE>                    break;
BE>                case "en-US":
BE>                    locReason = (p) => p.Reason_EN;
BE>                    break;
BE>                default:
BE>                    locReason = (p) => p.Reason_RU
BE>                    break;
BE>            }

BE>            cBVisitReason.DataSource = (from p in db.Visit_Reason
BE>                                  where p.IsDeleted == false
BE>                                  select new
BE>                                  {
BE>                                      p.ID,
BE>                                      Reason = locReason(p)
BE>                                  }).ToList();

BE>            cBVisitReason.ValueMember = "ID";
BE>            cBVisitReason.DisplayMember = "Reason";

BE>            cBVisitReason.Refresh();
BE>        }
BE>
Re[3]: Локализация и linq
От: sergeya Ниоткуда http://blogtani.ru
Дата: 03.02.18 18:20
Оценка: 74 (3)
Здравствуйте, karbofos42, Вы писали:

K>Мне кажется, что как-то так немного получше будет:


BE>>
BE>>            Func<Visit_Reason,string> locReason;
BE>>            
BE>>            ...
BE>>            
BE>>            cBVisitReason.DataSource = (from p in db.Visit_Reason
BE>>                                  where p.IsDeleted == false
BE>>                                  select new
BE>>                                  {
BE>>                                      p.ID,
BE>>                                      Reason = locReason(p)
BE>>                                  }).ToList();
BE>>


В этом случае из БД будут загружены все поля таблицы Visit_Reason.
Т.к. linq провайдеру не известно, какие поля Visit_Reason могут понадобиться внути функции locReason.

Если заменить функцию на выражение, то linq провайдер сможет распарсить его и запросить из БД только нужно поле.

По умолчанию динамическая подстановка выражения в linq невозможна, но существуют сторонние расширения.
Например, с помощью расширения AsExpandable из библиотеки LinqKit (http://www.albahari.com/nutshell/linqkit.aspx) можно сделать так:

BE>>
BE>>            Expr<Func<Visit_Reason,string>> locReason;
BE>>            
BE>>            ...
BE>>            
BE>>            cBVisitReason.DataSource = (from p in db.Visit_Reason.AsExpandable()
BE>>                                  where p.IsDeleted == false
BE>>                                  select new
BE>>                                  {
BE>>                                      p.ID,
BE>>                                      Reason = locReason.Invoke(p)
BE>>                                  }).ToList();
BE>>


Вызов AsExpandable() создает для IQueryable объекта враппер ExpandableQuery. Это враппер выполняет дополнительную трансформацию linq запросов, подставляя вместо внешних вызовов тело исходного выражения.

Если унаследовать все локализуемые таблицы от интерфейса (пусть будет ILocalizedDictionary), то можно будет написать обобщенное выражение и использовать его для всех справочников:

interface ILocalizedDectionary 
{ 
    public string Reason_UA {get;set}
    public string Reason_RU {get;set}
}

class Visit_Reason : ILocalizedDictionary
{}

class Leave_Reason : ILocalizedDictionary
{}

Expr<Func<ILocalizedDictionary,string>> locReason;

var visitReasons = (from p in db.Visit_Reason 
                                  select new
                                  {
                                      Reason = locReason(p)
                                  }).ToList();

var leaveReasons = (from p in db.Leave_Reason 
                                  select new
                                  {
                                      Reason = locReason(p)
                                  }).ToList();
Мобильная версия сайта RSDN — http://rsdn.org/forum/rsdn/6938747
Автор: sergeya
Дата: 19.10.17
Отредактировано 04.02.2018 0:42 sergeya (исправил опечатку) . Предыдущая версия .
Re: Локализация и linq
От: Hacker_Delphi Россия  
Дата: 04.02.18 11:34
Оценка:
Здравствуйте, BlackEric, Вы писали:

BE>Вопрос:

BE>как мне в зависимости от локали выбранной в приложении подставлять соответствующее поле из бд: Reason = p.Reason_RU, FR, EN и т.д.?

На самом деле, самый простой вариант (и его вроде бы даже озвучивали) — менять выражение (Expression Tree) перед исполнением, то есть делать Linq запрос, а в конце вызывать что-то типа "Localize()" — extension method, который через наследника Expression Visitor обойдёт всё выражение и поправит все поля на поля нужного языка.
Главная проблема здесь — метод можно будет не везде использовать (например — нельзя в подзапросах), но с другой стороны — он автоматом сработает и для подзапросов.
Думаю, получилось сумбурно, но это — единственный способ реализовать нужный функционал и не тащить на клиент лишние данные.
Ну и вторым нужно добавить Not mapped свойство, которое будет возвращать значение локализованного свойства.
Если при компиляции и исполнении вашей программы не происходит ни одной ошибки — это ошибка компилятора :)))
Re[2]: Локализация и linq
От: BlackEric http://black-eric.lj.ru
Дата: 04.02.18 15:16
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>Какой linq-to-database provider используется?


Дефолтный для MS SQL
https://github.com/BlackEric001
Re: Локализация и linq
От: takTak  
Дата: 04.02.18 18:23
Оценка:
мои "две копейки":

похожую вещь смутно припоминаю, но только там ещё был сверху слой OData, так вот там можно свой сериализатор (https://github.com/OData/ODataSamples/tree/master/WebApi/v4/CustomODataFormatter) использовать, в этом сериализаторе можно подменять на лету значения, но чтобы всё склеилось, на уровне сущности добавлялась дополнительная колонка , например, "Reason" , клиент всегда обращался к ней, в сериализаторе же можно на лету определять текущую "культуру" и подменять значение в колонке "Reason" значением из , например, "Reason_En"
Re: Локализация и linq
От: takTak  
Дата: 04.02.18 18:39
Оценка:
вспомнилось ещё... в EF есть понятие прерывания , можешь погуглить в ту сторону ("interceptor + localization + EF"),

я поискал на шару: мне выдали вот такое решение: https://github.com/mehalick/OrangeJetpack.Localization

использование вроде ничего выглядит : https://andy.mehalick.com/2013/09/07/localizing-entity-framework-poco-properties-with-json-part-1/

// localizes only root objects (DEFAULT)
var planets = _db.Planets.Localize<Planet>("en", LocalizationDepth.Shallow);

я бы ещё только "культуру" не через "стринг" задавал, а определял бы сразу из Thread.CurrentCulture, а , в остальном, вполне удобоваримо выглядит
Re[3]: Локализация и linq
От: MozgC США http://nightcoder.livejournal.com
Дата: 04.02.18 23:27
Оценка:
Здравствуйте, BlackEric, Вы писали:

MC>>Какой linq-to-database provider используется?

BE>Дефолтный для MS SQL

Я имею в виду EF, linq2sql, linq2db, и т.д. Какой?
Re[4]: Локализация и linq
От: BlackEric http://black-eric.lj.ru
Дата: 05.02.18 09:05
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>Здравствуйте, BlackEric, Вы писали:


MC>>>Какой linq-to-database provider используется?

BE>>Дефолтный для MS SQL

MC>Я имею в виду EF, linq2sql, linq2db, и т.д. Какой?


EF Code first
https://github.com/BlackEric001
Re[3]: Локализация и linq
От: Mr.Delphist  
Дата: 05.02.18 10:44
Оценка: +1
Здравствуйте, Danchik, Вы писали:

D>Здравствуйте, nikda, Вы писали:


D>[Skip]


D>Вот не надо так писать. Будет у него 10 локализаций и все будет тянуться с сервера. Зачем?


Согласен полностью, но тут проблема и в структуре БД. Вместо таблицы локализаций "id локали, id ресурса, локализованный контент ресурса" сделана column-based денормализация. И теперь добавить новую локаль без upgrade-скрипта — никак.
Re[2]: Локализация и linq
От: Mr.Delphist  
Дата: 05.02.18 12:22
Оценка:
Здравствуйте, takTak, Вы писали:

T>я бы ещё только "культуру" не через "стринг" задавал, а определял бы сразу из Thread.CurrentCulture


Не-не-не. Вот у меня, скажем, винда английской локали, а хочется на лету переключаться "английский -> французский -> датский". Особенно актуально для жителей швейцарских кантонов и прочих регионов с множественными официальными языками. Поэтому локаль в приолжении — только явно через параметр (тем более что часть тредов может создаваться сторонними либами, и там может быть установлена NeutralCulture от греха подальше).
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.