Showing posts with label Xaf Tips. Show all posts
Showing posts with label Xaf Tips. Show all posts

Monday, January 31, 2011

XAF Pessimistic Locking

 

What Locking is all about

Transactional isolation is usually implemented by locking whatever is accessed in a transaction. There are two different approaches to transactional locking: Pessimistic locking and optimistic locking.

The disadvantage of pessimistic locking is that a resource is locked from the time it is first accessed in a transaction until the transaction is finished, making it inaccessible to other transactions during that time. If most transactions simply look at the resource and never change it, an exclusive lock may be overkill as it may cause lock contention, and optimistic locking may be a better approach. With pessimistic locking, locks are applied in a fail-safe way. In the banking application example, an account is locked as soon as it is accessed in a transaction. Attempts to use the account in other transactions while it is locked will either result in the other process being delayed until the account lock is released, or that the process transaction will be rolled back. The lock exists until the transaction has either been committed or rolled back.

With optimistic locking, a resource is not actually locked when it is first is accessed by a transaction. Instead, the state of the resource at the time when it would have been locked with the pessimistic locking approach is saved. Other transactions are able to concurrently access to the resource and the possibility of conflicting changes is possible. At commit time, when the resource is about to be updated in persistent storage, the state of the resource is read from storage again and compared to the state that was saved when the resource was first accessed in the transaction. If the two states differ, a conflicting update was made, and the transaction will be rolled back.

In the banking application example, the amount of an account is saved when the account is first accessed in a transaction. If the transaction changes the account amount, the amount is read from the store again just before the amount is about to be updated. If the amount has changed since the transaction began, the transaction will fail itself, otherwise the new amount is written to persistent storage.

XAF Build in locking mechanism

XAF’s datalayer is based on XPO , which already has an optimistic locking implementation on its core. It is enabled by default for all objects that inherit XPBaseObjet and can be disabled by using the OptimisticLocking attribute .

    [OptimisticLocking(false)]

    public class Client:XPBaseObject {

        public Client(Session session) : base(session) {

        }

    }

For objects that have that attribute XPO is going to create an extra service field, the OptimisticLockingField to store that state of the object.

 

To see the locking mechanism in action you can perform the following steps

 

User 1 User 2

1. Run application

1. Run application

2. Go to Client object detail view

2. Go to Client object detail view

3. XPO reads the value from the optimistic field value (initial value is zero) and stores it to memory 3. XPO reads the value from the optimistic field value (initial value is zero) and stores it to memory

4. User is making a change and tries to save the object

  1. XPO queries the the optimisticfield value and compares it with the one in memory
  2. 2 values are equal so transaction is commited and optimisticlocking fields is raized by one

4. User is making a change and tries to save the object

  1. XPO queries the the optimisticfield value (now its one cause user 1 has save the record) and compares it with the one in memory
  2. 2 values are not equal so a locking exception is thrown by XPO giving information about the locked object

  5. User reloads the object from the database, new optimisticlocking field value is store in memory (one), user makes changes and is able to save the record

eXpand Pessimistic locking

XPO has no means to determine any information about users, but we are in XAF context and XAF has a build in security system and we know the current user. Lets try to implement a pessimistic locking feature for our XAF applications.

By carefully looking at “3. XPO reads the value from the optimistic field value (initial value is zero) and stores it to memory “ , we can see that there is a place to minimize the locking conflicts. What if we display the detailview in “ViewMode” and add an “Edit” action? And when the Edit action is executed the object will be reloaded from the db, also when is saved the DetailView will return is View mode.

I think we are going to gain something from that even if we use the default Xpo optimistic locking. Lucky us eXpand has already the ViewEditMode attribute that can do that job for us.

image  image

Now from the Pessimistic locking definition (when it is first is accessed by a transaction). We have to decide what that means in our case. A good idea would be to lock the object at the time that is changed by a user. Of course we can enhance that in the future upon your requests. We also need a special field that will store the user that locked the record and a special attribute to mark the object for using our pessimistic locking attribute. Finally its a good idea to disable default OptimisticLocking mechanism.

All the above should be transparent to the user of our feature, we need to spent zero time when using it in future projects

