Current filter:
                                You should refresh the page.

                                1 Solution

                                Creation Date Importance Sort by

                                The first thing you need to accomplish this task is to get a current application user using the SecuritySystem API (the CurrentUserId property in particular). To do this, use the approach described in the article: How do I get the current user in code?
                                You can set the CreatedXXX and UpdatedXXX properties in the overridden PersistentBase.AfterConstruction and IXPObject.OnSaving  methods in case of using XPO and in the IXafEntityObject.OnCreated, IXafEntityObject.OnSaving methods, in case of using EntityFramework. Many our customers prefer to implement these properties in their own base persistent class once for easier reusability (using inheritance) in many other business classes.

                                To make these properties read-only, set the AllowEdit property for them to "False" using ModelDefaultAttribute. This will allow you to keep the properties read-only in the UI, but still be able to edit them in code. If you do not need to edit them in code, you can follow the How to: Use Read-Only Persistent Properties (XPO) and Store read-only calculated field with Entity Framework Code First articles.

                                So, finally your class can look as follows: 

                                eXpress Persistent Objects (XPO)

                                [C#]
                                using DevExpress.ExpressApp; using DevExpress.ExpressApp.Model; using DevExpress.Persistent.Base; using DevExpress.Persistent.BaseImpl; using DevExpress.Persistent.BaseImpl.PermissionPolicy; using DevExpress.Xpo; using System; using System.ComponentModel; namespace YourSolutionName.Module.BusinessObjects { [DefaultClassOptions] public class Contact : BaseObject { public Contact(Session session) : base(session) { } PermissionPolicyUser GetCurrentUser() { return Session.GetObjectByKey<PermissionPolicyUser>(SecuritySystem.CurrentUserId); } public override void AfterConstruction() { base.AfterConstruction(); CreatedOn = DateTime.Now; CreatedBy = GetCurrentUser(); } protected override void OnSaving() { base.OnSaving(); UpdatedOn = DateTime.Now; UpdatedBy = GetCurrentUser(); } PermissionPolicyUser createdBy; [ModelDefault("AllowEdit", "False")] public PermissionPolicyUser CreatedBy { get { return createdBy; } set { SetPropertyValue("CreatedBy", ref createdBy, value); } } DateTime createdOn; [ModelDefault("AllowEdit", "False"), ModelDefault("DisplayFormat", "G")] public DateTime CreatedOn { get { return createdOn; } set { SetPropertyValue("CreatedOn", ref createdOn, value); } } PermissionPolicyUser updatedBy; [ModelDefault("AllowEdit", "False")] public PermissionPolicyUser UpdatedBy { get { return updatedBy; } set { SetPropertyValue("UpdatedBy", ref updatedBy, value); } } DateTime updatedOn; [ModelDefault("AllowEdit", "False"), ModelDefault("DisplayFormat", "G")] public DateTime UpdatedOn { get { return updatedOn; } set { SetPropertyValue("UpdatedOn", ref updatedOn, value); } } } }
                                [VB.NET]
                                Imports DevExpress.Xpo Imports DevExpress.ExpressApp Imports DevExpress.ExpressApp.Model Imports DevExpress.Persistent.Base Imports DevExpress.Persistent.BaseImpl Imports DevExpress.Persistent.BaseImpl.PermissionPolicy Namespace YourSolutionName.Module.BusinessObjects <DefaultClassOptions> Public Class Contact Inherits BaseObject Public Sub New(ByVal session As Session) MyBase.New(session) End Sub Private Function GetCurrentUser() As PermissionPolicyUser Return Session.GetObjectByKey(Of PermissionPolicyUser)(SecuritySystem.CurrentUserId) End Function Public Overrides Sub AfterConstruction() MyBase.AfterConstruction() CreatedOn = Date.Now CreatedBy = GetCurrentUser() End Sub Protected Overrides Sub OnSaving() MyBase.OnSaving() UpdatedOn = Date.Now UpdatedBy = GetCurrentUser() End Sub Private createdBy_Renamed As PermissionPolicyUser <ModelDefault("AllowEdit", "False")> Public Property CreatedBy() As PermissionPolicyUser Get Return createdBy_Renamed End Get Set(ByVal value As PermissionPolicyUser) SetPropertyValue("CreatedBy", createdBy_Renamed, value) End Set End Property Private createdOn_Renamed As Date <ModelDefault("AllowEdit", "False"), ModelDefault("DisplayFormat", "G")> Public Property CreatedOn() As Date Get Return createdOn_Renamed End Get Set(ByVal value As Date) SetPropertyValue("CreatedOn", createdOn_Renamed, value) End Set End Property Private updatedBy_Renamed As PermissionPolicyUser <ModelDefault("AllowEdit", "False")> Public Property UpdatedBy() As PermissionPolicyUser Get Return updatedBy_Renamed End Get Set(ByVal value As PermissionPolicyUser) SetPropertyValue("UpdatedBy", updatedBy_Renamed, value) End Set End Property Private updatedOn_Renamed As Date <ModelDefault("AllowEdit", "False"), ModelDefault("DisplayFormat", "G")> Public Property UpdatedOn() As Date Get Return updatedOn_Renamed End Get Set(ByVal value As Date) SetPropertyValue("UpdatedOn", updatedOn_Renamed, value) End Set End Property End Class End Namespace

                                 

                                Entity Framework (EF)

                                [C#]
                                using DevExpress.ExpressApp; using DevExpress.ExpressApp.Model; using DevExpress.Persistent.Base; using DevExpress.Persistent.BaseImpl.EF.PermissionPolicy; using System; using System.ComponentModel; namespace YourSolutioName.Module.BusinessObjects { [DefaultClassOptions] public class Contact : IXafEntityObject, IObjectSpaceLink { PermissionPolicyUser GetCurrentUser() { return objectSpace.GetObjectByKey<PermissionPolicyUser>(SecuritySystem.CurrentUserId); } public void OnCreated() { CreatedBy = GetCurrentUser(); CreatedOn = DateTime.Now; } public void OnSaving() { if (objectSpace != null) { UpdatedBy = GetCurrentUser(); UpdatedOn = DateTime.Now; } } public void OnLoaded() { } [Browsable(false)] public int Id { get; protected set; } [ModelDefault("AllowEdit", "False")] public PermissionPolicyUser CreatedBy { get; set; } [ModelDefault("AllowEdit", "False"), ModelDefault("DisplayFormat", "G")] public DateTime CreatedOn { get; set; } [ModelDefault("AllowEdit", "False")] public PermissionPolicyUser UpdatedBy { get; set; } [ModelDefault("AllowEdit", "False"), ModelDefault("DisplayFormat", "G")] public DateTime UpdatedOn { get; set; } #region IObjectSpaceLink members private IObjectSpace objectSpace; IObjectSpace IObjectSpaceLink.ObjectSpace { get { return objectSpace; } set { objectSpace = value; } } #endregion } }
                                [VB.NET]
                                Imports System Imports System.ComponentModel Imports DevExpress.ExpressApp Imports DevExpress.ExpressApp.Model Imports DevExpress.Persistent.Base Imports DevExpress.Persistent.BaseImpl.EF.PermissionPolicy Namespace YourSolutioName.Module.BusinessObjects <DefaultClassOptions> Public Class Contact Implements IXafEntityObject, IObjectSpaceLink Private Function GetCurrentUser() As PermissionPolicyUser Return objectSpace_Renamed.GetObjectByKey(Of PermissionPolicyUser)(SecuritySystem.CurrentUserId) End Function Public Sub OnCreated() Implements IXafEntityObject.OnCreated CreatedBy = GetCurrentUser() CreatedOn = Date.Now End Sub Public Sub OnSaving() Implements IXafEntityObject.OnSaving If objectSpace_Renamed IsNot Nothing Then UpdatedBy = GetCurrentUser() UpdatedOn = Date.Now End If End Sub Public Sub OnLoaded() Implements IXafEntityObject.OnLoaded End Sub Private privateId As Integer <Browsable(False)> Public Property Id() As Integer Get Return privateId End Get Protected Set(ByVal value As Integer) privateId = value End Set End Property <ModelDefault("AllowEdit", "False")> Public Property CreatedBy() As PermissionPolicyUser <ModelDefault("AllowEdit", "False"), ModelDefault("DisplayFormat", "G")> Public Property CreatedOn() As Date <ModelDefault("AllowEdit", "False")> Public Property UpdatedBy() As PermissionPolicyUser <ModelDefault("AllowEdit", "False"), ModelDefault("DisplayFormat", "G")> Public Property UpdatedOn() As Date #Region "IObjectSpaceLink members" Private objectSpace_Renamed As IObjectSpace Private Property IObjectSpaceLink_ObjectSpace() As IObjectSpace Implements IObjectSpaceLink.ObjectSpace Get Return objectSpace_Renamed End Get Set(ByVal value As IObjectSpace) objectSpace_Renamed = value End Set End Property #End Region End Class End Namespace

                                NOTE: Use your own security user type, which can be different from PermissionPolicyUser. You can see the correct type for your application in the SecurityStrategy > UserType  property in the Application Designer.


                                Further implementation considerations
                                1.
                                 If you are using the Clone Object module, decorate these service properties with the NonCloneable attribute to stop cloning their values by default.
                                2. If you want to temporarily hide these properties from the UI (with the capability to make them visible later), consider decorating them with the VisibleXXX code attributes: (VisibleInDetailViewAttributeVisibleInListViewAttributeVisibleInLookupListViewAttributeVisibleInReportsAttributeVisibleInDashboardsAttribute). Use the System.ComponentModel.BrowsableAttribute to completely hide this data from the UI.
                                3. If an application serves users in different time zones, consider using UtcDateTimeConverter when declaring your DateTime properties.
                                4. If the CreatedBy/UpdatedBy properties have an impact on an application's performance, consider the following tuning in the first place:
                                    - consider storing user's name in the CreatedBy ModifiedBy properties instead of the Oid property. Note that even though removing a dependence between tables may prevent loading extra user data, it results in data denormalization.
                                    - if you perform bulk updating of your database or use the database with external systems, consider creating an index involving these properties.
                                    - if you use the FullTextSearch Action, consider decorating these properties using SearchMemberOptionsAttribute with the SearchMemberMode.Exclude value to prevent using these properties' values in the search query.
                                In case of any problems with the application performance, profile your application as per the T148978: How to measure and improve the application's performance article first.

                                 




                                See also:
                                Concepts > Data Manipulation and Business Logic
                                Security - How to initialize the CreatedBy/ModifiedBy properties of a persistent object in a non-XAF console application when the WCF security is used
                                How to filter records by the current user (the object owner feature)
                                Security System Overview
                                Audit Trail Module Overview
                                Pull CreatedDateTime and UpdatedDateTime values from audittrail (an alternative approach without persisting any information, but which is not good from the performance point of view)

                                Created By, Edited By, Date Created, Date Modified properties for each entity in Domain Driven Design (StackOverFlow)
                                Audit fields(CreatedBy, UpdatedBy) in tables. Is it good idea? (StackOverFlow)


                                Search keywords: ModifiedBy, ModifiedOn, DateCreated, DateModified, LastModifiedDate, LastModifiedDate, LastModifiedBy, Owner, current user, date, time, stamp

                                Show all comments
                                • Dave Hesketh (Llamachant Technology) 09.07.2017
                                  Recommend adding the [NonCloneable] attribute to all of these properties and consider [VisibleInDetailView(false)] as the audit data tends to be unnecessarily crowded on a lot of views.
                                • Mario Blatarić 09.08.2017
                                  Hi, 

                                  -‍> what Dave said :-)

                                  A‍lso, I stuffed this properties in custom base class from which all of my business classes are inherited from (instead of standard BaseClass). Which also gives benefit to further customize all business classes at once if needed.
                                  Of course, this makes sense only if you want audit fields in all tables - which I want. 

                                  A‍dditionally, I am writing username instead of user id, which makes tables more readable in database (directly) and removes object dependency in user table. 

                                  I‍f you do want to use user object instead, it would be advisable to set NoForeignKey attribute especially if you have large data model. 

                                  R‍egards,
                                  M‍ario
                                • Alex Miller 09.08.2017
                                  I would normalize all persisted datetime stamps to UTC using DateTime.UtcNow instead of DateTime.Now. Just in case your end users enter data in different time zones.
                                • Alex Miller 09.08.2017
                                  I just saw in the comments on Dennis blog that Chris Royle already suggested using the UtcDateTimeConverter ValueConverterAttribute on the datetime stamps. I didn't know about this converter but this is the ideal solution to normalize persisted dates to UTC, but still display them in local time to the end user. Thanks Chris!
                                • Dave Hesketh (Llamachant Technology) 09.09.2017
                                  To build on what Mario and Chris wrote, if you are experiencing a performance hit and you have linked directly to your security object type, then you may want to remove them from the search criteria by adding the [SearchMemberOptions(SearchMemberMode.Exclude)] attribute. 
                                • Chris Royle (LOB) 09.11.2017
                                  Here's my unedited comments from Dennis' blog to keep these together (If only there was some form of software which one could use for discussing topics as a community - almost like the Romans did in their public spaces ;) :

                                  An observation, it's probably better to use Nameof() in the SetPropertyValue calls.


                                  Another, if you're using the Persistent/PersistentAlias pattern, you should be aware of calling OnPropertyChanged otherwise you can run into issues when trying to use partial SQL updates, see comments in T513152.


                                  Yet another observation... adding the audit information is useful, but does come with an unexpected performance penalty. You are adding loads more SQL SELECTs to populate the user classes. If there is fan-out from your user class to other tables, then this is added as well. 


                                  And finally, UpdatedOn is probably a good candidate field for an index if you're performing bulk updates / integrating with other systems. 


                                  Someone has also mentioned NonCloneable attribute in the topic.


                                  Something else with considering is whether you should explicitly store dates as UTC and make use of [ValueConverter(typeof(UtcDateTimeConverter))] decoration.


                                • Chris Royle (LOB) 09.11.2017
                                  @Alex, you're more than welcome. I only wish that I'd known about this sooner.
                                  @Dave,  I don't think that SearchMemberOptions helps. My issue is more related to when classes are loaded from database into session. I think this attribute only controls searching ? I'll have a play at some point with SQL profiler loaded.
                                • Alex Miller 09.11.2017
                                  @Chris I'm with you about the blog comments. I've been pestering Dennis to use Discourse for the XAF community. With the LinkedIn group, blog and KB, information is spread/duplicated all over the place. Notification digest and @mentions would be certainly welcome.
                                • Chris Royle (LOB) 09.11.2017
                                  @Alex, many years ago there were community forums under https://community.devexpress.com/forums/ which were shut down in the name of progress (despite protest).
                                • Andrey K (DevExpress Support) 09.13.2017

                                  Hello friends,

                                  Thank you all for your comments and the ideas you shared with us. We intentionally tried to make the article as simple as possible to allow our customers to adjust it to their needs as required. We added your notes to the "Further implementation considerations" section so they enrich the article with this valuable information. Your cooperation is very appreciated.

                                  Thanks,
                                  Andrey

                                • Andrey K (DevExpress Support) 09.13.2017

                                  @Mario Blatarić

                                  Hello Mario,

                                  >>>Izwjf you do want to use user object instead, it would be advisable to set NoForeignKey attribute especially if you have large data model. 

                                  The use of NoForeignKeyAttribute makes sense only with legacy databases that require that no constraint is enforced at the database level. We do not recommend this attribute by default as its use may lead to unexpected results. Would you please clarify why you needed to use NoForeignKeyAttribute or when and exactly how it helped you (please create a separate ticket on this matter).

                                  Thanks,
                                  Andrey

                                • Scott Gross 09.14.2017
                                  How can we ensure the the security system will allow writing to these properties on any descendent object without manually setting Allow write in every object when we are using PermissionPolicy User and Deny all by default?
                                • Andrey K (DevExpress Support) 09.15.2017
                                  Hello Scott,

                                  Please give me additional time to research this question. I will update this thread as soon as I can.

                                  Thanks,
                                  Andrey
                                • Andrey K (DevExpress Support) 09.18.2017

                                  Hello,

                                  We researched the issue and came to the conclusion that there is no way to make these properties writable in descendants. So, to make them work, you need to allow writing in these properties as in any common property. We will discuss how to cover this scenario when planning our future releases.

                                  Thanks,
                                  Andrey

                                • Chris Royle (LOB) 09.18.2017
                                  @Andrey K. re. the above - as a suggestion, is it not possible to circumvent the security system by utilising interfaces e.g. an IAuditableObject interface.
                                • Andrey K (DevExpress Support) 09.18.2017
                                  @Chris.  Thank you for the suggestion. We will discuss it as well.

                                  Andrey
                                • Scott Gross 11.28.2017
                                  I have a custom Secure ObjectSpace Provider, would there be a way to allow write for these members at this level?
                                • Uriah (DevExpress Support) 11.29.2017

                                  Hello Scott ,

                                  I've created a separate ticket on your behalf (T581984: Would the solution provided in the K18352 ticket work with a custom Secure ObjectSpace Provider). It has been placed in our processing queue and will be answered shortly.

                                • Jacob de Boer 2 03.22.2018
                                  Hi there

                                  I've asked this a seperate ticket, but I also wanted to ask this on this KB, because it seems relevant:

                                  How can I ignore the UpdatedBy and UpdatedOn properties in OptimisticLocking? I don't want to get any locking exceptions when different users try to modify this properties. Last user wins for this properties.

                                  Thanks,
                                  Jacob
                                • Dennis (DevExpress Support) 03.26.2018

                                  Hello Jacob,
                                  We appreciate your update. I think that you are referring to our recent discussion at Ignore certain properties from Optimistic Locking. If so, I agree that it can be helpful for other users.

                                  If you need our additional assistance regarding the same topic, feel free to reactivate your original ticket. Thanks.