From b4a74fda99073b47fd82ce65fb295fe973c5151e Mon Sep 17 00:00:00 2001 From: James Date: Sat, 24 Jan 2026 21:05:41 +0100 Subject: [PATCH] removed attribute generator insted --- Readme.md | 1 - .../MappingIntegrationTests.cs | 2 +- .../TenJames.CompMap.IntegrationTests.csproj | 2 +- .../TestDtos.cs | 2 +- .../MapperGeneratorTests.cs | 76 +----- .../TenJames.CompMap/AttributeDefinition.cs | 6 +- .../TenJames.CompMap/AttributeGenerator.cs | 238 ------------------ .../Attributes/AutoPropertyChainAttribute.cs | 12 + .../Attributes/MapFromAttribute.cs | 13 + .../Attributes/MapToAttribute.cs | 13 + TenJames.CompMap/TenJames.CompMap/Consts.cs | 8 +- .../TenJames.CompMap/InternalsVisibleTo.cs | 3 + .../TenJames.CompMap/{ => Mapper}/Mapper.cs | 2 +- .../TenJames.CompMap/MapperGenerator.cs | 2 +- .../TenJames.CompMap/MappingOptions.cs | 2 +- .../TenJames.CompMap/SourceBuilder.cs | 2 +- example/Example.Console/Program.cs | 4 +- example/Example.DTOS/Example.DTOS.csproj | 2 +- example/Example.DTOS/UserDto.cs | 2 +- 19 files changed, 59 insertions(+), 333 deletions(-) delete mode 100644 TenJames.CompMap/TenJames.CompMap/AttributeGenerator.cs create mode 100644 TenJames.CompMap/TenJames.CompMap/Attributes/AutoPropertyChainAttribute.cs create mode 100644 TenJames.CompMap/TenJames.CompMap/Attributes/MapFromAttribute.cs create mode 100644 TenJames.CompMap/TenJames.CompMap/Attributes/MapToAttribute.cs create mode 100644 TenJames.CompMap/TenJames.CompMap/InternalsVisibleTo.cs rename TenJames.CompMap/TenJames.CompMap/{ => Mapper}/Mapper.cs (99%) diff --git a/Readme.md b/Readme.md index cda2b25..ab48fdd 100644 --- a/Readme.md +++ b/Readme.md @@ -105,7 +105,6 @@ file: Include="TenJames.CompMap" Version="latest_version" OutputItemType="Analyzer" - ReferenceOutputAssembly="false" /> ``` diff --git a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs index fa1eb56..1b54106 100644 --- a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs +++ b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs @@ -3,7 +3,7 @@ namespace TenJames.CompMap.IntegrationTests; using System; using System.Collections.Generic; using System.Linq; -using Mappper; +using TenJames.CompMap.Mapper; using Xunit; public class MappingIntegrationTests diff --git a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TenJames.CompMap.IntegrationTests.csproj b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TenJames.CompMap.IntegrationTests.csproj index 5fe7fe3..9a1ab49 100644 --- a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TenJames.CompMap.IntegrationTests.csproj +++ b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TenJames.CompMap.IntegrationTests.csproj @@ -21,7 +21,7 @@ + /> diff --git a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs index 056c01c..8e2bd1a 100644 --- a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs +++ b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs @@ -5,7 +5,7 @@ namespace TenJames.CompMap.IntegrationTests; using System.Globalization; using System.Linq; using Attributes; -using Mappper; +using Mapper; /// /// DTO for reading product data diff --git a/TenJames.CompMap/TenJames.CompMap.Tests/MapperGeneratorTests.cs b/TenJames.CompMap/TenJames.CompMap.Tests/MapperGeneratorTests.cs index cbd049a..3597e6f 100644 --- a/TenJames.CompMap/TenJames.CompMap.Tests/MapperGeneratorTests.cs +++ b/TenJames.CompMap/TenJames.CompMap.Tests/MapperGeneratorTests.cs @@ -9,78 +9,6 @@ namespace TenJames.CompMap.Tests; public class MapperGeneratorTests { - [Fact] - public void AttributeGenerator_ShouldGenerateMapFromAttribute() - { - // Arrange - var attributeGenerator = new AttributeGenerator(); - var driver = CSharpGeneratorDriver.Create(attributeGenerator); - var compilation = CSharpCompilation.Create( - nameof(AttributeGenerator_ShouldGenerateMapFromAttribute), - references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) } - ); - - // Act - var runResult = driver.RunGenerators(compilation).GetRunResult(); - - // Assert - var generatedAttribute = runResult.GeneratedTrees - .FirstOrDefault(t => t.FilePath.EndsWith("MapFromAttribute.g.cs", StringComparison.Ordinal)); - - Assert.NotNull(generatedAttribute); - var generatedCode = generatedAttribute.GetText().ToString(); - Assert.Contains("public class MapFromAttribute", generatedCode); - Assert.Contains("Type sourceType", generatedCode); - } - - [Fact] - public void AttributeGenerator_ShouldGenerateMapToAttribute() - { - // Arrange - var attributeGenerator = new AttributeGenerator(); - var driver = CSharpGeneratorDriver.Create(attributeGenerator); - var compilation = CSharpCompilation.Create( - nameof(AttributeGenerator_ShouldGenerateMapToAttribute), - references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) } - ); - - // Act - var runResult = driver.RunGenerators(compilation).GetRunResult(); - - // Assert - var generatedAttribute = runResult.GeneratedTrees - .FirstOrDefault(t => t.FilePath.EndsWith("MapToAttribute.g.cs", StringComparison.Ordinal)); - - Assert.NotNull(generatedAttribute); - var generatedCode = generatedAttribute.GetText().ToString(); - Assert.Contains("public class MapToAttribute", generatedCode); - Assert.Contains("Type destinationType", generatedCode); - } - - [Fact] - public void AttributeGenerator_ShouldGenerateMapperInterface() - { - // Arrange - var attributeGenerator = new AttributeGenerator(); - var driver = CSharpGeneratorDriver.Create(attributeGenerator); - var compilation = CSharpCompilation.Create( - nameof(AttributeGenerator_ShouldGenerateMapperInterface), - references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) } - ); - - // Act - var runResult = driver.RunGenerators(compilation).GetRunResult(); - - // Assert - var generatedMapper = runResult.GeneratedTrees - .FirstOrDefault(t => t.FilePath.EndsWith("Mapper.g.cs", StringComparison.Ordinal)); - - Assert.NotNull(generatedMapper); - var generatedCode = generatedMapper.GetText().ToString(); - Assert.Contains("public interface IMapper", generatedCode); - Assert.Contains("public class BaseMapper : IMapper", generatedCode); - Assert.Contains("TDestination Map(object source)", generatedCode); - } [Fact] public void MapperGenerator_ShouldRunWithoutErrors() @@ -106,7 +34,7 @@ public partial class Target }"; var compilation = CreateCompilation(sourceCode); - var generators = new IIncrementalGenerator[] { new AttributeGenerator(), new MapperGenerator() }; + var generators = new IIncrementalGenerator[] { new MapperGenerator() }; var driver = CSharpGeneratorDriver.Create(generators); // Act @@ -120,7 +48,7 @@ public partial class Target Assert.Empty(errors); // Check that some code was generated - Assert.True(outputCompilation.SyntaxTrees.Count() > 1, "Generator should produce additional syntax trees"); + Assert.True(outputCompilation.SyntaxTrees.Any(), "Generator should produce additional syntax trees"); } private static CSharpCompilation CreateCompilation(string source) diff --git a/TenJames.CompMap/TenJames.CompMap/AttributeDefinition.cs b/TenJames.CompMap/TenJames.CompMap/AttributeDefinition.cs index 2f3a92f..04b374e 100644 --- a/TenJames.CompMap/TenJames.CompMap/AttributeDefinition.cs +++ b/TenJames.CompMap/TenJames.CompMap/AttributeDefinition.cs @@ -5,7 +5,7 @@ namespace TenJames.CompMap; /// /// Information about a mapping attribute. /// -public class AttributeDefinition +internal class AttributeDefinition { /// /// Name of the attribute. @@ -26,7 +26,7 @@ public class AttributeDefinition /// /// Information about an argument for a mapping attribute. /// -public class ArgumentDefinition +internal class ArgumentDefinition { /// /// Name of the argument. @@ -47,7 +47,7 @@ public class ArgumentDefinition /// /// Static class containing predefined attribute definitions. /// -public static class AttributeDefinitions +internal static class AttributeDefinitions { private static readonly AttributeDefinition MapFrom = new() { diff --git a/TenJames.CompMap/TenJames.CompMap/AttributeGenerator.cs b/TenJames.CompMap/TenJames.CompMap/AttributeGenerator.cs deleted file mode 100644 index 7300266..0000000 --- a/TenJames.CompMap/TenJames.CompMap/AttributeGenerator.cs +++ /dev/null @@ -1,238 +0,0 @@ -namespace TenJames.CompMap; - -using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; - -/// -[Generator] -public class AttributeGenerator : IIncrementalGenerator -{ - - /// - public void Initialize(IncrementalGeneratorInitializationContext context) => RegisterAttributes(context); - - private static void RegisterAttributes(IncrementalGeneratorInitializationContext context) => - context.RegisterPostInitializationOutput(ctx => - { - var attributes = AttributeDefinitions.GetAllAttributes(); - foreach (var attribute in attributes) - { - var sourceText = GenerateAttributeSourceText(attribute); - ctx.AddSource($"{attribute.Name}Attribute.g.cs", sourceText); - } - - // Generate modifier attributes (like AutoPropertyChain) - var modifierAttributes = AttributeDefinitions.GetAllModifierAttributes(); - foreach (var attribute in modifierAttributes) - { - var sourceText = GenerateModifierAttributeSourceText(attribute); - ctx.AddSource($"{attribute.Name}Attribute.g.cs", sourceText); - } - - ctx.AddSource("Mapper.g.cs", GenerateMapperClass()); - }); - - private static SourceText GenerateMapperClass() - { - var sourceText = SourceText.From( - $$""" - // - #nullable enable - using System; - using System.Collections.Generic; - using System.Reflection; - - namespace TenJames.CompMap.Mappper; - - /// - /// Interface for mapping between types. - /// - public interface IMapper - { - /// - /// Tries to map the source object to the destination type. - /// If the mapping is not defined, by default an exception is thrown. - /// - /// Source for the mapping - /// Destination type for the map - /// - TDestination Map(object source); - } - - - /// - /// Base implementation of IMapper - /// - public class BaseMapper : IMapper { - - /// - /// Default implementation for null source - /// - /// Destination type - /// - protected virtual TDestination OnNull() - { - return default(TDestination); - } - - /// - /// Default implementation for mapping - /// - /// the source - /// Method on Source - /// Method on destination - /// Destination type - /// - protected virtual TDestination OnMap(object source, MethodInfo? mapToMethod, MethodInfo? mapFromMethod) - { - if (mapToMethod != null && mapToMethod.ReturnType == typeof(TDestination)) - { - return (TDestination)mapToMethod.Invoke(source, new object[] { this }); - } - - // if TDestination has a static method MapFrom - if (mapFromMethod != null) - { - return (TDestination)mapFromMethod.Invoke(null, new object[] { this, source }); - } - - return OnError(source, new NotImplementedException($"No mapping defined from {source.GetType().FullName} to {typeof(TDestination).FullName}")); - } - - /// - /// An error occurred during mapping - /// - /// - /// - /// Destination type - /// By default, it always throw - /// By default, it throws an error - protected virtual TDestination OnError(object source, Exception ex) - { - throw ex; - } - - /// - /// What should happen when source is enumerable - /// - /// - /// Destination type - /// How should collection be mapped - protected virtual TDestination OnEnumerable(object source) - { - var enumerable = (System.Collections.IEnumerable)source; - - if (typeof(TDestination).IsArray) - { - var elementType = typeof(TDestination).GetElementType(); - var listType = typeof(List<>).MakeGenericType(elementType); - var list = (System.Collections.IList)Activator.CreateInstance(listType); - foreach (var item in enumerable) - { - var mappedItem = this.GetType().GetMethod("Map").MakeGenericMethod(elementType).Invoke(this, new object[] { item }); - list.Add(mappedItem); - } - var array = Array.CreateInstance(elementType, list.Count); - list.CopyTo(array, 0); - return (TDestination)(object)array; - } - if (typeof(TDestination).IsGenericType && ( typeof(TDestination).GetGenericTypeDefinition() == typeof(List<>) || - typeof(TDestination).GetGenericTypeDefinition() == typeof(IEnumerable<>) || - typeof(TDestination).GetGenericTypeDefinition() == typeof(ICollection<>) || - false - )) - { - var elementType = typeof(TDestination).GetGenericArguments()[0]; - var list = (System.Collections.IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); - foreach (var item in enumerable) - { - var mappedItem = this.GetType().GetMethod("Map").MakeGenericMethod(elementType).Invoke(this, new object[] { item }); - list.Add(mappedItem); - } - return (TDestination)list; - } - else - { - return OnError(source, new NotImplementedException($"Mapping to collection type {typeof(TDestination).FullName} is not supported.")); - } - } - - - /// - public virtual TDestination Map(object source) - { - switch (source) - { - case null: - return OnNull(); - - // if source is iEnumerable - case System.Collections.IEnumerable enumerable: - return OnEnumerable(source); - } - - - // if source has a mapping method to TDestination, use it - var mapMethod = source.GetType().GetMethod("MapTo", new Type[] { typeof(IMapper) }); - var mapFromMethod = typeof(TDestination).GetMethod("MapFrom", new Type[] { typeof(IMapper), source.GetType() }); - - return OnMap(source, mapMethod, mapFromMethod); - } - } - - """, - Encoding.UTF8); - return sourceText; - } - - private static SourceText GenerateAttributeSourceText(AttributeDefinition attribute) - { - var src = new StringBuilder(); - src.AppendLine("// "); - src.AppendLine("using System;"); - src.AppendLine(); - src.AppendLine($"namespace {Consts.AttributesNamespace}"); - src.AppendLine("{"); - src.AppendLine($" /// "); - src.AppendLine($" /// {attribute.Description}"); - src.AppendLine($" /// "); - src.AppendLine($" [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]"); - src.AppendLine($" public class {attribute.Name}Attribute ("); - foreach (var arg in attribute.Arguments) - { - var comma = arg != attribute.Arguments.Last() ? "," : ""; - src.AppendLine($" {arg.Type} {arg.Name}{comma} // {arg.Value}"); - } - for (var i = 0; i < attribute.Arguments.Count; i++) - { - var arg = attribute.Arguments[i]; - var comma = i < attribute.Arguments.Count - 1 ? "," : ""; - } - - src.AppendLine(" ): Attribute{"); - src.AppendLine(" }"); - src.AppendLine("}"); - return SourceText.From(src.ToString(), Encoding.UTF8); - } - - private static SourceText GenerateModifierAttributeSourceText(AttributeDefinition attribute) - { - var src = new StringBuilder(); - src.AppendLine("// "); - src.AppendLine("using System;"); - src.AppendLine(); - src.AppendLine($"namespace {Consts.AttributesNamespace}"); - src.AppendLine("{"); - src.AppendLine($" /// "); - src.AppendLine($" /// {attribute.Description}"); - src.AppendLine($" /// "); - src.AppendLine($" [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]"); - src.AppendLine($" public class {attribute.Name}Attribute : Attribute"); - src.AppendLine(" {"); - src.AppendLine(" }"); - src.AppendLine("}"); - return SourceText.From(src.ToString(), Encoding.UTF8); - } -} diff --git a/TenJames.CompMap/TenJames.CompMap/Attributes/AutoPropertyChainAttribute.cs b/TenJames.CompMap/TenJames.CompMap/Attributes/AutoPropertyChainAttribute.cs new file mode 100644 index 0000000..fe373a0 --- /dev/null +++ b/TenJames.CompMap/TenJames.CompMap/Attributes/AutoPropertyChainAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace TenJames.CompMap.Attributes +{ +/// +/// Enables automatic property chain mapping. Maps flattened properties like CategoryName to nested properties like Category.Name. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public class AutoPropertyChainAttribute : Attribute +{ +} +} diff --git a/TenJames.CompMap/TenJames.CompMap/Attributes/MapFromAttribute.cs b/TenJames.CompMap/TenJames.CompMap/Attributes/MapFromAttribute.cs new file mode 100644 index 0000000..c151f0c --- /dev/null +++ b/TenJames.CompMap/TenJames.CompMap/Attributes/MapFromAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace TenJames.CompMap.Attributes +{ +/// +/// Indicates that the decorated class can be mapped from the specified source type. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public class MapFromAttribute ( + Type sourceType // The source type to map from. +): Attribute{ +} +} diff --git a/TenJames.CompMap/TenJames.CompMap/Attributes/MapToAttribute.cs b/TenJames.CompMap/TenJames.CompMap/Attributes/MapToAttribute.cs new file mode 100644 index 0000000..b1cf2ce --- /dev/null +++ b/TenJames.CompMap/TenJames.CompMap/Attributes/MapToAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace TenJames.CompMap.Attributes +{ +/// +/// Indicates that the decorated class can be mapped to the specified destination type. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public class MapToAttribute ( + Type destinationType // The destination type to map to. +): Attribute{ +} +} diff --git a/TenJames.CompMap/TenJames.CompMap/Consts.cs b/TenJames.CompMap/TenJames.CompMap/Consts.cs index b98065d..29f026a 100644 --- a/TenJames.CompMap/TenJames.CompMap/Consts.cs +++ b/TenJames.CompMap/TenJames.CompMap/Consts.cs @@ -3,16 +3,12 @@ namespace TenJames.CompMap; /// /// Internal constants used throughout the CompMap library /// -public static class Consts +internal static class Consts { - /// - /// Attributes namespace - /// - public const string AttributesNamespace = "TenJames.CompMap.Attributes"; /// /// Mapper namespace /// - public const string MapperNamespace = "TenJames.CompMap.Mappper"; + public const string MapperNamespace = "TenJames.CompMap.Mapper"; } diff --git a/TenJames.CompMap/TenJames.CompMap/InternalsVisibleTo.cs b/TenJames.CompMap/TenJames.CompMap/InternalsVisibleTo.cs new file mode 100644 index 0000000..d4666d0 --- /dev/null +++ b/TenJames.CompMap/TenJames.CompMap/InternalsVisibleTo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("TenJames.CompMap.Tests")] diff --git a/TenJames.CompMap/TenJames.CompMap/Mapper.cs b/TenJames.CompMap/TenJames.CompMap/Mapper/Mapper.cs similarity index 99% rename from TenJames.CompMap/TenJames.CompMap/Mapper.cs rename to TenJames.CompMap/TenJames.CompMap/Mapper/Mapper.cs index 58e648e..238f000 100644 --- a/TenJames.CompMap/TenJames.CompMap/Mapper.cs +++ b/TenJames.CompMap/TenJames.CompMap/Mapper/Mapper.cs @@ -1,6 +1,6 @@ // #nullable enable -namespace TenJames.CompMap.Mappper; +namespace TenJames.CompMap.Mapper; using System; using System.Collections; diff --git a/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs b/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs index 375ec41..4f2ed04 100644 --- a/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs +++ b/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs @@ -13,7 +13,7 @@ namespace TenJames.CompMap; /// Mapper generator that creates mapping methods based on attributes /// [Generator] -public class MapperGenerator : IIncrementalGenerator +internal class MapperGenerator : IIncrementalGenerator { /// diff --git a/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs b/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs index 535ab94..0f69f53 100644 --- a/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs +++ b/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs @@ -6,7 +6,7 @@ namespace TenJames.CompMap; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -public class MappingOptions +internal class MappingOptions { public TypeDeclarationSyntax TypeDeclarationSyntax { get; set; } diff --git a/TenJames.CompMap/TenJames.CompMap/SourceBuilder.cs b/TenJames.CompMap/TenJames.CompMap/SourceBuilder.cs index 33db54a..248ee7f 100644 --- a/TenJames.CompMap/TenJames.CompMap/SourceBuilder.cs +++ b/TenJames.CompMap/TenJames.CompMap/SourceBuilder.cs @@ -6,7 +6,7 @@ namespace TenJames.CompMap; /// /// SourceBuilder /// -public class SourceBuilder +internal class SourceBuilder { private readonly StringBuilder sourceText; diff --git a/example/Example.Console/Program.cs b/example/Example.Console/Program.cs index 0b0b46e..ebe9467 100644 --- a/example/Example.Console/Program.cs +++ b/example/Example.Console/Program.cs @@ -1,7 +1,7 @@ using System.Text.Json; using Example.DTOS; using Example.Entities; -using TenJames.CompMap.Mappper; +using TenJames.CompMap.Mapper; var mapper = new BaseMapper(); var jsonOptions = new JsonSerializerOptions { WriteIndented = true }; @@ -31,7 +31,7 @@ Console.WriteLine("\n2. UserReadDto (from Example.DTOS assembly):"); Console.WriteLine(JsonSerializer.Serialize(userDto, jsonOptions)); -Console.WriteLine("\n=== Cross-assembly mapping works! ==="); +Console.WriteLine("\n=== mapping works! ==="); Console.WriteLine("- Models are defined in Example.Entities assembly"); Console.WriteLine("- DTOs with [MapFrom] are defined in Example.DTOS assembly"); Console.WriteLine("- Source generator correctly generates mappings across assembly boundaries"); diff --git a/example/Example.DTOS/Example.DTOS.csproj b/example/Example.DTOS/Example.DTOS.csproj index 73c8c91..aae97b8 100644 --- a/example/Example.DTOS/Example.DTOS.csproj +++ b/example/Example.DTOS/Example.DTOS.csproj @@ -10,7 +10,7 @@ - + diff --git a/example/Example.DTOS/UserDto.cs b/example/Example.DTOS/UserDto.cs index 8aea4e4..2709e66 100644 --- a/example/Example.DTOS/UserDto.cs +++ b/example/Example.DTOS/UserDto.cs @@ -2,7 +2,7 @@ namespace Example.DTOS; using Entities; using TenJames.CompMap.Attributes; -using TenJames.CompMap.Mappper; +using TenJames.CompMap.Mapper; /// /// DTO for reading user data - maps FROM the User entity