Customizing XAF types is very easy as you know , just some lines of code and can do the trick.

        public override void CustomizeTypesInfo(DevExpress.ExpressApp.DC.ITypesInfo typesInfo) {

            base.CustomizeTypesInfo(typesInfo);

            var typeInfos = typesInfo.PersistentTypes.Where(info => info.FindAttribute<PessimisticLockingAttribute>() != null);

            foreach (var typeInfo in typeInfos) {

                typeInfo.AddAttribute(new OptimisticLockingAttribute(false));

                var memberInfo = typeInfo.FindMember(LockedUser);

                if (memberInfo == null) {

                    memberInfo = typeInfo.CreateMember(LockedUser, SecuritySystem.UserType);

                    memberInfo.AddAttribute(new BrowsableAttribute(false));

                }

            }

        }

 

Great! Now we need to define our specifications. Remember we are dealing with data now and with a complex feature that may evolve from fellow developers request. For those cases at eXpand we use a BDD approach, and MSpec as our BDD framework.

 

Here are some specs

PessimisticLockingViewController, When Object Change
» should lock the object

PessimisticLockingViewController, When objectspace rollback
» should unlock object

PessimisticLockingViewController, When ospace commited
» should unlock object

PessimisticLockingViewController, When View CurrentObject Changing
» should unlock object

PessimisticLockingViewController, When View Is closing
» should unlock object

PessimisticLocker, When object is about to be unlocked
» should not unlock if current user does not match locked user

PessimisticLockingViewController, When a locked object is open by a second user
» should not allowedit on view

PessimisticLockingViewController, When 2 users open the same object and both try to change it
» should mark as readonly last user view

PessimisticLockingViewController, When editing a locked detailview
» should allow edit for the pessimistic locking context

PessimisticLocker, When unlocking new object
» should do nothing

PessimisticLocker, When locking new object
» should do nothing

PessimisticLocker, When new object locking state is queried
» should return unlocked

and in this file you can see the implementation of them

https://github.com/expand/eXpand/blob/master/Xpand/Xpand.Tests/Xpand.Tests/Xpand.ExpressApp/PessimisticLockingSpecs.cs

As you see from the specifications when an object is locked the detailview will be read only for a PessimisticLocking context. But real world is strange we have to cover exceptions as well. What will happen if an object was locked and our application was terminated abnormally? We need an action that will force unlock the object.

image

 

Maybe there is a need for some timeout implementation there but I do not have strong ideas on this, better wait for some feedback from out there first before spending any more resources. Anyway we are very close now. What we are missing is a message that will tell which user has locked an object when it is locked.

 

To display the message I think we can utilize our AdditionalViewControlsProvider module. That module allows to conditionally display (When our LockingUser field is different for the current user) a message. Also allows us to conditionalize/localize the message it self. Lets see how

 

First step will be to use the AdditionalViewControlsRule to display the message as bellow

 

    [PessimisticLocking]

    [Custom("ViewEditMode","View")]

    [AdditionalViewControlsRule("teee", "LockedUser!='@CurrentUserID' AND LockedUser Is Not Null", "1=0", "Record is locked by user {0}", Position.Top, MessageProperty = "LockedUserMessage")]

    public class Client : BaseObject {

        public Client(Session session)

            : base(session) {

        }

        private string _lockedUserMessage;

        [NonPersistent][Browsable(false)]

        public string LockedUserMessage {

            get {

                var memberValue = GetMemberValue("LockedUser");

                if (_lockedUserMessage != null) {

                    return memberValue != null ? string.Format(_lockedUserMessage, memberValue) : null;

                }

                return null;

            }

            set { _lockedUserMessage = value; }

        }

    }

that will create a rule at our model like the following.

image

and will display the message

image

2nd step is to refactor the attribute to something easier to use like

    public class PessimisticLockingMessageAttribute : AdditionalViewControlsRuleAttribute {

        public PessimisticLockingMessageAttribute(string id)

            : base(id, "LockedUser!='@CurrentUserID' AND LockedUser Is Not Null", "1=0", "Record is locked by user {0}", Position.Top) {

            MessageProperty = "LockedUserMessage";

        }

    }

 

and 3rd step is to refactor the LockedUserMessage property. We have seen already that dynamically adding a property is a piece of cake, but how can we dynamically add a property that has behavior such as the LockedUserMessage property?

