Есть бд имеющая кучу локализованных справочников вида
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 и т.д.?
Здравствуйте, BlackEric, Вы писали:
BE>Вопрос: BE>как мне в зависимости от локали выбранной в приложении подставлять соответствующее поле из бд: Reason = p.Reason_RU, FR, EN и т.д.?
По тупому делаете 3 разных варианта запроса (общую часть запроса выделить) и в зависимости от локали исполняете нужный вам запрос.
Еще можно все переводы вынести в отдельную таблицу, тогда локаль добавить в сам запрос.
Здравствуйте, 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 на вторую таблицу сам добавь — мне лень синтаксис вспоминать.
Всё, что нас не убивает, ещё горько об этом пожалеет.
Вопрос странный, так как сам по себе linq не имеет отношение к базе данных.
Но потелепатируем
Entity Framework — простыми способами никак, если вообще возможно
LinqToSql — никак
NHibernate — в глаза не видел
Linq2db — переопределить MappingSchema.
Здравствуйте, 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";
Здравствуйте, 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();
}
}
Здравствуйте, BlackEric, Вы писали:
BE>Поскольку структуру бд менять нельзя, а красивого решения без недостатков я так и не увидел, то сделал в лоб:
динамик нужен, чтобы медленнее работало? Просто как object же можно result объявить.
Дублирование запроса чтобы наверняка где-то накосячить?
Мне кажется, что как-то так немного получше будет:
BE>
Здравствуйте, 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();
Здравствуйте, BlackEric, Вы писали:
BE>Вопрос: BE>как мне в зависимости от локали выбранной в приложении подставлять соответствующее поле из бд: Reason = p.Reason_RU, FR, EN и т.д.?
На самом деле, самый простой вариант (и его вроде бы даже озвучивали) — менять выражение (Expression Tree) перед исполнением, то есть делать Linq запрос, а в конце вызывать что-то типа "Localize()" — extension method, который через наследника Expression Visitor обойдёт всё выражение и поправит все поля на поля нужного языка.
Главная проблема здесь — метод можно будет не везде использовать (например — нельзя в подзапросах), но с другой стороны — он автоматом сработает и для подзапросов.
Думаю, получилось сумбурно, но это — единственный способ реализовать нужный функционал и не тащить на клиент лишние данные.
Ну и вторым нужно добавить Not mapped свойство, которое будет возвращать значение локализованного свойства.
Если при компиляции и исполнении вашей программы не происходит ни одной ошибки — это ошибка компилятора :)))
похожую вещь смутно припоминаю, но только там ещё был сверху слой OData, так вот там можно свой сериализатор (https://github.com/OData/ODataSamples/tree/master/WebApi/v4/CustomODataFormatter) использовать, в этом сериализаторе можно подменять на лету значения, но чтобы всё склеилось, на уровне сущности добавлялась дополнительная колонка , например, "Reason" , клиент всегда обращался к ней, в сериализаторе же можно на лету определять текущую "культуру" и подменять значение в колонке "Reason" значением из , например, "Reason_En"
Здравствуйте, MozgC, Вы писали:
MC>Здравствуйте, BlackEric, Вы писали:
MC>>>Какой linq-to-database provider используется? BE>>Дефолтный для MS SQL
MC>Я имею в виду EF, linq2sql, linq2db, и т.д. Какой?
Здравствуйте, Danchik, Вы писали:
D>Здравствуйте, nikda, Вы писали:
D>[Skip]
D>Вот не надо так писать. Будет у него 10 локализаций и все будет тянуться с сервера. Зачем?
Согласен полностью, но тут проблема и в структуре БД. Вместо таблицы локализаций "id локали, id ресурса, локализованный контент ресурса" сделана column-based денормализация. И теперь добавить новую локаль без upgrade-скрипта — никак.
Здравствуйте, takTak, Вы писали:
T>я бы ещё только "культуру" не через "стринг" задавал, а определял бы сразу из Thread.CurrentCulture
Не-не-не. Вот у меня, скажем, винда английской локали, а хочется на лету переключаться "английский -> французский -> датский". Особенно актуально для жителей швейцарских кантонов и прочих регионов с множественными официальными языками. Поэтому локаль в приолжении — только явно через параметр (тем более что часть тредов может создаваться сторонними либами, и там может быть установлена NeutralCulture от греха подальше).