LINQ to SQL – code generation bug

The code generation performed by MSLinqToSQLGenerator or SQLMetal generates weird property code. For example, in AdvantureWorks, the table Product has a column ProductLine. Using the tools that come with LINQ to SQL, this column translates to a property:

[Column(Storage="_ProductLine", DbType="NChar(2)")]
public string ProductLine
{
get
{
return this._ProductLine;
}
set
{
if ((this._ProductLine != value)) {
this.OnProductLineChanging(value);
this.SendPropertyChanging();
this._ProductLine = value;
this.SendPropertyChanged("ProductLine");
this.OnProductLineChanged();
}
}
}

The odd code involves the SendPropertyChanging() method call. This method call should pass the name of the property, just like the SendPropertyChanged() method call, according to the documentation. Another interesting detail: The OnProductLineChanging and OnProductLineChanged partial method calls are out of order:

  1. Call OnProductLineChanging() partial method
  2. Raise PropertyChanging event, but don’t tell which property is changing – send an empty string instead
  3. Set the property’s field’s value to the specified new value
  4. Raise PropertyChanged event and specify which property is changing
  5. Call OnProductLineChanged() partial method

Why is the PropertyChanged event raised before calling the OnProductLineChanged partial method?
All classes created by LINQ to SQL add the following protected methods:

protected virtual void SendPropertyChanging() {
if ((this.PropertyChanging != null)) {
this.PropertyChanging(this, emptyChangingEventArgs);
}
}
protected virtual void SendPropertyChanged(String propertyName) {
if ((this.PropertyChanged != null)) {
this.PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}

Again, what’s odd about this code is how the SendPropertyChanging() method does not have a property name parameter and sends a emptyChangingEventArgs field reference to the PropertyChanging event rather than creating a new instance of the EventArgs like the SendPropertyChanged() method call does. By creating a new instance of the EventArgs in SendPropertyChanged, it’s able to pass the property name in the constructor (like the documentation says it should).
Here is the field that is passed to all invocations of the event:

private static PropertyChangingEventArgs emptyChangingEventArgs =
new PropertyChangingEventArgs(String.Empty);

As you can see from this constructor, the property that is changing is an empty string. Given the fact that this is a private field and should not be modified by extension methods, it’s odd that this field is not static readonly.
My guess is that the code is generated incorrectly to account for a data-binding or allocation problem. I’ve come to this conclusion by the emptyChangingEventArgs field – it reduces the object instance creation in half for each property change when there are event consumers for the changing event. The big disadvantage for event consumers is that they doesn’t know which property is changing on an object instance.
One alternative is to use PLINQO, which creates the properties correctly.
UPDATE: I have found that this has already been reported. Unfortunately, Microsoft has closed this bug and said it was by design, even though the generated code does not follow Microsoft’s documentation for the PropertyChangingEventArgs class.

LINQ – WHERE X IN (…)

I couldn’t figure out a way to perform the equivalent of WHERE Column1 IN ('A', 'B', 'C') in LINQ, where (‘A’, ‘B’, ‘C’) would represent an IEnumerable<T> where T is the type of Column1. So I thought it was an excellent time to write an extension method that would generate a dynamic expression tree that would add AND (Column1 == "A" OR Column1 = "B" OR ...) to the LINQ query. So I wrote the following code:

public static IQueryable<TSource> WhereIn<TSource, TKey> (
this IQueryable<TSource> source1,
Expression<Func<TSource, TKey>> keySelector,
IEnumerable<TKey> source2) {
if (null == source1)
throw new ArgumentNullException ("source1");
if (null == keySelector)
throw new ArgumentNullException ("keySelector");
if (null == source2)
throw new ArgumentNullException ("source2");
Expression where = null;
foreach (TKey value in source2) {
Expression equal = Expression.Equal (
keySelector.Body,
Expression.Constant (value, typeof (TKey))
);
if (null == where)
where = equal;
else
where = Expression.OrElse (where, equal);
}
return source1.Where<TSource> (
Expression.Lambda<Func<TSource, bool>> (
where, keySelector.Parameters));
}

An example of the usage:

var q = (from u in db.Users
where u.LastLogin > new DateTime (2007, 5, 1)
orderby u.LastLogin descending
select new { u.FirstName, u.LastName, u.UserName, u.LastLogin }
).WhereIn (u => u.UserName, new string[] { "A", "B", "C" });

A day later, I found the right way that will actually generate “WHERE X IN (…)” in LINQ to SQL thanks to Mark Blomsma.

SqlMetal.exe crashes when a column name in the result set is [ ].

The last message to be written to the console was: “Error : Index was outside the bounds of the array.”.
To reproduce this bug, create this stored procedure and run SqlMetal against it:
CREATE PROCEDURE [dbo].[ThisProcedureWillCauseSqlMetalToCrash] AS
BEGIN
SET NOCOUNT ON;
SELECT 1 [A], 2 [ ], 3 [C]
END

Applies to:

  • Visual Studio 2008 RTM
  • Microsoft Windows SDK v6.0A

Please rate and validate this problem at the MSDN Microsoft Product Feedback Center so Microsoft responds with a solution or workaround.

Some Enterprise Library 3.0 – April 2007 bug fixes

Here are the bug fixes I wrote for Enterprise Library 3.0 – April 2007, related to using the EntLibLoggingProxyTraceListener to proxy .NET TraceListener messages to enterprise library and logging over MSMQ via MsmqTraceListener.
In Microsoft. Practices. EnterpriseLibrary. Logging. TraceListeners. EntLibLoggingProxyTraceListener, comment out
properties.Add(TraceEventCacheKey, eventCache); // line 66 & 146 (fix for SerializationException)
In Microsoft. Practices. EnterpriseLibrary. Logging. TraceListeners. MsmqTraceListener, change queueMessage.Label = messageLabel;
to queueMessage.Label = string.IsNullOrEmpty (messageLabel) ? string.Empty : messageLabel; // line 153 (fix for ArgumentNullException)
In LoggingDatabase.sql, change [Title] [nvarchar](256) NOT NULL,
to [Title] [nvarchar](256) NULL,