Easy as always :), we just have to create our Custom memberinfo like

    public class LockedUserMessageXpMemberInfo : XPCustomMemberInfo {

        string _theValue;

 

        public LockedUserMessageXpMemberInfo(XPClassInfo owner)

            : base(owner, "LockedUserMessage", typeof(string), null, true, false) {

        }

        public override object GetValue(object theObject) {

            var typeInfo = XafTypesInfo.Instance.FindTypeInfo(theObject.GetType());

            var memberValue = typeInfo.FindMember("LockedUser").GetValue(theObject);

            if (_theValue != null) {

                return memberValue != null ? string.Format(_theValue, memberValue) : null;

            }

            return null;

        }

        public override void SetValue(object theObject, object theValue) {

            _theValue = theValue as string;

            base.SetValue(theObject, theValue);

        }

    }

 

and register it on the system.

        public override void CustomizeTypesInfo(DevExpress.ExpressApp.DC.ITypesInfo typesInfo) {

            base.CustomizeTypesInfo(typesInfo);

            var typeInfos = typesInfo.PersistentTypes.Where(info => info.FindAttribute<PessimisticLockingMessageAttribute>() != null);

            foreach (var typeInfo in typeInfos) {

                var memberInfo = typeInfo.FindMember("LockedUserMessage");

                if (memberInfo == null) {

                    var xpClassInfo = XafTypesInfo.XpoTypeInfoSource.XPDictionary.GetClassInfo(typeInfo.Type);

                    var lockedUserMessageXpMemberInfo = new LockedUserMessageXpMemberInfo(xpClassInfo);

                    lockedUserMessageXpMemberInfo.AddAttribute(new BrowsableAttribute(false));

                    XafTypesInfo.Instance.RefreshInfo(typeInfo);

                }

            }

        }

 

Conclusion

That was a long post but the result i believe is great. All the above are implemented in eXpand framework. Next time you want to use the pessimistic lock approach presented in this blog you only have to decorate your class with 3 attributes

    [PessimisticLocking]

    [Custom("ViewEditMode","View")]

    [PessimisticLockingMessageAttribute("AnId")]

    public class Client : BaseObject {

        public Client(Session session)

            : base(session) {

        }

        private string _name;

        public string Name {

            get {

                return _name;

            }

            set {

                SetPropertyValue("Name", ref _name, value);

            }

        }

    }

 

Download expand from http://expandframework.com/downloads/download.html and sent use your feedback or report any problems you find at our forums http://expandframework.com/forum.html

eXpand FeatureCenter implementation contains a demo of this bolg under the Miscallenous/Pessimistic locking navigation menu

DiggIt!

Monday, January 24, 2011

Easy Sequential numbers

Where they can be used?? In many places sequential for example invoices, sales orders etc.

One could tell that this is an easy task and maybe that assumption is right but let me write down some real world requirements that can prove the opposite to you.

  1. Invoices Number Uniqueness should be quarantine in a multi user environment
  2. Number number should be sequential
  3. Invoices numbers may have series (eg AB-1001 for invoices coming from store sales , EP-1001 for invoices coming from patient treatment)
  4. Starting number must be configurable. (eg need to start numbering at 5000 this year, and next year start at 7000)
  5. There should be a way to reuse numbers of deleted invoices
  6. Storage (table schema) of invoice numbers should be controlled by the user
  7. End user API should be very flexible and easy
  8. Of course whatever you build has to be available in both win and web platforms

Now what you think? How much time do you need to build that?

If you have some XAF experience it will be about only some hrs work to create a reusable code that fellow devs and future projects can utilize. So in this post i am going to go through the process step by step

Step1: Check Devexpress support center

SC has samples for thousand of cases so before building anything it is most advisable to check there.

Searching revealed this one
How to generate and assign a sequential number for a business object within a database transaction, while being a part of a successful saving process (XAF)

That sample is using ExcplicitUnitOfWork (in other words explicit sql transaction) and that can guarantee requirement 1,2

Step2: Refactor the sample to fit your needs

Although the sample can be used as is in real world, has some requirements that are making it less flexible. It requires to inherit from a special BasePersistentObject class.

To overcome that we can introduce an interface instead and push all depended code to our SequenceGenerator class

    public interface ISupportSequenceObject {

        long Sequence { get; set; }

    }

image

Now we can inherit from any object as you see in the above code and we only have to write the code that is inside the OnSaving method! .

 

