On this page

Obtaining the connection string for a site in sps - Complete Guide
Memory leaks using SPS object model.
Does following Microsoft guidelines(or any guidelines..), is Always a good idea ?

Ads

Navigation

Search

Categories

Clouds

Sql Server (5) .Net (16) .Net 2.0 (2) C# (3) @ff Topic (5) Architectural solutions (9) ASP (1) BDD (5) Blog related (8) database (2) Development process (8) Facebook (1) job interviews (1) Lessons (5) Life (12) Microsoft (5) IIS 6 (2) SPS (sharepoint server) (3) Drivers (1) Internet Explorer (2) Windows 2003 server (1) NightDuck (2) Performance (5) Security (9) Sql Server 2000 (4) Study (2) TDD (1) Threading (3) Under the hood (1) Web (1) Web services (1) XSS (6)

Archive

Blogroll

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 63
This Year: 0
This Month: 0
This Week: 0
Comments: 33

Sign In
Pick a theme:

 Wednesday, October 11, 2006
Wednesday, October 11, 2006 5:45:58 PM (GMT Standard Time, UTC+00:00) ( Sql Server  | .Net | Lessons | SPS (sharepoint server) | Performance )

As you know, Microsoft didn't intended that you access the SPS database, but via object model only.
the problem that it has many bugs, performance issues, security issues and lots lots problems that will make the programmer's life a living hell.

So, lets see how can we break the 1st guideline of SPS programming - "do not use the SPS database directly".
man , I'm feeling like a criminal now, presenting a guide how to do something that Microsoft invested a lot of effort to prevent us from doing.

Lets get down to business,
don't count that the connection string is laying in some property, this one we need to do some dirty work.

A little background how we are going to do it:

The connection string looks like this :

"Integrated Security=SSPI;Server=someServer;database=SomeDatabase"

as you can see, the only thing that can change here is the server name and the database name.

lets create a core function that will receive DB collection and the desired site guid and construct the connection string.

private string GetConnectionStringForSite(SPContentDatabaseCollection DBs, Guid siteGuid)
{
    string rc = "";
    SPContentDatabase oDB = null;

    for(int i=0; i<DBs.Count;i++)
    {
        // Get the database
        oDB = DBs[i];

        SqlCommand c = new SqlCommand();
        string strConn = "Integrated Security=SSPI;Server=" + oDB.Server + ";database=" + oDB.Name;

        using(SqlConnection conn = new SqlConnection(strConn))
        {
            // Set the Connection
            c.Connection = conn;
            try
            {
                // Open the connection
                conn.Open();

                c.CommandText = "SELECT FullUrl FROM Sites where Id=@Id";

                // Set the parameter
                c.Parameters.Add(new SqlParameter("@Id",System.Data.SqlDbType.UniqueIdentifier));
                c.Parameters["@Id"].Value = siteGuid;

                // Execute reader
                SqlDataReader reader = c.ExecuteReader(System.Data.CommandBehavior.SingleRow);

                // check if we have rows
                bool hasRows = reader.HasRows;

                // Close the connection and the reader
                reader.Close();
                conn.Close();

                // Check if it got rows
                if(hasRows)
                {
                    // Set the return value
                    rs = strConn;

                    // Stop iterating through the DB's
                    break;
                }

                // Close the reader
                reader.Close();
            }
            finally
            {
                // Close the connection
                if(conn.State != System.Data.ConnectionState.Closed) conn.Close();
                oDB = null;
            }
        }
    }
    return rc;
}

 

this function will be placed in a dedicated class, i call is "SPSDB"

lets create the class itself and its private members

    public class SPSDB
    {
        private string _conString = "";
        private string _vsUrl = "";

        public String URL
        {
            get{return _vsURL;}
        }
    }

now we need to write the constructor and the connection "factory"

the constructor will look like this :

