April 2008 - Posts
Some time ago I tried out SQL Server 2008 Reporting Services and I was amazed. I'm serious MS Paint terrorist and nice pictures are not one of my strong skills. So, if something makes things look nice to me I'm very happy.
I downloaded last CTP of SQL Server 2008 and installed it on my Virtual PC that runs Windows Server 2008. There were no problems during installation and after installing I was able to create my sample database. I also installed Reporting Services. During installation the reporting services web site was created on my server.
One thing we got is new Report Designer. It looks like application form Office family, specially user interface. It is easy and user friendly. After setting up data source I was able to start building the reports.
Now comes the most exciting part - I was able to create a nice looking report and it took only 20 minutes to get it done. You can see the result on the following screenshot. Everything was very simple and I was able to create my report fast.
When you look at the picture you may notice exporting features on top of the page. It doesn't work yet but the formats list was very cool. If new SQL Server will be available on market I'm sure it is worth to buy it.
Another day, another solved mystery. Developers have interesting lifes as long as there are problems to solve. Another surprise from SharePoint - how to bog server down using only couple of lines of code? Of course, I have solution too that eliminates the issue.
Let's say you have large document library with many documents (let's say there is a little bit more than 90.000 documents). Some of them are versioned, some of them are new and so on. Now, let's say you have to manipulate documents in your code and you have to find document with specific ID. The document, if it exists, is given to you as SPListItem:
...
SPListItem item = list[new Guid(itemGuidString)];
...
When you try something like this the code may run fast - as long as you have few documents you don't have any performance issues. But if you have about 90.000 documents than retrieveing one document as list item may take about 1:00 or 1:30 minutes with no problems. And good server you are using is not saving you this time.
What you have to do is to ask documents by items's unique ID from SPList.
...
SPListItem item = list.GetItemByUniqueId(new Guid(itemGuidString));
...
If you ask documents this way then documents are retrieved through CAML query and you save on performance a lot.
And another discovery about SharePoint 2007 content deployment. You should be aware of forbidden file types. When making mass import of content then import fails when it finds forbidden file type. All files after first forbidden file will not be imported. I don't know if this blog entry has some value or not but maybe I will save somebody's time about hours or days :)
I just putted up Unity behind my test project and had some troubles with singletons. There is some misleading information in documentation you should be aware of. I created one class and one interface, both in same project to save some time.
namespace mytest
{
public interface ITest
{
string Name { get; set; }
}
public class Test : ITest
{
public string Name { get; set; }
}
}
So, as we can see, it's nothing special. Now let's take application configuration file. You can see tag called lifetime there. Example in manual uses "singleton" as type attribute of this tag. Instead of it, to avoid errors, you should use correct name of the LifetimeManager.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configsections>
<section name="unity"
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration" />
</configsections>
<unity>
<containers>
<container>
<types>
<type type="csharptest.ITest,csharptest"
mapTo="csharptest.Test,csharptest"
>
<lifetime type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,
Microsoft.Practices.Unity" />
</type>
</types>
</container>
</containers>
</unity>
</configuration>
And now, let's see my little test code to test Unity and singleton. Point of this code is easy - we are creating Unity container and ask tqo ITest type objects from it. To make sure they are the same we will compare their hash codes.
namespace mytest
{
class Program
{
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
UnityConfigurationSection section;
section =(UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(container);
ITest test1 = container.Resolve<ITest>();
ITest test2 = container.Resolve<ITest>();
Console.WriteLine(test1.GetHashCode().ToString());
Console.WriteLine(test2.GetHashCode().ToString());
Console.ReadLine();
}
}
}
I hope it helps you!
When exporting and importing data between SharePoint Server 2007 webs I found interesting bug - item creation and last modification dates will be lost during import. Example - document is created at 05.04.2008. After import I checked out data in manifest.xml. Everything seemed to be correct - also modification and creation dates of documents. After export these dates are incorrect. Instead of original dates I can see date and time of export instead of original dates. Dirty guy as I am I also sniffed around in SharePoint database - I saw correct dates there! But when looking my document library through browser I saw incorrect dates. Does anybody know what is the problem? Is it bug and is there any workaround to this problem?
During one SharePoint migration project I had problem with document versions comments. I needed some way to set these in my code. As I didn't found any normal way to do it through SharePoint API I worked out something I call dirty hack. But it works.
The Idea
As API seemed more and more hopeless in my context I checked out what's going on in database and I discovered that I can set those version comments also after importing all the data. My hack affected only two tables in SharePoint database and after solving some mysteries I got everything work as expected.
Database
The first thing we will look at is SharePoint database. We are interested in two tables: AllDocs and AllDocVersions. Version fields are UIVersion in first table and Version in second table. Both tables have field called CheckinComment - this is the fields we have to change.
Make sure you don't edit SharePoint lists or items through browser when using this solution - you may overwrite the changes you made through code.
Version Format
Let's look at the versions for a moment. We don't see nice version numbers like 0.1, 1.0, 2.2 in these tables. Instead we have some integers like 1, 512, 1026. There is little trick how to compute original versions to integers. You have to use the following equation:
version integer = major version * 512 + minor version
Version Class
Next thing is class called Version. I wrote about it blog entry Using Version Class. As Version is sealed class we are not able to extend it. But we can always use extension methods to visually add new methods to classes. Now let's add new extension method for Version class that returns Sharepoint version integer from current version.
public static class VersionExtender
{
public static Int32 GetSharePointDocVersion(this Version ver)
{
return ver.Major * 512 + ver.Minor;
}
}
Changing the comments
Now we are ready to change the comments. As an example I give a method that is very compact so it easier for you to take it and try it out. Of course it is possible to optimize and refactor this method many ways. You are free to do it.
The method takes three arguments: versionString, comment, and guid. versionString is version label of document we want to change, comment - of course - is comment for list item version and guid is GUID of list item.
void SaveComment(string versionString, string comment, Guid guid)
{
Version version = new Version(versionString);
Int32 uiVersion = version.GetSharePointDocVersion();
SqlConnection cn = new SqlConnection("SP ConnStr");
SqlCommand cmd;
string cmdTxt;
cn.Open();
cmd = cn.CreateCommand();
cmdTxt = "update alldocs set CheckinComment=@Comment Where ";
cmdTxt+= "UIVersion=@Version and cast(id as varchar(100))=@id";
cmd.CommandText = cmdTxt;
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("@Comment", comment);
cmd.Parameters.AddWithValue("@Version", uiVersion);
cmd.Parameters.AddWithValue("@id", guid.ToString());
cmd.ExecuteNonQuery();
cmd = cn.CreateCommand();
cmdTxt = "update alldocversions set CheckinComment=@Comment ";
cmdTxt+= "Where Version=@Version and ";
cmdTxt+= "cast(id as varchar(100))=@id";
cmd.CommandText = cmdtxt;
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("@Comment", comment);
cmd.Parameters.AddWithValue("@Version", uiVersion);
cmd.Parameters.AddWithValue("@id", guid.ToString());
cmd.ExecuteNonQuery();
cmd.Dispose();
cn.Dispose();
}
Okay, no you should be able to edit comments of document versions through code. If you know some better way how to do it, please let me know.
Here is one example about how to bulletproof the loops. This example holds well for legacy code and - of course - for hurry-written-code (that might be the current one, unfortunately).
One of the most dangerous things I've seen are pieces of code that loop through object arrays and lists, same time expecting that array or list contains only correct elements. What do you think about ArrayList by example? Is it able to hold only those objects that are correct ones in current context or is ArrayList more powerful and can hold also incorrect objects?
Well, I'm always very suspicious when I see ArrayList or IList on .Net 2.0 code. I always ask myself one question: isn't it possible to refactor this code so there is List<ImportantClass> instead of ArrayList or IList? If it is possible then I will refactor this code.
Here's my example.
public void SaveChanges(ArrayList entries)
{
foreach(MyObject bizObject in entries)
{
if(bizObject.IsDirty)
bizObject.Save();
}
}
What bad things may happen?
- Entries may be null.
- It is possible that entries list contains nulls.
- It is possible that entries list contains entries that are impossible to cast to required type.
- On large arrays cast has some impact on performance.
One can argue now that it is possible to use try-catch here and suppress all the errors. No problem - you can do it - if you want to see some grey hair (color code: #EFEFEF) in mirror after couple of debugging sessions. Try-catch overuse has these bad side efects.
- It may suppress also these exceptions that are thrown out by IsDirty property or Save method.
- When exception is thrown then try-catch needs hell load of resources in some cases.
So, what kind of code should be the best shot (in my humbe opinion)? I consider something like this. Instead of List<MyObject> you can use the generic list or collections that fits your needs better.
public void SaveChanges(List<MyObject> entries)
{
if(entries == null)
return;
foreach(MyObject bizObject in entries)
{
if(bizObject == null)
continue;
if(bizObject.IsDirty)
bizObject.Save();
}
}
Of course, we can also add here exception handling if we need it but it has nothing to do with exceptions that can be easily avoided here. Also it is not possible anymore to add other objects that can cause cast exceptions to our list. And we will also win on performance because we are checking the objects instead of waiting for errors.
Happy looping, guys! :)
When dealing with version numbers we often need to convert them to string and vice versa. There is lot of code where versions are handled manually in code. I don't know why. But I know for sure there is class called Version and I'm sure this class will help us a lot. Let's see a little example. The following code:
Version ver = new Version("1.4");
Console.WriteLine("Major: " + ver.Major.ToString());
Console.WriteLine("Minor: " + ver.Minor.ToString());
Console.WriteLine("String: " + ver.ToString());
Console.ReadLine();
outputs something like this:
Major: 1
Minor: 4
String: 1.4
As we can see, this class makes using versions very easy. There is one nasty limitation - Version class is sealed. If there is need for non-sealed class then you should write it by yourself.
More Posts