Development With A Dot

Blog on development in general, and specifically on .NET

Sponsors

News

My Friends

My Links

Permanent Posts

Portuguese Communities

May 2010 - Posts

Visual NHibernate Update

I have previously talked about Visual NHibernate. It has grown since last time, now offering support for multiple databases (SQL Server, Oracle, MySQL, PostgreSQL, Firebird), generates projects from existing databases or from existing Visual Studio projects and produces XML or Fluent mappings, to name just a few. To me it is by far the most interesting tools for working with NHibernate I know of (granted, I haven't tried NHibernate Profiler).

For a limited period, Slyce Software is offering a 30% discount, until the final version is released, so you may want to have a look. Please note that I am in no way related to Slyce, but made some feature requests which have been implemented (thanks, Gareth!).

Bookmark and Share
NHibernate Tools

Felice Pollano is the author of a two great new tools for working with NHibernate:

  • NH Workbench: an IDE for writing HQL queries against a model
  • db2hbm: generation of .hbm.xml files from a database (currently only SQL Server, more to come)

I suggest you give them a try and give Felix your feedback!

Bookmark and Share
Enhanced Dynamic Filtering

Remember my last post on dynamic filtering?
Well, this time I'm extending the code in order to allow two levels of querying:

Match type, represented by the following options:


public enum MatchType
{
	StartsWith = 0,

	Contains = 1
}

And word match:


public enum WordMatch
{
	AnyWord = 0,

	AllWords = 1,

	ExactPhrase = 2
}

You can combine the two levels in order to achieve the following combinations:

MatchType.StartsWith + WordMatch.AnyWord Matches any record that starts with any of the words specified
MatchType.StartsWith + WordMatch.AllWords Not available: does not make sense, throws an exception
MatchType.StartsWith + WordMatch.ExactPhrase Matches any record that starts with the exact specified phrase
MatchType.Contains + WordMatch.AnyWord Matches any record that contains any of the specified words
MatchType.Contains + WordMatch.AllWords Matches any record that contains all of the specified words
MatchType.Contains + WordMatch.ExactPhrase Matches any record that contains the exact specified phrase

Here is the code:


public static IList Search(this IQueryable query, Type entityType, String dataTextField, String phrase, MatchType matchType, WordMatch wordMatch, Int32 maxCount)
{
	String [] terms = phrase.Split(' ').Distinct().ToArray();
	StringBuilder result = new StringBuilder();
	PropertyInfo displayProperty = entityType.GetProperty(dataTextField);
	IList searchList = null;
	MethodInfo orderByMethod = typeof(Queryable).GetMethods(BindingFlags.Public | BindingFlags.Static).Where(m => m.Name == "OrderBy").ToArray() [ 0 ].MakeGenericMethod(entityType, displayProperty.PropertyType);
	MethodInfo takeMethod = typeof(Queryable).GetMethod("Take", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(entityType);
	MethodInfo whereMethod = typeof(Queryable).GetMethods(BindingFlags.Public | BindingFlags.Static).Where(m => m.Name == "Where").ToArray() [ 0 ].MakeGenericMethod(entityType);
	MethodInfo distinctMethod = typeof(Queryable).GetMethods(BindingFlags.Public | BindingFlags.Static).Where(m => m.Name == "Distinct" && m.GetParameters().Length == 1).Single().MakeGenericMethod(entityType);
	MethodInfo toListMethod = typeof(Enumerable).GetMethod("ToList", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(entityType);

	MethodInfo matchMethod = typeof(String).GetMethod
	(
		(matchType == MatchType.StartsWith) ? "StartsWith" : "Contains",
		new Type [] { typeof(String) }
	);

	MemberExpression member = Expression.MakeMemberAccess
	(
		Expression.Parameter(entityType, "n"),
		displayProperty
	);

	MethodCallExpression call = null;
	LambdaExpression where = null;
	LambdaExpression orderBy = Expression.Lambda
	(
		member,
		member.Expression as ParameterExpression
	);

	switch (matchType)
	{
		case MatchType.StartsWith:
			switch (wordMatch)
			{
				case WordMatch.AnyWord:
					call = Expression.Call
					(
						member,
						matchMethod,
						Expression.Constant(terms [ 0 ])
					);

					where = Expression.Lambda
					(
						call,
						member.Expression as ParameterExpression
					);

					for (Int32 i = 1; i < terms.Length; ++i)
					{
						String term = terms [ i ];

						MethodCallExpression call2 = Expression.Call
						(
							member,
							matchMethod,
							Expression.Constant(term)
						);

						LambdaExpression where2 = Expression.Lambda
						(
							call2,
							member.Expression as ParameterExpression
						);

						var exp = Expression.Invoke(where2, where.Parameters.Cast<Expression>());

						where = Expression.Lambda
						(
							Expression.Or
							(
								where.Body,
								exp
							),
							where.Parameters.ToArray()
						);
					}
					break;

				case WordMatch.ExactPhrase:
					call = Expression.Call
					(
						member,
						matchMethod,
						Expression.Constant(phrase)
					);

					where = Expression.Lambda
					(
						call,
						member.Expression as ParameterExpression
					);
					break;

				case WordMatch.AllWords:
					throw (new Exception("The match type StartsWith is not supported with word match AllWords"));
			}

			break;

		case MatchType.Contains:
			switch (wordMatch)
			{
				case WordMatch.AnyWord:
					call = Expression.Call
					(
						member,
						matchMethod,
						Expression.Constant(terms [ 0 ])
					);

					where = Expression.Lambda
					(
						call,
						member.Expression as ParameterExpression
					);

					for (Int32 i = 1; i < terms.Length; ++i)
					{
						String term = terms [ i ];

						MethodCallExpression call2 = Expression.Call
						(
							member,
							matchMethod,
							Expression.Constant(term)
						);

						LambdaExpression where2 = Expression.Lambda
						(
							call2,
							member.Expression as ParameterExpression
						);

						var exp = Expression.Invoke(where2, where.Parameters.Cast<Expression>());

						where = Expression.Lambda
						(
							Expression.Or
							(
								where.Body,
								exp
							),
							where.Parameters.ToArray()
						);
					}
					break;

				case WordMatch.ExactPhrase:
					call = Expression.Call
					(
						member,
						matchMethod,
						Expression.Constant(phrase)
					);

					where = Expression.Lambda
					(
						call,
						member.Expression as ParameterExpression
					);
					break;

				case WordMatch.AllWords:
					call = Expression.Call
					(
						member,
						matchMethod,
						Expression.Constant(terms [ 0 ])
					);

					where = Expression.Lambda
					(
						call,
						member.Expression as ParameterExpression
					);

					for (Int32 i = 1; i < terms.Length; ++i)
					{
						String term = terms [ i ];

						MethodCallExpression call2 = Expression.Call
						(
							member,
							matchMethod,
							Expression.Constant(term)
						);

						LambdaExpression where2 = Expression.Lambda
						(
							call2,
							member.Expression as ParameterExpression
						);

						var exp = Expression.Invoke(where2, where.Parameters.Cast<Expression>());

						where = Expression.Lambda
						(
							Expression.AndAlso
							(
								where.Body,
								exp
							),
							where.Parameters.ToArray()
						);
					}
					break;
			}
			break;
	}

	query = orderByMethod.Invoke(null, new Object [] { query, orderBy }) as IQueryable;
	query = whereMethod.Invoke(null, new Object [] { query, where }) as IQueryable;

	if (maxCount != 0)
	{
		query = takeMethod.Invoke(null, new Object [] { query, maxCount }) as IQueryable;
	}

	searchList = toListMethod.Invoke(null, new Object [] { query }) as IList;

	return (searchList);
}

And this is how you'd use it:


IQueryable query = ctx.MyEntities;
IList list = Search(query, typeof(MyEntity), "Name", "Ricardo Peres", MatchType.Contains, WordMatch.ExactPhrase, 10 /*0 for all*/);

Bookmark and Share
Browser Uniqueness

The Electronic Frontier Foundation (EFF) has recently published a paper on browsers being tracked by it's unique fingerprint. If you're curious to know what your favorite browser is doing to protect (or not) your privacy, check it here: https://panopticlick.eff.org

Many thanks to Miguel Monteiro for this (and other) nice links!

Bookmark and Share
Posted: May 20 2010, 10:51 AM by Ricardo Peres | with no comments
Filed under:
Lesser Known NHibernate Session Methods

The NHibernate ISession, the core of NHibernate usage, has some methods which are quite misunderstood and underused, to name a few, Merge, Persist, Replicate and SaveOrUpdateCopy.

Their purpose is:

  • Merge: copies properties from a transient entity to an eventually loaded entity with the same id in the first level cache; if there is no loaded entity with the same id, one will be loaded and placed in the first level cache first; if using version, the transient entity must have the same version as in the database;
  • Persist: similar to Save or SaveOrUpdate, attaches a maybe new entity to the session, but does not generate an INSERT or UPDATE immediately and thus the entity does not get a database-generated id, it will only get it at flush time;
  • Replicate: copies an instance from one session to another session, perhaps from a different session factory;
  • SaveOrUpdateCopy: attaches a transient entity to the session and tries to save it.

Here are some samples of its use.


ISession session = ...;

AuthorDetails existingDetails = session.Get<AuthorDetails>(1);	//loads an entity and places it in the first level cache
AuthorDetails detachedDetails = new AuthorDetails { ID = existingDetails.ID, Name = "Changed Name" };	//a detached entity with the same ID as the existing one
Object mergedDetails = session.Merge(detachedDetails);	//merges the Name property from the detached entity into the existing one; the detached entity does not get attached

session.Flush();	//saves the existingDetails entity, since it is now dirty, due to the change in the Name property


AuthorDetails details = ...;
ISession session = ...;

session.Persist(details);	//details.ID is still 0

session.Flush();	//saves the details entity now and fetches its id


ISessionFactory factory1 = ...;
ISessionFactory factory2 = ...;

ISession session1 = factory1.OpenSession();
ISession session2 = factory2.OpenSession();

AuthorDetails existingDetails = session1.Get<AuthorDetails>(1);	//loads an entity

session2.Replicate(existingDetails, ReplicationMode.Overwrite);	//saves it into another session, overwriting any possibly existing one with the same id; other options are Ignore, where any existing record with the same id is left untouched, Exception, where an exception is thrown if there is a record with the same id and LatestVersion, where the latest version wins

Bookmark and Share
ASP.NET List Control

Today I developed a simple control for generating lists in ASP.NET, something that the base class library does not contain; it allows for nested lists where the list item types and images can be configured on a list by list basis. Since it was a great fun to develop, I'd like to share it here. Here is the code:


[ParseChildren(true)]
[PersistChildren(false)]
public class List: WebControl
{
	public List(): base("ul")
	{
		this.Items = new List<ListItem>();
		this.ListStyleType = ListStyleType.Auto;
		this.ListStyleImageUrl = String.Empty;
		this.CommonCssClass = String.Empty;
		this.ContainerCssClass = String.Empty;
	}

	[DefaultValue(ListStyleType.Auto)]
	public ListStyleType ListStyleType
	{
		get;
		set;
	}

	[DefaultValue("")]
	[UrlProperty("*.png;*.gif;*.jpg")]
	public String ListStyleImageUrl
	{
		get;
		set;
	}

	[DefaultValue("")]
	[CssClassProperty]
	public String CommonCssClass
	{
		get;
		set;
	}

	[DefaultValue("")]
	[CssClassProperty]
	public String ContainerCssClass
	{
		get;
		set;
	}

	[Browsable(false)]
	[PersistenceModeAttribute(PersistenceMode.InnerProperty)]
	public List<ListItem> Items
	{
		private set;
		get;
	}

	protected override void Render(HtmlTextWriter writer)
	{
		String cssClass = String.Join(" ", new String [] { this.CssClass, this.ContainerCssClass });

		if (cssClass.Trim().Length != 0)
		{
			this.CssClass = cssClass;
		}

		if (String.IsNullOrEmpty(this.ListStyleImageUrl) == false)
		{
			this.Style[ HtmlTextWriterStyle.ListStyleImage ] = String.Format("url('{0}')", this.ResolveClientUrl(this.ListStyleImageUrl));
		}

		if (this.ListStyleType != ListStyleType.Auto)
		{
			switch (this.ListStyleType)
			{
				case ListStyleType.Circle:
				case ListStyleType.Decimal:
				case ListStyleType.Disc:
				case ListStyleType.None:
				case ListStyleType.Square:
					this.Style [ HtmlTextWriterStyle.ListStyleType ] = this.ListStyleType.ToString().ToLower();
					break;
				case ListStyleType.LowerAlpha:
					this.Style [ HtmlTextWriterStyle.ListStyleType ] = "lower-alpha";
					break;
				case ListStyleType.LowerRoman:
					this.Style [ HtmlTextWriterStyle.ListStyleType ] = "lower-roman";
					break;
				case ListStyleType.UpperAlpha:
					this.Style [ HtmlTextWriterStyle.ListStyleType ] = "upper-alpha";
					break;
				case ListStyleType.UpperRoman:
					this.Style [ HtmlTextWriterStyle.ListStyleType ] = "upper-roman";
					break;
			}
		}
			base.Render(writer);
	}

	protected override void RenderChildren(HtmlTextWriter writer)
	{
		foreach (ListItem item in this.Items)
		{
			this.writeItem(item, this, 0);
		}

		base.RenderChildren(writer);
	}

	private void writeItem(ListItem item, Control control, Int32 depth)
	{
		HtmlGenericControl li = new HtmlGenericControl("li");
		control.Controls.Add(li);

		if (String.IsNullOrEmpty(this.CommonCssClass) == false)
		{
			String cssClass = String.Join(" ", new String [] { this.CommonCssClass, this.CommonCssClass + depth });
			li.Attributes [ "class" ] = cssClass;
		}

		foreach (String key in item.Attributes.Keys)
		{
			li.Attributes[key] = item.Attributes [ key ];
		}

		li.InnerText = item.Text;

		if (item.ChildItems.Count != 0)
		{
			HtmlGenericControl ul = new HtmlGenericControl("ul");
			li.Controls.Add(ul);

			if (String.IsNullOrEmpty(this.ContainerCssClass) == false)
			{
				ul.Attributes["class"] = this.ContainerCssClass;
			}

			if ((item.ListStyleType != ListStyleType.Auto) || (String.IsNullOrEmpty(item.ListStyleImageUrl) == false))
			{
				if (String.IsNullOrEmpty(item.ListStyleImageUrl) == false)
				{
					ul.Style[HtmlTextWriterStyle.ListStyleImage] = String.Format("url('{0}');", this.ResolveClientUrl(item.ListStyleImageUrl));
				}

				if (item.ListStyleType != ListStyleType.Auto)
				{
					switch (this.ListStyleType)
					{
						case ListStyleType.Circle:
						case ListStyleType.Decimal:
						case ListStyleType.Disc:
						case ListStyleType.None:
						case ListStyleType.Square:
							ul.Style[ HtmlTextWriterStyle.ListStyleType ] = item.ListStyleType.ToString().ToLower();
							break;
						case ListStyleType.LowerAlpha:
							ul.Style [ HtmlTextWriterStyle.ListStyleType ] = "lower-alpha";
							break;
						case ListStyleType.LowerRoman:
							ul.Style [ HtmlTextWriterStyle.ListStyleType ] = "lower-roman";
							break;
						case ListStyleType.UpperAlpha:
							ul.Style [ HtmlTextWriterStyle.ListStyleType ] = "upper-alpha";
							break;
						case ListStyleType.UpperRoman:
							ul.Style [ HtmlTextWriterStyle.ListStyleType ] = "upper-roman";
							break;
					}
				}
			}

			foreach (ListItem childItem in item.ChildItems)
			{
				this.writeItem(childItem, ul, depth + 1);
			}
		}
	}
}


[Serializable]
[ParseChildren(true, "ChildItems")]
public class ListItem: IAttributeAccessor
{
	public ListItem()
	{
		this.ChildItems = new List<ListItem>();
		this.Attributes = new Dictionary<String, String>();
		this.Text = String.Empty;
		this.Value = String.Empty;
		this.ListStyleType = ListStyleType.Auto;
		this.ListStyleImageUrl = String.Empty;
	}

	[DefaultValue(ListStyleType.Auto)]
	public ListStyleType ListStyleType
	{
		get;
		set;
	}

	[DefaultValue("")]
	[UrlProperty("*.png;*.gif;*.jpg")]
	public String ListStyleImageUrl
	{
		get;
		set;
	}

	[DefaultValue("")]
	public String Text
	{
		get;
		set;
	}

	[DefaultValue("")]
	public String Value
	{
		get;
		set;
	}

	[Browsable(false)]
	public List<ListItem> ChildItems
	{
		get;
		private set;
	}

	[Browsable(false)]
	public Dictionary<String, String> Attributes
	{
		get;
		private set;
	}

	String IAttributeAccessor.GetAttribute(String key)
	{
		return (this.Attributes [ key ]);
	}

	void IAttributeAccessor.SetAttribute(String key, String value)
	{
		this.Attributes [ key ] = value;
	}
}


[Serializable]
public enum ListStyleType
{
	Auto = 0,
	Disc,
	Circle,
	Square,
	Decimal,
	LowerRoman,
	UpperRoman,
	LowerAlpha,
	UpperAlpha,
	None
}



	
		
			
			
				
			
		
		
	


Bookmark and Share
Posted: May 18 2010, 09:36 PM by Ricardo Peres | with 1 comment(s)
Filed under: ,
Reading A User's Profile

One frequent question is: how can we read a user's profile properties? The answer is simple, we use class ProfileBase:


//a specific user
ProfileBase profile = ProfileBase.Create("username", true);

//all users
BaseProfile [] profiles = Membership.GetAllUsers().Cast().Select(u => ProfileBase.Create(u.UserName, true)).ToArray();

Bookmark and Share
Posted: May 18 2010, 02:18 PM by Ricardo Peres | with 1 comment(s)
Filed under: ,
Change Data Capture

There's an hidden gem in SQL Server 2008: Change Data Capture (CDC). Using CDC we get full audit capabilities with absolutely no implementation code: we can see all changes made to a specific table, including the old and new values!

You can only use CDC in SQL Server 2008 Developer or Enterprise, Professional and Express editions are not supported. Here are the steps you need to take, just remember SQL Agent must be running:


use SomeDatabase;

-- first create a table
CREATE TABLE Author
(
	ID INT NOT NULL PRIMARY KEY IDENTITY(1, 1),
	Name NVARCHAR(20) NOT NULL,
	EMail NVARCHAR(50) NOT NULL,
	Birthday DATE NOT NULL
)

-- enable CDC at the DB level
EXEC sys.sp_cdc_enable_db

-- check CDC is enabled for the current DB
SELECT name, is_cdc_enabled
FROM sys.databases
WHERE name = 'SomeDatabase'

-- enable CDC for table Author, all columns
exec sys.sp_cdc_enable_table @source_schema = 'dbo', @source_name = 'Author', @role_name = null

-- insert values into table Author
insert into Author (Name, EMail, Birthday, Username)
values ('Bla', 'bla@bla', 1990-10-10, 'bla')

-- check CDC data for table Author
-- __$operation: 1 = DELETE, 2 = INSERT, 3 = BEFORE UPDATE 4 = AFTER UPDATE
-- __$start_lsn: operation timestamp
select *
from cdc.dbo_author_CT

-- update table Author
update Author
set EMail = 'bla@bla.bla'
where Name = 'Bla'

-- check CDC data for table Author
select *
from cdc.dbo_author_CT

-- delete from table Author
delete from Author

-- check CDC data for table Author
select *
from cdc.dbo_author_CT

-- disable CDC for table Author
-- this removes all CDC data, so be carefull
exec sys.sp_cdc_disable_table @source_schema = 'dbo', @source_name = 'Author', @capture_instance = 'dbo_Author'

-- disable CDC for the entire DB
-- this removes all CDC data, so be carefull
exec sys.sp_cdc_disable_db

More info here and here.

Bookmark and Share
More Posts