The best part is that we do not even spent one minute to think about the validity of the generating seq numbers code. That is Devexpress job and they are best in doing it. What we could do is just track the issue for changes (maybe bugs will raise from other users in future )

I am happy with this refactoring so lets move on to the other requirements.
image

Time spent 1.5hrs 

 

Invoices numbers may have series

How about it?? Should be very easy if we understand what are SequenceGenerator does. It saves a number for a specific type in the database. So instead of a specific type we can refactor it to save a specific type and a series string. We can do that by introducing a new Prefix property to our  ISupportSequenceObject interface and refactor our SequenceGenerator to save that prefix as well .

 

    public interface ISupportSequenceObject {

        long Sequence { get; set; }

        string Prefix { get; }

    }

 

our previous front end API was left as simple as it was b4 as you see bellow

 image

Starting number must be configurable. (eg need to start numbering at 5000 this year, and next year start at 7000)

Ok that sounds very similar to our previous req. In essence the year is just a serie, so we could just change our class to

        string ISupportSequenceObject.Prefix {

            get { return Serie.ToString()+DateTime.Today.Year; }

        }

and now our numbers are unique per serier+year!. And how can we control the starting number?

Very easy as everything in XAF!. Sequence numbers are saved using a persistent object, so the thing we need to do is just create a new Sequence number manual and the other numbers will follow since they are sequential.

 

            var unitOfWork = new UnitOfWork();

            var sequenceObjects = Enum.GetValues(typeof (SerieEnum)).Cast<SerieEnum>().Select(

                serie => SequenceGenerator.CreateSequenceObject(serie.ToString() + 2012, unitOfWork));

            foreach (var sequenceObject in sequenceObjects) {

                sequenceObject.NextSequence = 7000;

            }

            unitOfWork.CommitChanges();

Time spent 30min 

There should be a way to reuse numbers of deleted invoices

 

What does that mean? When an object that supports sequences (ISupportSequence object) is deleted we need to store the deleted sequence number and allow end user through UI to reuse it at a later time.

 

We already have an SequenceObject that stores the sequence number what we miss is an one to many relation with an object that stores deleted numbers. The following class will do the job

 

    public class SequenceReleasedObject : XpandBaseCustomObject {

        public SequenceReleasedObject(Session session)

            : base(session) {

        }

        private SequenceObject _sequenceObject;

        public SequenceObject SequenceObject {

            get {

                return _sequenceObject;

            }

            set {

                SetPropertyValue("SequenceObject", ref _sequenceObject, value);

            }

        }

        private long _sequence;

        public long Sequence {

            get {

                return _sequence;

            }

            set {

                SetPropertyValue("Sequence", ref _sequence, value);

            }

        }

    }

 

