Extended User Data - Might have a better way

rated by 0 users
This post has 9 Replies | 3 Followers

Top 500 Contributor
Male
Posts 84
Points 1,360
Jon_ProactiveLogic Posted: Fri, May 23 2008 8:30 AM

Hi All,

I wrote an article a while ago about how I hacked and slashed my way through the CS SDK to extend user data and store that new data in columns in the cs_UserProfile table in CS here:

http://proactivelogic.com/blog/index.php/2007/11/03/extending-community-server-2007-user-profiles-and-vista-setup/

I have been relooking at this approach and I have an idea on a better way to deal with the data layer, that would allow you to NOT update the core CS code. I need to test this out, but I don't see why it would not work yet Cool  I'm posting this up for some early brainstorming feedback.

The pros are: No CS core code updates, enables enlisting in transactions with CS data updates and your own custom data updates
The cons are: Round trips to the database for any call with extended data, longer locks on data

Looking at the pros and cons, I think this plan is worth it, and the perf hit most likely going to be minimal.

Here is my plan that I will code up and test sometime this weekend:

I noticed that since CS is using a provider model for the data, we can plugin our own provider:

        <add
            name = "ProactiveLogicCommonDataProvider"
            type = "ProactiveLogic.CS.Data.PLSqlCommonDataProvider, ProactiveLogic.PLSQLCommonDataProvider"
            connectionStringName = "SiteSqlServer"    databaseOwnerStringName = "SiteSqlServerOwner"
            />

Since I want to make use of the existing CS SQL code already, and only tie in where needed, derivation seems to be the right implementation choice:

public class PLSQLCommonDataProvider: CommunityServer.Data.SqlCommonDataProvider
{

        public PLSQLCommonDataProvider(string databaseOwner, string connectionString) : base ( databaseOwner,  connectionString)
        {
        }

}

Then in order to enlist CS and our code in the same transaction, the Transaction Scope class could be used:

    public class PLSQLCommonDataProvider: CommunityServer.Data.SqlCommonDataProvider
    {
        public PLSQLCommonDataProvider(string databaseOwner, string connectionString) : base ( databaseOwner,  connectionString)
        {
        }


        public override CommunityServer.Components.User CreateUpdateDeleteUser(CommunityServer.Components.User user, CommunityServer.Components.DataProviderAction action, bool createLocalUserOnly, out CommunityServer.Components.CreateUserStatus status)
        {
            CommunityServer.Components.User retUser = user;

            // implicitly enlist CS and my code in the same transaction
            using (System.Transactions.TransactionScope trans = new System.Transactions.TransactionScope())
            {
                base.CreateUpdateDeleteUser(user, action, createLocalUserOnly, out status);   
               
                string someKey = user.GetExtendedAttribute("SomeDataKey");
                // use extended attributes passed from the UI and call your own SQL code here
                // would need some error handling and updating of the output status
                trans.Complete();
            }
            return retUser;
        }
       
    }

For me this would enable binary reuse of CS, enable extended data to be stored in new columns, and allow that data to be queried efficiently for searches and other lookups on this extended data.

Any thoughts?

Cheers,
Jon

 

Check out my my site where I integrated CS:  Vast Rank http://www.vastrank.com (College Rankings by the People)

  • | Post Points: 20
Top 150 Contributor
Posts 166
Points 4,555

That's one of the technique's we use at Four Roads it allows for extension of the product without changing it's code line.

We've taken this a stage further with some of projects to allow for unit testing and mocking, I'll be Blogging about that some time soon on our site.

Rob

Four Roads

  • | Post Points: 20
Top 500 Contributor
Male
Posts 84
Points 1,360

Very cool Rob.  Looks like you have some nice niche products for CS too, congrats.

Jon

 

Check out my my site where I integrated CS:  Vast Rank http://www.vastrank.com (College Rankings by the People)

  • | Post Points: 5
Top 500 Contributor
Male
Posts 84
Points 1,360

OK, for anyone who stumbles upon this thread - I finally tried the technique I proposed above, and it works GREAT!

I am now able to tie in my own data layer updates in the same transaction as CS data updates without touching ANY CS SDK code at all.

I tested the transactionality by putting a breakpoint in my code after the CS calls update the DB and before a hardcoded "throw exception" I put in to test the transaction.  I could see the CS updates in the db by doing a dirty read (in sql query with nolock).  Then I let my code throw an exception and the CS updates were rolled back.  I then tested updating my own data using Linq to SQL within the same transaction scope, and all of the data updated successfully upon the call to commit.