public SPSDB(string siteUrl)
{
    using(Microsoft.SharePoint.Administration.SPGlobalAdmin ga = new Microsoft.SharePoint.Administration.SPGlobalAdmin())
    {
        Microsoft.SharePoint.Administration.SPVirtualServerCollection VSc = ga.VirtualServers;

        SPVirtualServer vs = null;
        Guid g;
        bool isFound = false;
        
        // Run through all the VS collection
        for(int i=0; i<VSc.Count;i++)
        {
            // Get the virtual server reference
            vs = VSc[i];

            // if the current vs is not from the needed state than continue to the next VS
            if(vs.state != SPVirtualServerState.Ready)
                continue;

            // Run through all the sites in the vs
            for(int j=0; j< vs.Sites.Count;j++)
            {
                using(SPSite s = vs.Sites[j])
                {
                    // Check if we found the needed vs
                    if(s.Url.ToLower() == siteUrl.ToLower())
                    {
                        // Save the needed data
                        isFound = true;
                        g = s.ID;
                        _vsURL = vs.Url.ToString();
                        _conString = GetConnectionStringForSite(vs.ContentDatabases,g);
                        break;
                    }
                }
            }

            // Check if the vs has been found
            if(curVs != null)
                break;

            // Clear the virtual server object
            vs = null;

            // Clear memory
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        // Throw an exception if the site guid is not there
        if(!isFound)
            throw new Exception("the given site url cannot be found");
        
    }
}

and the factory method will look like this :

public SqlConnection GetCon()
{
    return new SqlConnection(_conString);
}

thats it!
now to use it you can do it like this :

SPSDB dbstuff = new SPSDB("http://www.krokhmal.com");

using(SqlConnection con = dbstuff.GetCon())
{
    // Your DB code here ...
}

I've added a file attachment to this post with the mentioned class.
enjoy.

SPSDB.rar (1.34 KB)
Comments [0] | | # 
 Thursday, October 05, 2006
Thursday, October 05, 2006 5:53:17 PM (GMT Standard Time, UTC+00:00) ( .Net | SPS (sharepoint server) | Performance )

Recently i have given a task to build a tool for managing file versions in share point.
for some architectural decisions and performance issues, I've decided to do the aggregations directly through the share-point database (yes, even if is against Microsoft's guidelines).

Considering the fact that Microsoft invested a lot of afford so you wont find where each SPsite is located (on which DB) , one of the things i had to do is to iterate through all the sites in SPsitecollections in every SPVirtualServer in the SPVirtualServerCollection.
Sounds like a lot of "fun".

Well, actually there was problem.
I'll show some bad practices to iterate through that and explain each one

Option 1 :

SPGlobalAdmin globalAdmin = new SPGlobalAdmin();
SPVirtualServerCollection vServers = globalAdmin.VirtualServers;


// Run through the virtual servers
foreach (SPVirtualServer vs in vServers)

   // Run through the sites
   foreach (SPSite site in vs.Sites) 
   { 
      if(site.Url == predefinedUrl)
      {
         // Do some logic ...
      }
   }
}


Option 2 :

SPGlobalAdmin globalAdmin = new SPGlobalAdmin();
SPVirtualServerCollection vServers = globalAdmin.VirtualServers;


// Run through the virtual servers
for(int i=0; i<vServers.Count; i++)

   // Run through the sites
   for(int j=0; j<vServers[i].Sites.Count; j++) 
   { 
      if(vServers[i].Sites[j].Url == predefinedUrl)
      {
         // Do some logic ...
      }
   }
}

Option 3 :

SPGlobalAdmin globalAdmin = new SPGlobalAdmin();
SPVirtualServerCollection vServers = globalAdmin.VirtualServers;


// Run through the virtual servers
for(int i=0; i<vServers.Count; i++)

   SPVirtualServer vs = vServers[i];
   
   // Run through the sites
   for(int j=0; vs.Sites.Count; j++) 
   { 
      SPSite s = vs.Sites[j];
      if(s.Url == predefinedUrl)
      {
         // Do some logic ...
      }
   }
}


So, what's wrong with this practices ?
the problem lays in the way the object model is implemented : Unmanaged code - which means that you must to release memory explicitly.
garbage collector will not handle these object, and you must handle it in your code.

