diff --git a/Extensions/Xtensive.Orm.Localization.Tests/Model/DictionaryModel.cs b/Extensions/Xtensive.Orm.Localization.Tests/Model/DictionaryModel.cs new file mode 100644 index 0000000000..8788090c87 --- /dev/null +++ b/Extensions/Xtensive.Orm.Localization.Tests/Model/DictionaryModel.cs @@ -0,0 +1,160 @@ +using System; +using System.Globalization; +using Xtensive.Orm.Model; + +namespace Xtensive.Orm.Localization.Tests.Model +{ + [HierarchyRoot(InheritanceSchema = InheritanceSchema.ConcreteTable)] + public abstract class IdentifiedEntity : Entity + { + [Key, Field] + public int Id { get; set; } + + [Version, Field(Nullable = false)] + public int RecordVersion { get; set; } + + public IdentifiedEntity(Session session) : base(session) { } + } + + public abstract class AbstractDictionary : IdentifiedEntity + { + public const string TableNamePrefix = "Dict_"; + + [Field(Nullable = false, Length = 64)] + public string Identifier { get; set; } + [Field(Nullable = false)] + public bool Enabled { get; set; } + [Field(Nullable = false)] + public bool Selectable { get; set; } + [Field(Nullable = false)] + public bool Displayable { get; set; } + public abstract string Name { get; set; } + public abstract string Description { get; set; } + + public AbstractDictionary(Session session) : base(session) { } + } + + public class AbstractNonLocalizableDictionary : AbstractDictionary + { + [Field(Nullable = false, Length = 512)] + public override string Name { get; set; } + [Field(Length = 2048, LazyLoad = true)] + public override string Description { get; set; } + + public AbstractNonLocalizableDictionary(Session session) : base(session) { } + } + + public abstract class AbstractLocalizableDictionary : AbstractDictionary, ILocalizable + where T : AbstractLocalizableDictionary + where TT : AbstractDictionaryLocalization + { + public override string Name { get => Localizations.Current.Name; set => Localizations.Current.Name = value; } + public override string Description { get => Localizations.Current.Description; set => Localizations.Current.Description = value; } + + [Field] + public LocalizationSet Localizations { get; private set; } + + public AbstractLocalizableDictionary(Session session) : base(session) { } + } + + public abstract class AbstractDictionaryLocalization : Localization + where TT : AbstractDictionaryLocalization + where T : AbstractLocalizableDictionary + { + public const string TableNamePrefix = AbstractDictionary.TableNamePrefix; + public const string TableNameSuffix = "_Localization"; + + [Field(Nullable = false, Length = 512)] + public string Name { get; set; } + [Field(Length = 2048, LazyLoad = true)] + public string Description { get; set; } + + [Version, Field(Nullable = false)] + public int RecordVersion { get; set; } + + protected AbstractDictionaryLocalization(Session session, CultureInfo culture, T target) : base(session, culture, target) { } + } + + [TableMapping(TableNamePrefix + nameof(CommunicationPlatform))] + public class CommunicationPlatform : AbstractNonLocalizableDictionary + { + [Field(Length = 50)] + public string ProtocolPrefix { get; set; } + + public CommunicationPlatform(Session session) : base(session) { } + } + + [TableMapping(TableNamePrefix + nameof(BuiltinMessage))] + public class BuiltinMessage : AbstractLocalizableDictionary, ILocalizable + { + public BuiltinMessage(Session session) : base(session) { } + } + + [HierarchyRoot] + [TableMapping(TableNamePrefix + nameof(BuiltinMessage) + TableNameSuffix)] + public class BuiltinMessageLocalization : AbstractDictionaryLocalization + { + public BuiltinMessageLocalization(Session session, CultureInfo culture, BuiltinMessage target) : base(session, culture, target) { } + } + + public class Country : IdentifiedEntity, ILocalizable + { + [Field] + public int OrderValue { get; set; } + [Field] + public bool Enabled { get; set; } + + // Localizable field. Note that it is non-persistent + public string Name + { + get => Localizations.Current.Name; + set => Localizations.Current.Name = value; + } + + [Field] + public LocalizationSet Localizations { get; private set; } + + public Country(Session session) : base(session) { } + } + + [HierarchyRoot] + public class CountryLocalization : Localization + { + [Field(Length = 100)] + public string Name { get; set; } + public CountryLocalization(Session session, CultureInfo culture, Country target) : base(session, culture, target) { } + } + + [HierarchyRoot] + public class Color : Entity, ILocalizable + { + [Key, Field] + public int Id { get; set; } + + [Field] + public int OrderValue { get; set; } + [Field] + public bool Enabled { get; set; } + + // Localizable field. Note that it is non-persistent + public string Name + { + get => Localizations.Current.Name; + set => Localizations.Current.Name = value; + } + + [Field] + public LocalizationSet Localizations { get; private set; } + + public Color(Session session) : base(session) { } + } + + [HierarchyRoot] + public class ColorLocalization : Localization + { + [Field(Length = 100)] + public string Name { get; set; } + + public ColorLocalization(Session session, CultureInfo culture, Color target) : base(session, culture, target) { } + } +} diff --git a/Extensions/Xtensive.Orm.Localization.Tests/ProjectionToCustomTypesTests.cs b/Extensions/Xtensive.Orm.Localization.Tests/ProjectionToCustomTypesTests.cs new file mode 100644 index 0000000000..749bd9fe99 --- /dev/null +++ b/Extensions/Xtensive.Orm.Localization.Tests/ProjectionToCustomTypesTests.cs @@ -0,0 +1,148 @@ +using System.Linq; +using System.Threading; +using NUnit.Framework; +using Xtensive.Orm.Localization.Tests.Model; + +namespace Xtensive.Orm.Localization.Tests +{ + [TestFixture] + public class ProjectionToCustomTypesTests : AutoBuildTest + { + protected override void PopulateDatabase() + { + using (var session = Domain.OpenSession()) { + using (var ts = session.OpenTransaction()) { + + // populating database + + Country c1 = new Country(session) { + Enabled = true, + Name = "Magyarország", + OrderValue = 3 + }; + Country c2 = new Country(session) { + Enabled = true, + Name = "Anglia", + OrderValue = 6 + }; + Country c3 = new Country(session) { + Enabled = false, + Name = "Spain", + OrderValue = 2 + }; + using (new LocalizationScope(EnglishCulture)) { + c1.Name = "Hungary"; + c2.Name = "England"; + } + + CommunicationPlatform cp1 = new CommunicationPlatform(session) { + Identifier = "cp1", + Name = "cp1", + Description = "dcp1", + ProtocolPrefix = "abc", + Enabled = true + }; + CommunicationPlatform cp2 = new CommunicationPlatform(session) { + Identifier = "cp2", + Name = "cp2", + Description = "dcp2", + ProtocolPrefix = "def" + }; + + BuiltinMessage m1 = new BuiltinMessage(session) { + Identifier = "bm1", + Name = "bm1", + Description = "dbm1", + Enabled = true + }; + BuiltinMessage m2 = new BuiltinMessage(session) { + Identifier = "bm2", + Name = "bm2", + Description = "dbm2", + Enabled = true + }; + using (new LocalizationScope(EnglishCulture)) + m2.Name = "eng-bm2"; + using (new LocalizationScope(SpanishCulture)) + m2.Name = "de-bm2"; + + ts.Complete(); + } + } + } + + [Test] + public void NonLocalizableTest() + { + Thread.CurrentThread.CurrentCulture = EnglishCulture; + using (var session = Domain.OpenSession()) { + using (var ts = session.OpenTransaction()) { + var q = session.Query.All().OrderBy(e => e.Identifier).Select(e => new { e.Identifier, e.Name, e.Enabled, e.Description }); + var l = q.ToList(); + // assertions + var propertyInfos = l.First().GetType().GetProperties(); + Assert.AreEqual(propertyInfos.Length, 4); + Assert.AreEqual(propertyInfos[0].Name, nameof(CommunicationPlatform.Identifier)); + Assert.AreEqual(propertyInfos[1].Name, nameof(CommunicationPlatform.Name)); + Assert.AreEqual(propertyInfos[2].Name, nameof(CommunicationPlatform.Enabled)); + Assert.AreEqual(propertyInfos[3].Name, nameof(CommunicationPlatform.Description)); + + ts.Complete(); + } + } + } + + [Test] + public void SimpleClassHierarchyTest() + { + Thread.CurrentThread.CurrentCulture = EnglishCulture; + using (var session = Domain.OpenSession()) { + using (var ts = session.OpenTransaction()) { + var q = session.Query.All().OrderBy(e => e.OrderValue).Select(e => new { e.Name, e.Enabled }); + var l = q.ToList(); + // assertions + var propertyInfos = l.First().GetType().GetProperties(); + Assert.AreEqual(propertyInfos.Length, 2); + Assert.AreEqual(propertyInfos.First().Name, nameof(Country.Name)); + Assert.AreEqual(propertyInfos.Last().Name, nameof(Country.Enabled)); + + ts.Complete(); + } + } + } + + [Test] + public void ComplexClassHierarchyTest() + { + Thread.CurrentThread.CurrentCulture = EnglishCulture; + using (var session = Domain.OpenSession()) { + using (var ts = session.OpenTransaction()) { + var q = session.Query.All().OrderBy(e => e.Identifier).Select(e => new { e.Identifier, e.Name, e.Enabled, e.Description }); + var l = q.ToList(); + // assertions + Assert.AreEqual(2, l.Count); + + var propertyInfos = l.First().GetType().GetProperties(); + Assert.AreEqual(propertyInfos.Length, 4); + Assert.AreEqual(propertyInfos[0].Name, nameof(BuiltinMessage.Identifier)); + Assert.AreEqual(propertyInfos[1].Name, nameof(BuiltinMessage.Name)); + Assert.AreEqual(propertyInfos[2].Name, nameof(BuiltinMessage.Enabled)); + Assert.AreEqual(propertyInfos[3].Name, nameof(BuiltinMessage.Description)); + + var f = l.First(); + Assert.AreEqual(f.Identifier, "bm1"); + Assert.AreEqual(f.Name, "bm1"); + Assert.AreEqual(f.Description, "dbm1"); + Assert.AreEqual(f.Enabled, true); + + var s = l.Last(); + Assert.AreEqual(s.Identifier, "bm2"); + Assert.AreEqual(s.Name, "eng-bm2"); + Assert.AreEqual(s.Description, "dbm2"); + Assert.AreEqual(s.Enabled, true); + } + } + } + + } +} \ No newline at end of file diff --git a/Extensions/Xtensive.Orm.Localization/Internals/LocalizationExpressionVisitor.cs b/Extensions/Xtensive.Orm.Localization/Internals/LocalizationExpressionVisitor.cs index 3c52e71e76..704840ce7e 100644 --- a/Extensions/Xtensive.Orm.Localization/Internals/LocalizationExpressionVisitor.cs +++ b/Extensions/Xtensive.Orm.Localization/Internals/LocalizationExpressionVisitor.cs @@ -25,7 +25,7 @@ protected override Expression VisitMemberAccess(MemberExpression me) if (Map == null) return me; - var localizationInfo = Map.Get(me.Member.ReflectedType); + var localizationInfo = Map.Get(me.Expression?.Type ?? me.Member.ReflectedType); if (localizationInfo == null || !localizationInfo.LocalizationTypeInfo.Fields.Contains(me.Member.Name)) return base.VisitMemberAccess(me);