In addition, I added a "Nice to Have" to my code base and extended the CS User using C# 3.0 extension methods to provide strongly typed access to the CS User extended attributes.  Here is an example of my helper class.  It would have been a touch nicer if C# 3.0 allowed for extension properties.

    /// </summary>
    public static class CSUserHelper
    {
        public static int GetStoreAffiliatedToId(this CommunityServer.Components.User CSUser)
        {
            return int.Parse(CSUser.GetExtendedAttribute(CSUserExtendedAttributes.StoreAffiliatedToId));
        }

        public static void SetStoreAffiliatedToId(this CommunityServer.Components.User CSUser, int StoreAffiliatedToId)
        {
            CSUser.SetExtendedAttribute(CSUserExtendedAttributes.StoreAffiliatedToId, StoreAffiliatedToId.ToString());
        }
    }


    public class CSUserExtendedAttributes
    {
        /// <summary>
        /// Store that the user is tied to
        /// </summary>
        public const string StoreAffiliatedToId = "StoreAffiliatedToId";
        
    }


What this would allow for is some pretty cool syntax in the data layer for example:

public override CommunityServer.Components.User CreateUpdateDeleteUser(CommunityServer.Components.User user, CommunityServer.Components.DataProviderAction action, bool createLocalUserOnly, out CommunityServer.Components.CreateUserStatus status)
{

    Debug.WriteLine(user.GetStoreAffiliatedToId());


This technique of extending CS was the missing peice for me, and now I think I have it nailed down on how to use CS through strictly binary reuse!

Cheers,
Jon

Check out my my site where I integrated CS:  Vast Rank http://www.vastrank.com (College Rankings by the People)

  • | Post Points: 20
Not Ranked
Posts 2
Points 40
Viking replied on Wed, Jul 16 2008 8:01 PM

Were you able to get a user search against the new columns working with this approach?

  • | Post Points: 20
Top 500 Contributor
Male
Posts 84
Points 1,360

I'm going to be digging into the search aspect very soon.  I will report back to let you know how it goes.

Cheers,

Jon

Check out my my site where I integrated CS:  Vast Rank http://www.vastrank.com (College Rankings by the People)

  • | Post Points: 20
Not Ranked
Posts 2
Points 40
Viking replied on Thu, Jul 17 2008 11:12 AM

Awesome.  Thanks!

  • | Post Points: 20
Top 500 Contributor
Male
Posts 84
Points 1,360

I did a 5 minute debug session and it looks like one way would be to override this method:

        public override UserSet GetUsers(UserQuery query, bool returnModerationCounters )

And copy the sdk implementation into your own code - and tweak the line that does this:

myCommand.Parameters.Add("@sqlPopulate", SqlDbType.NText).Value = SqlGenerator.BuildMemberQuery(query, databaseOwner);

It seems like it is doable and NOT too painful (but not as elegant as the other overrides since you need to copy sdk code over to your own code). When I actuially implement it I will report back.

Thanks,

Jon

Check out my my site where I integrated CS:  Vast Rank http://www.vastrank.com (College Rankings by the People)

  • | Post Points: 5
Top 500 Contributor
Male
Posts 84
Points 1,360

Hi,

Check out this thread: http://dev.communityserver.com/forums/t/499348.aspx

I just added the way I got search to work on my extended user data, in custom tables.

Cheers,

Jon

Check out my my site where I integrated CS:  Vast Rank http://www.vastrank.com (College Rankings by the People)

  • | Post Points: 5
Top 500 Contributor
Male
Posts 84
Points 1,360

Hi,

Just to chime back in on this extensibility approach - it has worked great:  I used it on http://www.vastrank.com/ which I launched today.

If you go to "people" you can search on these extended attributes (college and college affiliation) and see the data in the users profiles.

All of that data is stored in a completely different tables than the CS data.

HTH,
Jon

Check out my my site where I integrated CS:  Vast Rank http://www.vastrank.com (College Rankings by the People)

  • | Post Points: 5
Page 1 of 1 (10 items) | RSS
Powered by Community Server (Commercial Edition), by Telligent Systems

Copyright© 2008 Telligent Systems Inc. All rights reserved
CommunityServer.com  •  Telligent.com