From f1818aae1b3f8711ce487537b10df159a8d17fc4 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Mon, 26 Aug 2013 16:58:08 +0200 Subject: [PATCH 1/3] Updating the Profile and Member classes --- src/Umbraco.Core/Models/Membership/Member.cs | 131 +++++++++++++++++- src/Umbraco.Core/Models/Membership/Profile.cs | 22 ++- 2 files changed, 143 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Models/Membership/Member.cs b/src/Umbraco.Core/Models/Membership/Member.cs index e1d7224623..3fa26af085 100644 --- a/src/Umbraco.Core/Models/Membership/Member.cs +++ b/src/Umbraco.Core/Models/Membership/Member.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; using System.Runtime.Serialization; namespace Umbraco.Core.Models.Membership @@ -13,11 +15,123 @@ namespace Umbraco.Core.Models.Membership /// [Serializable] [DataContract(IsReference = true)] + [DebuggerDisplay("Id: {Id}")] internal class Member : MemberProfile, IMembershipUser { - public int Id { get; set; } + private bool _hasIdentity; + private int _id; + private Guid _key; + private DateTime _createDate; + private DateTime _updateDate; + + private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); + private static readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); + private static readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); + private static readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); + private static readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); + + /// + /// Integer Id + /// + [DataMember] + public new int Id + { + get + { + return _id; + } + set + { + SetPropertyValueAndDetectChanges(o => + { + _id = value; + HasIdentity = true; //set the has Identity + return _id; + }, _id, IdSelector); + } + } + + /// + /// Guid based Id + /// + /// The key is currectly used to store the Unique Id from the + /// umbracoNode table, which many of the entities are based on. + [DataMember] + public Guid Key + { + get + { + if (_key == Guid.Empty) + return _id.ToGuid(); + + return _key; + } + set + { + SetPropertyValueAndDetectChanges(o => + { + _key = value; + return _key; + }, _key, KeySelector); + } + } + + /// + /// Gets or sets the Created Date + /// + [DataMember] + public DateTime CreateDate + { + get { return _createDate; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _createDate = value; + return _createDate; + }, _createDate, CreateDateSelector); + } + } + + /// + /// Gets or sets the Modified Date + /// + [DataMember] + public DateTime UpdateDate + { + get { return _updateDate; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _updateDate = value; + return _updateDate; + }, _updateDate, UpdateDateSelector); + } + } + + /// + /// Indicates whether the current entity has an identity, eg. Id. + /// + public virtual bool HasIdentity + { + get + { + return _hasIdentity; + } + protected set + { + SetPropertyValueAndDetectChanges(o => + { + _hasIdentity = value; + return _hasIdentity; + }, _hasIdentity, HasIdentitySelector); + } + } + public string Username { get; set; } public string Email { get; set; } + public string Password { get; set; } public string PasswordQuestion { get; set; } public string PasswordAnswer { get; set; } @@ -28,11 +142,18 @@ namespace Umbraco.Core.Models.Membership public DateTime LastLoginDate { get; set; } public DateTime LastPasswordChangeDate { get; set; } public DateTime LastLockoutDate { get; set; } + public object ProfileId { get; set; } public IEnumerable Groups { get; set; } - public Guid Key { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public bool HasIdentity { get; private set; } + + #region Internal methods + + internal virtual void ResetIdentity() + { + _hasIdentity = false; + _id = default(int); + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/Profile.cs b/src/Umbraco.Core/Models/Membership/Profile.cs index a1c4d54e1b..b3959bb9cd 100644 --- a/src/Umbraco.Core/Models/Membership/Profile.cs +++ b/src/Umbraco.Core/Models/Membership/Profile.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Models.Membership /// /// Initializes a new instance of the class. /// - public Profile() + protected Profile() { ProviderUserKeyType = typeof(int); } @@ -29,14 +29,16 @@ namespace Umbraco.Core.Models.Membership private object _id; private string _name; + private object _providerUserKey; private Type _userTypeKey; private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + private static readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKey); private static readonly PropertyInfo UserTypeKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKeyType); [DataMember] - public object Id + public virtual object Id { get { @@ -53,7 +55,7 @@ namespace Umbraco.Core.Models.Membership } [DataMember] - public string Name + public virtual string Name { get { @@ -72,8 +74,18 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] public virtual object ProviderUserKey { - get { throw new System.NotImplementedException(); } - set { throw new System.NotImplementedException(); } + get + { + return _providerUserKey; + } + set + { + SetPropertyValueAndDetectChanges(o => + { + _providerUserKey = value; + return _id; + }, _providerUserKey, ProviderUserKeySelector); + } } /// From 37d190c97db93cb8cf73169c8ca4f20b1d6231f7 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Mon, 26 Aug 2013 16:59:41 +0200 Subject: [PATCH 2/3] Implementing read-only DTOs for improved reading of Members from db. Started implementation of Member, MemberType and MemberGroup repositories. --- .../Models/Rdbms/MemberReadOnlyDto.cs | 74 ++++++++++ .../Models/Rdbms/PropertyDataReadOnlyDto.cs | 65 +++++++++ .../Relators/PropertyDataRelator.cs | 43 ++++++ .../Interfaces/IMemberRepository.cs | 9 ++ .../Repositories/MemberGroupRepository.cs | 7 + .../Repositories/MemberRepository.cs | 133 ++++++++++++++++++ .../Repositories/MemberTypeRepository.cs | 7 + .../Persistence/RepositoryFactory.cs | 5 + src/Umbraco.Core/Umbraco.Core.csproj | 7 + 9 files changed, 350 insertions(+) create mode 100644 src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs create mode 100644 src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs create mode 100644 src/Umbraco.Core/Persistence/Relators/PropertyDataRelator.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs diff --git a/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs new file mode 100644 index 0000000000..21c6ad7780 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/MemberReadOnlyDto.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("umbracoNode")] + [PrimaryKey("id")] + [ExplicitColumns] + public class MemberReadOnlyDto + { + /* from umbracoNode */ + [Column("id")] + public int NodeId { get; set; } + + [Column("trashed")] + public bool Trashed { get; set; } + + [Column("parentID")] + public int ParentId { get; set; } + + [Column("nodeUser")] + public int? UserId { get; set; } + + [Column("level")] + public short Level { get; set; } + + [Column("path")] + public string Path { get; set; } + + [Column("sortOrder")] + public int SortOrder { get; set; } + + [Column("uniqueID")] + public Guid? UniqueId { get; set; } + + [Column("text")] + public string Text { get; set; } + + [Column("nodeObjectType")] + public Guid? NodeObjectType { get; set; } + + [Column("createDate")] + public DateTime CreateDate { get; set; } + + /* from cmsContentType joined with cmsContent */ + [Column("ContentTypeAlias")] + public string ContentTypeAlias { get; set; } + + /* cmsContentVersion */ + [Column("VersionId")] + public Guid VersionId { get; set; } + + [Column("VersionDate")] + public DateTime UpdateDate { get; set; } + + [Column("LanguageLocale")] + public string Language { get; set; } + + /* cmsMember */ + [Column("Email")] + public string Email { get; set; } + + [Column("LoginName")] + public string LoginName { get; set; } + + [Column("Password")] + public string Password { get; set; } + + /* Properties */ + [ResultColumn] + public List Properties { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs new file mode 100644 index 0000000000..c6f08f3605 --- /dev/null +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataReadOnlyDto.cs @@ -0,0 +1,65 @@ +using System; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Models.Rdbms +{ + [TableName("cmsPropertyData")] + [PrimaryKey("id")] + [ExplicitColumns] + public class PropertyDataReadOnlyDto + { + /* cmsPropertyData */ + [Column("id")] + public int Id { get; set; } + + [Column("contentNodeId")] + public int NodeId { get; set; } + + [Column("VersionId")] + public Guid? VersionId { get; set; } + + [Column("propertytypeid")] + public int PropertyTypeId { get; set; } + + [Column("dataInt")] + public int? Integer { get; set; } + + [Column("dataDate")] + public DateTime? Date { get; set; } + + [Column("dataNvarchar")] + public string VarChar { get; set; } + + [Column("dataNtext")] + public string Text { get; set; } + + /* cmsPropertyType */ + [Column("dataTypeId")] + public int DataTypeId { get; set; } + + [Column("propertyTypeGroupId")] + public int? PropertyTypeGroupId { get; set; } + + [Column("Alias")] + public string Alias { get; set; } + + [Column("Name")] + public string Name { get; set; } + + [Column("helpText")] + public string HelpText { get; set; } + + [Column("mandatory")] + public bool Mandatory { get; set; } + + [Column("validationRegExp")] + public string ValidationRegExp { get; set; } + + [Column("Description")] + public string Description { get; set; } + + /* cmsDataType */ + [Column("controlId")] + public Guid ControlId { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/PropertyDataRelator.cs b/src/Umbraco.Core/Persistence/Relators/PropertyDataRelator.cs new file mode 100644 index 0000000000..49c5e671e6 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Relators/PropertyDataRelator.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Relators +{ + internal class PropertyDataRelator + { + internal MemberReadOnlyDto Current; + + internal MemberReadOnlyDto Map(MemberReadOnlyDto a, PropertyDataReadOnlyDto p) + { + // Terminating call. Since we can return null from this function + // we need to be ready for PetaPoco to callback later with null + // parameters + if (a == null) + return Current; + + // Is this the same MemberReadOnlyDto as the current one we're processing + if (Current != null && Current.UniqueId == a.UniqueId) + { + // Yes, just add this PropertyDataReadOnlyDto to the current MemberReadOnlyDto's collection + Current.Properties.Add(p); + + // Return null to indicate we're not done with this MemberReadOnlyDto yet + return null; + } + + // This is a different MemberReadOnlyDto to the current one, or this is the + // first time through and we don't have a Tab yet + + // Save the current MemberReadOnlyDto + var prev = Current; + + // Setup the new current MemberReadOnlyDto + Current = a; + Current.Properties = new List(); + Current.Properties.Add(p); + + // Return the now populated previous MemberReadOnlyDto (or null if first time through) + return prev; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs new file mode 100644 index 0000000000..9ce6642e3b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Persistence.Repositories +{ + internal interface IMemberRepository : IRepositoryVersionable + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs new file mode 100644 index 0000000000..182e47eecf --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Persistence.Repositories +{ + public class MemberGroupRepository + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs new file mode 100644 index 0000000000..2b4ca4e92c --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Caching; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Relators; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + internal class MemberRepository : VersionableRepositoryBase, IMemberRepository + { + public MemberRepository(IDatabaseUnitOfWork work) : base(work) + { + } + + public MemberRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) + { + } + + /* The following methods might be relevant for this specific repository (in an optimized form) + * GetById - get a member by its integer Id + * GetByKey - get a member by its unique guid Id (which should correspond to a membership provider user's id) + * GetByPropertyValue - get members with a certain property value (supply both property alias and value?) + * GetByMemberTypeAlias - get all members of a certain type + * GetByMemberGroup - get all members in a specific group + * GetAllMembers + */ + + #region Overrides of RepositoryBase + + protected override IMembershipUser PerformGet(int id) + { + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id }); + sql.OrderByDescending(x => x.VersionDate); + + var dto = + Database.Fetch( + new PropertyDataRelator().Map, sql); + + if (dto == null || dto.Any() == false) + return null; + + return new Member(); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + + #endregion + + #region Overrides of PetaPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) + { + //TODO Count + var sql = new Sql(); + sql.Select("umbracoNode.*", "cmsContentType.alias AS ContentTypeAlias", "cmsContentVersion.VersionId", + "cmsContentVersion.VersionDate", "cmsContentVersion.LanguageLocale", "cmsMember.Email", + "cmsMember.LoginName", "cmsMember.Password", "cmsPropertyData.id", "cmsPropertyData.dataDate", + "cmsPropertyData.dataInt", "cmsPropertyData.dataNtext", "cmsPropertyData.dataNvarchar", + "cmsPropertyData.propertytypeid", "cmsPropertyType.Alias", "cmsPropertyType.Description", + "cmsPropertyType.Name", "cmsPropertyType.mandatory", "cmsPropertyType.validationRegExp", + "cmsPropertyType.helpText", "cmsPropertyType.propertyTypeGroupId", "cmsPropertyType.dataTypeId", + "cmsDataType.controlId") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.VersionId, right => right.VersionId) + .LeftJoin().On(left => left.Id, right => right.PropertyTypeId) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + throw new NotImplementedException(); + } + + protected override Guid NodeObjectTypeId + { + get { return new Guid(Constants.ObjectTypes.Member); } + } + + #endregion + + #region Unit of Work Implementation + + protected override void PersistNewItem(IMembershipUser entity) + { + throw new NotImplementedException(); + } + + protected override void PersistUpdatedItem(IMembershipUser entity) + { + throw new NotImplementedException(); + } + + #endregion + + #region Overrides of VersionableRepositoryBase + + public override IMembershipUser GetByVersion(Guid versionId) + { + throw new NotImplementedException(); + } + + protected override void PerformDeleteVersion(int id, Guid versionId) + { + throw new NotImplementedException(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs new file mode 100644 index 0000000000..eb971eec56 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Persistence.Repositories +{ + public class MemberTypeRepository + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 1d9d9aff21..36d035244e 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -115,6 +115,11 @@ namespace Umbraco.Core.Persistence CreateUserTypeRepository(uow)); } + internal virtual IMemberRepository CreateMemberRepository(IDatabaseUnitOfWork uow) + { + return new MemberRepository(uow, RuntimeCacheProvider.Current); + } + internal virtual IEntityRepository CreateEntityRepository(IDatabaseUnitOfWork uow) { return new EntityRepository(uow); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2dcea41648..7f07eeb1b7 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -201,6 +201,8 @@ + + @@ -468,6 +470,7 @@ + @@ -484,6 +487,7 @@ + @@ -498,6 +502,9 @@ + + + From 2b82e74b90ec591f4ec3e7723eb841a7f269b8b3 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Mon, 26 Aug 2013 17:00:12 +0200 Subject: [PATCH 3/3] Adding test fixture for the MemberRepository --- .../Repositories/MemberRepositoryTest.cs | 91 +++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 2 files changed, 92 insertions(+) create mode 100644 src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs new file mode 100644 index 0000000000..76108692c1 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -0,0 +1,91 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests.Persistence.Repositories +{ + [TestFixture, NUnit.Framework.Ignore] + public abstract class MemberRepositoryTest : MemberRepositoryBaseTest + { + private Database _database; + + #region Overrides of MemberRepositoryBaseTest + + [SetUp] + public override void Initialize() + { + base.Initialize(); + + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + + _database = new Database(@"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco", + "System.Data.SqlClient"); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + public override Database Database + { + get { return _database; } + } + + #endregion + + + } + + [TestFixture] + public abstract class MemberRepositoryBaseTest + { + [SetUp] + public virtual void Initialize() + { + TestHelper.SetupLog4NetForTests(); + TestHelper.InitializeContentDirectories(); + + string path = TestHelper.CurrentAssemblyDirectory; + AppDomain.CurrentDomain.SetData("DataDirectory", path); + + RepositoryResolver.Current = new RepositoryResolver( + new RepositoryFactory()); + + ApplicationContext.Current = new ApplicationContext( + //assign the db context + new DatabaseContext(new DefaultDatabaseFactory()), + //assign the service context + new ServiceContext(new PetaPocoUnitOfWorkProvider(), new FileUnitOfWorkProvider(), new PublishingStrategy()), + //disable cache + false) + { + IsReady = true + }; + + Resolution.Freeze(); + } + + [TearDown] + public virtual void TearDown() + { + SqlSyntaxContext.SqlSyntaxProvider = null; + AppDomain.CurrentDomain.SetData("DataDirectory", null); + + //reset the app context + ApplicationContext.Current = null; + + RepositoryResolver.Reset(); + } + + public abstract Database Database { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 72950fe466..0d2d690a46 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -188,6 +188,7 @@ +