Having the storage object we then need to grad the deleted ISupportSequence object and create a new SequenceReleasedObject

 

    public class Invoice : BaseObject, ISupportSequenceObject {

        public Invoice(Session session) : base(session) {

        }

        protected override void OnDeleted() {

            base.OnDeleted();

            SequenceGenerator.ReleaseSequence(this);

        }

 

Now that we have our data stored in the database, we are going to use XAF to allow user to restore a deleted number.

 

I am thinking of a control (PropertyEditor in XAF terms) that could be used to render the sequence property of the ISupportSequence object. Also that control should have a button to the right, that on click is going to display a SequenceReleasedObject list . User will select something from the list and when the transaction is committed the SequenceReleasedObject value will replace ISupportSequenceObject value and will be deleted.

 

Sounds hard to you? In fact it is not so much. You have to remember to use the tools that XAF provides for you. Here is what I mean . XAF already has a similar property editor to render aggregated object properties

image

When editor's button is clicked then a detailview of the aggregated object is shown. We could just use the same editor and replace that part. Instead of displaying a detailview we could just display a listview of SequenceReleaseObjects. Exactly the same process we could follow for the web platform.

 

Now we have our property editors, but lets make dev job even more easier. Lets create a platform independent marker attribute that will handle the property editor type assignment.

 

First we create a marker attribute like

    [AttributeUsage(AttributeTargets.Property)]

    public class SequencePropertyAttribute : Attribute {

    }

and a marker interface that will be implemented by both our editors

    public interface IReleasedSequencePropertyEditor {

    }

 

write a simple controller that will do the assignment as

    public class CustomAttibutesController : WindowController {

        public override void CustomizeTypesInfo(ITypesInfo typesInfo) {

            base.CustomizeTypesInfo(typesInfo);

            var memberInfos = typesInfo.PersistentTypes.SelectMany(info => info.OwnMembers);

            foreach (var memberInfo in memberInfos) {

                HandleSequencePropertyAttribute(memberInfo);

            }

        }

 

        void HandleSequencePropertyAttribute(IMemberInfo memberInfo) {

            var sequencePropertyAttribute = memberInfo.FindAttribute<SequencePropertyAttribute>();

            if (sequencePropertyAttribute != null) {

                var typeInfo = ReflectionHelper.FindTypeDescendants(XafTypesInfo.Instance.FindTypeInfo(typeof(IReleasedSequencePropertyEditor))).Single();

                memberInfo.AddAttribute(new CustomAttribute("PropertyEditorType", typeInfo.FullName));

            }

        }

    }

and of course decorate our property

 

private long _sequence;

        [SequenceProperty]

        public long Sequence {

            get {

                return _sequence;

            }

            set {

                SetPropertyValue("Sequence", ref _sequence, value);

            }

        }

Time spent 3hr

Storage (table schema) of invoice numbers should be controlled by the user

Does the above remind you anything? It sure does to me. Reminds me the exact same problem we have with the Devexpress support center sample . It was not based on interfaces so if we extract an interface from our Sequence class (the one that store the numbers in the db) like

    public interface ISequenceObject {

        string TypeName { get; set; }

        long NextSequence { get; set; }

    }

and refactor again our SequenceGenerator to replace our references to SequenceObject persistent class with the new interface. Now the end user can control the schema of the table cause the only thing he has to do is implement the ISequenceObject to any object he wants.

 

Time spent 1hr

 

Conclusion

We have spent almost 6 hrs to implement all requirements but nothing was in vain. Cause the effort produce something very reusable. Bellow is our final, very easy to use and flexible approach.

    public class Invoice : BaseObject, ISupportSequenceObject {

        public Invoice(Session session) : base(session) {

        }

        protected override void OnDeleting() {

            base.OnDeleting();

            SequenceGenerator.ReleaseSequence(this);

        }

 

        protected override void OnSaving() {

            base.OnSaving();

            if (Session.IsNewObject(this))

                SequenceGenerator.GenerateSequence(this);

 

        }

 

 

        private long _sequence;

        [SequenceProperty]

        public long Sequence {

            get {

                return _sequence;

            }

            set {

                SetPropertyValue("Sequence", ref _sequence, value);

            }

        }

        private SerieEnum _serie;

        public SerieEnum Serie {

            get {

                return _serie;

            }

            set {

                SetPropertyValue("Serie", ref _serie, value);

            }

        }

 

        string ISupportSequenceObject.Prefix {

            get { return Serie.ToString()+DateTime.Today.Year; }

        }

    }

 

that is the power of XAF! everything else should live in a reusable framework, like eXpand !

 

PS: If you have more ideas that can enhance the current approach or eXpandFramework in general please drop us a mail or post to expand forums http://expandframework.com/forum.html

 

You can find a working example of this blog at expand featurecenter application. Download it from http://expandframework.com/downloads/download.html

DiggIt!

Tuesday, November 16, 2010

Converting Lambda expressions at runtime

DevExpress XPO as all modern ORM supports object querying using linq and lamda expressions. A sample query can be written as

            var xpQuery = new XPQuery<Customer>(Session.DefaultSession);

            IQueryable<Customer> customers = xpQuery.Where(customer => customer.Age == 25);

 

From the above query we can get a list of customers that are 25  years old . As you can see the code is very simple.

The problem 
When working with modularized applications you may be in context that you have access to the Customer type  only at runtime or you may want to apply that query to different object types that all have an Age property. How that can be done?

1st attempt

Lets register the customer to our module and use reflection to create the XPQuery.

            ModuleXXX moduleXxx = new ModuleXXX();

            moduleXxx.DoQuery(typeof (Customer));

 

and the DoQuery method will be something like

 

        public void DoQuery(Type customerType) {

            Type makeGenericType = typeof (XPQuery<>).MakeGenericType(customerType);

            object xpQuery = Activator.CreateInstance(makeGenericType, new object[] {Session.DefaultSession});

            //THIS IS A DEAD END

            //although we can construct the XPQuery object we have to stop here cause there is no way to use it since we not have the type at design time

        }

 2nd Attempt

Refactor our customer using an ISupportAge interface that will live in our module and use that interface with the DoQuery method

    public interface ISupportAge {

        int Age { get; set; }

    }

 

    public class Customer : BaseObject, ISupportAge {

        public Customer(Session session) : base(session) {

        }

        private int _age;

        public int Age {

            get {

                return _age;

            }

            set {

                SetPropertyValue("Age", ref _age, value);

            }

        }

    }

 

Now the DoQuery will have no arguments

 

            ModuleXXX moduleXxx = new ModuleXXX();

            moduleXxx.DoQuery();

 

and will look like

 

        public IQueryable<ISupportAge> DoQuery() {

            var xpQuery = new XPQuery<ISupportAge>(Session.DefaultSession);

            return xpQuery.Where(customer => customer.Age == 25);

        }

 

as you see in the code above in order to be able to use lamda expressions i have passed the ISupportAge interface in my XPQuery method. Too bad that XPQuery supports as generic parameters only persistent types and not interfaces

 

3rd Attempt

The previous attempt was pretty closed to what we wanted. Brainstorming and researching a little more we can find that lambda expressions are fully decomposable. That means that they consist of parts like body,parameters,sub expressions that can be decomposed and recomposed again. After decomposing the tree using Xpand.Utils.Linq.ExpressionConverter the DoQuery method now will  look similar to

 

        public IQueryable<ISupportAge> DoQuery(Type customerType) {

            //here we create a lambda based on the ISupportAge interface

            Expression<Func<ISupportAge, bool>> expression = date => date.Age == 25;

 

            //Convert the lambda using the customerType argument the new expression will be of type Expression<Func<Customer, bool>>

            //but since our method knows nothing about customer we have used the abstract Expression type.

            Expression convert = new ExpressionConverter().Convert(customerType, expression);

 

            //create the Xpquery instance

            Type xpQueryGenericType = typeof(XPQuery<>).MakeGenericType(new[] { customerType });

            object xpquery = Activator.CreateInstance(xpQueryGenericType, new[] { Session.DefaultSession });

 

            //find the where method

            MethodInfo mi = typeof(Queryable).GetMethods().Where(info => info.Name=="Where").FirstOrDefault();

            mi = mi.MakeGenericMethod(customerType);

 

            //execute the where but now using the converted lambda

            return ((IEnumerable)mi.Invoke(xpquery, new[] { xpquery, convert })).OfType<ISupportAge>().AsQueryable();

        }

 

You can find the ExpressionConverter at eXpand source code avalibale at http://expandframework.com/download.html
DiggIt!

Monday, September 13, 2010

Non constant attributes parameters?

Given my previous post

Simple maths can boost your app performance

did some more thoughts over it in this post 

.Net compiler says no!!

An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type   

public class CustomAttribute:Attribute {

    public CriteriaOperator CriteriaOperator { get; set; }

 

    public CustomAttribute(CriteriaOperator criteriaOperator) {

        CriteriaOperator = criteriaOperator;

    }

}

[CustomAttribute(CriteriaOperator.Parse("Name=?","eXpand"))]

public class Class1 {

 

}

the above cannot be compiled

BUT XAF SAYS YES

Why? Cause it has its own type system the XafTypesInfo system. And the types are added it to it at runtime. Meaning? We can do anything!!!

Take for example the following implementation of the above

core classes

public class MyController:ViewController {

    public override void CustomizeTypesInfo(ITypesInfo typesInfo)

    {

        base.CustomizeTypesInfo(typesInfo);

        var typeInfos = XafTypesInfo.Instance.FindTypeInfo(typeof(AttributeRegistrator)).Descendants.Where(info => !(info.IsAbstract));

        foreach (var typeInfo in typeInfos) {

            var logicRegistrator = (AttributeRegistrator)ReflectionHelper.CreateObject(typeInfo.Type);

            foreach (var attribute in logicRegistrator.GetAttributes(typeInfo)) {

                typeInfo.AddAttribute(attribute);

            }

        }

    }

}

 

public abstract class AttributeRegistrator {

    public abstract IEnumerable<Attribute> GetAttributes(ITypeInfo typesInfo);

}

implemention

public class CustomAttributeRegistrator:AttributeRegistrator {

    public override IEnumerable<Attribute> GetAttributes(ITypeInfo typesInfo) {

        if (typesInfo==typeof(Class1))

            yield return new CustomAttribute(CriteriaOperator.Parse("Name=?", "eXpand"))

    }

}

Cause modern programming is based on attributes the AttributeRegistrator example is most usefull when for example multiple developers wan to to work on the same class, the example was stolen from eXpand featurecenter application were that case is valid :) (Many developers work on the same file)