problems in option 1 :

  • implementing this practice (please don't), will cause memory leaks, due to a fact that dispose() method is not being used, and memory is not being released.
  • Exception management - suppose you will call the dispose method of the SPSite object and the end of the foreach loop,
    what will happen if an exception occur ?
    the memory allocated to the object will not be released, and the garbage collector won't release it either, causing memory leaks each time exception takes place.
  • Even if you decide to include some exception management code (try-catch for example), the given object won't be reachable because it lives in it's own scope (the foreach scope).

Problems in option 2 :

  • implementing this practice (just like the 1st option), will cause memory leaks, due to a fact that dispose() method is not being used, and memory is not being released.
  • reading the previews statement probably raise some reasonable question :
    "hey, I'm not creating any new objects, and does not allocating any memory, just accessing some property in a given collection, what is the problem here ?"
    this is a question i asked myself too, so i dug deep through it, and found that the sites in the Sites collection that lays in a virtual server object is not being created until you reference them explicitly - which means : accessing their properties or getting an outer reference to some site through a variable.
    so when accessing a property will cause memory allocation behind the scenes.
  • calling the dispose method like so :

    Sites[i].Dispose();

         will not release the memory.
         maybe its related to the fact that the Sites[i] object is defined as "read-only" .
         if you find the reason for that please let me know.

Problems in option 3 :

  • OK, i must admit, this looks much better than the ones mentioned above, but still lack of some memory releasing.

How can we write it right ?
here is an example of what i consider as a good practice :

Option 4:

// Wrap the globalAdmin object in a using scope - this object is unmanaged
using(SPGlobalAdmin globalAdmin = new SPGlobalAdmin())
{
    // Get the virtual servers collection reference
    SPVirtualServerCollection vServers = globalAdmin.VirtualServers; 

    // Run through the virtual servers
    for(int i=0; i<vServers.Count; i++)
    {
        SPVirtualServer vs = vServers[i];
    
        // Run through the sites
        for(int j=0; vs.Sites.Count; j++)
        {
            // Wrap the SPSite object in a using scope - this object is unmanaged
            using(SPSite s = vs.Sites[j])
            {
                if(s.Url == predefinedUrl)
                {
                    // Do some logic ...
                }
            }
        }

        // Make sure that this objects memory will be cleared by the GC
        vs = null;

        // Call the garbage collector and make sure that the memory is released
        GC.Collect();
        GC.WaitForPendingFinalizers();
    
    }
}


as you can see, unmanaged object is wrapped in a using scope,
and the GC(garbage collector) is being activated manually.

changing from one of the bad practices to this one improved dramatically performance.
for example :
iterating with option number 2 caused the w3wp.exe process (that is the IIS process) to reach 1200 MB and crash (reaching the given limit) after iterating through 2700 sites,
while implementing option 4, i didn't saw any dramatic changes in the process info, and it stayed at 87 MB only and iterated through all the sites, a total of 8876 sites.

Comments [0] | | # 
 Thursday, August 17, 2006
Thursday, August 17, 2006 8:54:25 PM (GMT Standard Time, UTC+00:00) ( .Net | Architectural solutions | SPS (sharepoint server) | Microsoft )

when planning a software solution, one of the most important things is to design the solution the best way it can be,
even before writing the first line of code, we need to know exactly how we should implement it.

so what would you do if you never planned/worked/implemented such type of a solution ?

needless to say that inventing the wheel all over again will be unnecessary ,
first you consult with the "elders" (the more experienced co-workers),
trying maybe google up the solution,
going over the references and the guide lines in developing this kind of a solution or at the given platform,
starting some thinking team and so ...

finally, you came up with a solution, and....
start implementing it.

now, in a perfect world, once you did the steps mentioned above, you have a perfect solution.
but in the real world, something must to go wrong.

recently i came across some interesting example of this phenomena.
while designing a "file Version manager solution" on SPS (Share point Server) platform,
following the SPS development guidelines, which says that you (the developer), should never approach the SPS database.
always, but always get the data through the object model.

so, by doing that, you came up with a working application,but..... catastrophic performance.
after consulting with other staff, we came to the conclusion that the bottleneck is in the object model itself.
so the only thing we had to do is the direct approach to the database ( a big NO NO on the guide lines).
and apparently the performance graph suddenly got a dramatic change (better performance).

i think that guide lines did not meant to become laws, but to be just as a suggestions to most of the cases.
what do you think about this issue ?

Comments [2] | | #