2

I have setup a test database and console app to confirm the following:

Given a SQL Database with the following function:

CREATE FUNCTION ufn_GTFO
(
    @Guid as uniqueidentifier
)
RETURNS VARCHAR(100)
AS
BEGIN
    -- Declare the return variable here
    DECLARE @Result as VARCHAR(100)

    -- Add the T-SQL statements to compute the return value here
    SELECT @Result = 'This is a test'

    -- Return the result of the function
    RETURN @Result
END
GO

And the following table:

CREATE TABLE [dbo].[Test](
    [PKey] [int] IDENTITY(1,1) NOT NULL,
    [WFT] [uniqueidentifier] NULL,
 CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED 
(
    [PKey] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

The Scalar valued function ufn_GTFO is normally composable, such that the following C#:

static void ConcreteTest()
{
    DataClasses1DataContext db = new DataClasses1DataContext();
    var q = from t in db.Tests
            select new { t.PKey, GTFO = db.ufn_GTFO(t.WFT) };

    var p = q.ToArray();
}

Is translated into the following SQL:

SELECT [t0].[PKey], [dbo].[ufn_GTFO]([t0].[WFT]) AS [GTFO] FROM [dbo].[Test] AS [t0]

However, if I use the refactor -> extract interface on the DataContext, and use an instance of that:

static void InterfaceTest()
{
    IDataClasses1DataContext db = new DataClasses1DataContext();
    var q = from t in db.Tests
            select new { t.PKey, GTFO = db.ufn_GTFO(t.WFT) };

    var p = q.ToArray();
}

I get the following SQL, and calls to ufn_GTFO occur once for each record as .ToArray() enumerates the results.

SELECT [t0].[PKey], [t0].[WFT] AS [guid]
FROM [dbo].[Test] AS [t0]

So, my question is why does this happen and what can I do to prevent it while still using the interface?

Update 1: I've compared the IL generated for the concrete method versus the interface method, and they differ only in the reference to the interface and a compiler generated display class that doesn't seem to have any bearing on the result.

2
  • I won't post an answer to this, as I don't know from a LINQ perspective, but I do know that SQL Server will run ufn_GTFO in the first query once for every row, as well. Commented Jan 10, 2012 at 18:35
  • @eric - you are correct, but running it locally vs. making another call per row is vastly different from a performance perspective. Commented Jan 11, 2012 at 2:20

1 Answer 1

1
+150

linq to sql relies heavily on attributes to map class (datacontext) members to database members. Your interfaces likely does not have the FunctionAttribute over the ufn_GRFO method. without that attribute the link between the C# world and the SQL function is severed; however. your interface is also not decorated with the DatabaseAttribute as that attribute is only valid on classes and not interfaces. without that attribute you have severed the link between C# and the whole database. by default Linq uses the AttributeMappingSource to map type members in the DataContext. Sense the interface is the type in question and that type has severed the link to that database, due to the fact that you can't apply the database attribute to it, your default mapping source will not map the function ufn_GRFO to the database function, rather linq will treat it as a .NET function to call with the data from the WFT field.

I suspect the way around this issue is to provide your datacontext with a custom MappingSource implementation and that ignores the DatabaseAttribute and only considers the attributes on the properties.

http://msdn.microsoft.com/en-us/library/system.data.linq.mapping.functionattribute.aspx

http://msdn.microsoft.com/en-us/library/system.data.linq.mapping.databaseattribute.aspx

http://msdn.microsoft.com/en-us/library/system.data.linq.mapping.attributemappingsource.aspx

http://msdn.microsoft.com/en-us/library/system.data.linq.mapping.mappingsource.aspx

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.