DiggIt!

Monday, September 6, 2010

Simple maths can boost your app performance

Xaf provides methods in your controllers to enable you to customize the types that is using. I am speaking about the CustomizeTypesInfo method

internal class MyClass:ViewController {

    public override void CustomizeTypesInfo(DevExpress.ExpressApp.DC.ITypesInfo typesInfo)

    {

        base.CustomizeTypesInfo(typesInfo);

    }

}

The thing is that using such decoupled approach can be very dangerous and resource consuming.

Take the following example .

Say you have 10 classes and you want to customize 2 of them . You also want to decouple the customization logic.

Then you probably write one controller like

internal class MyController1:ViewController {

    public override void CustomizeTypesInfo(DevExpress.ExpressApp.DC.ITypesInfo typesInfo)

    {

        base.CustomizeTypesInfo(typesInfo);

        var type = typeof(Class1);

        foreach (var typeInfo in typesInfo.PersistentTypes.Where(info => info.Type==type)) {

            typeInfo.AddAttribute(new SomeAttribute());

        }

    }

}

and another one like

internal class MyController2:ViewController {

    public override void CustomizeTypesInfo(DevExpress.ExpressApp.DC.ITypesInfo typesInfo)

    {

        base.CustomizeTypesInfo(typesInfo);

        var type = typeof(Class2);

        foreach (var typeInfo in typesInfo.PersistentTypes.Where(info => info.Type==type)) {

            typeInfo.CreateMember("SomeName", typeof (SomeType));

        }

    }

}

the decoupling you wanted has as result to force the CLR to enumerate your typesInfo.PersistentTypes twice in order to apply your logic. And if you put that in real world your classes may be 500 and your logics 1000 that means 500x500 to apply 1000 logics!!! Its a big number and can slow you down for sure

Luckily that Xaf architecture can help us once more here!!! And to those that guess it already i am again speaking about the powerfull XafTypesInfo system.

So how to design the above to get max performance?

1st create 2abstract classes like

public abstract class LogicRegistrator {

    protected abstract void ExecuteCore(ITypeInfo typeInfo);

    public abstract void Execute(ITypeInfo typeInfo);

 

}

public abstract class LogicRegistrator<T> : LogicRegistrator

{

    public override void Execute(ITypeInfo typeInfo)

    {

        if (typeInfo.Type == typeof(T))

            ExecuteCore(typeInfo);

    }

}

then create 2 more classes for implementing your logics

public class Behaviour1:LogicRegistrator<Class1> {

    protected override void ExecuteCore(ITypeInfo typeInfo) {

        typeInfo.AddAttribute(new SomeAttribute());

    }

}

public class Behaviour2:LogicRegistrator<Class2> {

    protected override void ExecuteCore(ITypeInfo typeInfo) {

        typeInfo.CreateMember("SomeName", typeof(SomeType));

    }

}

and final step one controller that can handle all logics.

internal class MyController:ViewController {

    public override void CustomizeTypesInfo(DevExpress.ExpressApp.DC.ITypesInfo typesInfo)

    {

        base.CustomizeTypesInfo(typesInfo);

        var typeInfos = XafTypesInfo.Instance.FindTypeInfo(typeof(LogicRegistrator)).Descendants.Where(info => !(info.IsAbstract));

        foreach (var typeInfo in typeInfos) {

            var logicRegistrator = (LogicRegistrator) ReflectionHelper.CreateObject(typeInfo.Type);

            logicRegistrator.Execute(typeInfo);

        }

    }

}

That way your logic still remain decoupled at LogicRegistrator descenants and you not have to enumarate all persistent classes for each one of them.

ps: I did not calculate how much faster that would be cause the post was about simple maths :)

To be continued….

DiggIt!