Linq To Sql进阶系列(四)User Define Function篇

本文探讨了LinqToSql如何支持SQL中的用户定义函数(UDF),包括标量值函数(SVF)和表值函数(TVF)的内联查询。通过具体实例展示了如何在LinqToSql中映射和使用UDF。
1,UDF 简介
  UDF可以分为两中类型。一种为Scalar Valued Function,简称为SVF,是返回值类型的UDF. 另一种为Table Valued Function 简称为TVF,是返回一个table的UDF. 人们通常喜欢拿UDF和Store Procedure做比较。其实,他们各有千秋。UDF最多只能返回一个RowSet,而Store Procedure可以是多个。Store Procedure支持CUD操作,而UDF不支持。但是UDF在sql 中支持内联查询,这个又是Sprocs所不能及的。因此Linq To Sql 也支持UDF的内联查询。
  2,SVF
  看下面这个例子。返回某个类别产品最小的单元价格。
CREATE FUNCTION [dbo].[MinUnitPriceByCategory] (@categoryID INT ) RETURNS Money AS BEGIN -- Declare the return variable here DECLARE @ResultVar Money -- Add the T-SQL statements to compute the return value here SELECT @ResultVar = MIN(p.UnitPrice) FROM Products as p WHERE p.CategoryID =
@categoryID -- Return the result of the function RETURN @ResultVar END
  用OR Designer(请参考OR工具介绍 )将其映射为Dbml。如下
<Function Name="dbo.MinUnitPriceByCategory" Method="MinUnitPriceByCategory"
IsComposable="true"> <Parameter Name="categoryID" Type="System.Int32" DbType="Int" /> <Return Type="System.Decimal" /> </Function>
  在这里,笔者将带着大家开始习惯用dbml来维护数据库的映射,而不是code.在beta2发布后,有人很快就能发现mapping code无法编译了。因为接口改动了。好,回归正题。
  无论是UDF还是Sprocs都会被映射为Function. 而IsComposable="true"是UDF独有的一个特性,是标志UDF身份的,Linq用它来区别Sprocs和UDF。这个字段说明,该函数是支持内联查询的。Name则是其在数据库中的名称。再来看其生成的code.
[Function(Name="dbo.MinUnitPriceByCategory", IsComposable=true)] public System.Nullable<decimal> MinUnitPriceByCategory([Parameter(DbType="Int")]
System.Nullable<int> categoryID) { return ((System.Nullable<decimal>)(this.ExecuteMethodCall(this,
((MethodInfo)(MethodInfo.GetCurrentMethod())), categoryID).ReturnValue)); }
  Linq To Sql将Sprocs和UDF映射成DataContext类里的方法的形式,这样用户就可以像调用函数那样,调用该UDF。因为这个例子是SVF,所以,返回decimal类型的值。再来看它的应用。刚才说过,可以像函数那样调用它。比如:
int result = db.IntSVF(variable); int result = db.IntSVF(constant);
  再就让我们来看几个内联的查询的。所谓内联(in-line),就是说,你可以把UDF当作一个表(TVF),或一个变量(SVF),写在sql语句里。比如:
SELECT * FROM Products AS t0 WHERE t0.UnitPrice = dbo.MinUnitPriceByCategory(t0.CategoryID)
  在这个sql语句中,就调用了上面那个UDF。同样Linq To Sql也支持这样操作。可以写为
var q = from p in db.Products where p.UnitPrice == db.MinUnitPriceByCategory(p.CategoryID) select p;
  大家可以看看其生成的Sql是不是和上面的一样。再举一个UDF的例子
CREATE FUNCTION [dbo].[TotalProductUnitPriceByCategory] (@categoryID int) RETURNS Money AS BEGIN -- Declare the return variable here DECLARE @ResultVar Money -- Add the T-SQL statements to compute the return value here SELECT @ResultVar = (Select SUM(UnitPrice) from Products where CategoryID = @categoryID) -- Return the result of the function RETURN @ResultVar END
计算某类产品的单价总和。这次,我们在select字句里调用它
SELECT CategoryID, [dbo].[TotalProductUnitPriceByCategory](CategoryID)
AS [TotalUnitPrice] FROM Categories
  其同样功能的Linq语句为:
var q = from c in db.Categories select new {c.CategoryID, TotalUnitPrice =
db.TotalProductUnitPriceByCategory(c.CategoryID)};
  其实,对于SVF,可以放在除from等与table有关语句之外的任何地方。比如Order by, Group by等。同样Linq全部支持。如例
var q = from p in db.Products where p.UnitsOnOrder >= db.SVF(p.UnitsInStock) group p by db.SVF(p.CategoryID) into g order by db.SVF(g.Key) select db.SVF(g.Key);
  当然,这个纯粹是给个例子,并没有太多实际意义。

  3,系统函数的映射
  目前为止,无论是OR designer还是SqlMetal均不支持对系统函数的映射。笔者也只是尝试着,手工映射,并成功调用。我们拿Var函数举例。Var是求方差。让我们来模仿上面那个dbml来改写自己的dbml。我们将要多money类型做求var值。并且希望能够调用sql server提供的var函数。那就需要我们将映射的名称改成var,并且改动参数和返回值类型。其最后的dbml为:
<Function Name="Var" Method="Var" IsComposable="true"> <Parameter Name="para" Type="System.Decimal" DbType="Money" /> <Return Type="System.Decimal" /> </Function>
  其生成的code为:
[Function(IsComposable=true)] public System.Nullable<decimal> Var([Parameter(DbType="Money")]
System.Nullable<decimal> para) { return ((System.Nullable<decimal>)(this.ExecuteMethodCall(this,
((MethodInfo)(MethodInfo.GetCurrentMethod())), para).ReturnValue)); }
  将该方法,放在DataContext的一个partial类中,我们并不想破坏其原来的mapping 文件,所以,单独放在一个partial类中。而后,我们尝试着Linq To Sql中调用该函数
var q = (from p in db.Products select db.Var(p.UnitPrice)).ToList();
  其生成的sql为
SELECT CONVERT(Decimal(29,4),Var([t0].[UnitPrice])) AS [value] FROM [dbo].[Products] AS [t0] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel
  我们就这样骗过了Linq To Sql的Run-Time。 成功调用sql server提供var函数。再比如,有人习惯于用NEWID()随机排序,达到取随机记录的目的。其原始sql为:
SELECT TOP 10 * FROM TABLE1 ORDER BY NEWID();
  那用Linq To Sql该如何来做这个事情呢?不好意思,目前还不支持对系统函数的映射。那就手工来做吧。
  因为NewId返回uniqueidentifier类型,我们将这个函数定义为
[Function(Name = "NewID", IsComposable = true)] public Guid NewID() { return ((Guid)(this.ExecuteMethodCall(this,
((MethodInfo)(MethodInfo.GetCurrentMethod()))).ReturnValue)); }
  调用时,可以
var q = db.Table1.OrderBy(p => db.NewID()).Take(10).ToList();
  这只是一个小技巧,并不说明,所有的函数都可以这么做。

  4 TVF
  返回一个table 的UDF称为TVF.看下面例子
CREATE FUNCTION [dbo].[ProductsUnderThisUnitPrice] (@price Money ) RETURNS TABLE AS RETURN SELECT * FROM Products as P Where p.UnitPrice < @price TVF在sql中支持from,join,union等操作。同样,这些操作在Linq To Sql中一样支持。
该TVF的dbml为: <Function Name="dbo.ProductsUnderThisUnitPrice" Method="ProductsUnderThisUnitPrice"
IsComposable="true"> <Parameter Name="price" Type="System.Decimal" DbType="Money" /> <ElementType Name="ProductsUnderThisUnitPriceResult"> <Column Name="ProductID" Type="System.Int32" DbType="Int NOT NULL"
CanBeNull="false" /> <Column Name="ProductName" Type="System.String" DbType="NVarChar(40) NOT NULL"
CanBeNull="false" /> <Column Name="SupplierID" Type="System.Int32" DbType="Int" CanBeNull="true" /> <Column Name="CategoryID" Type="System.Int32" DbType="Int" CanBeNull="true" /> <Column Name="QuantityPerUnit" Type="System.String" DbType="NVarChar(20)"
CanBeNull="true" /> <Column Name="UnitPrice" Type="System.Decimal" DbType="Money" CanBeNull="true" /> <Column Name="UnitsInStock" Type="System.Int16" DbType="SmallInt"
CanBeNull="true" /> <Column Name="UnitsOnOrder" Type="System.Int16" DbType="SmallInt"
CanBeNull="true" /> <Column Name="ReorderLevel" Type="System.Int16" DbType="SmallInt"
CanBeNull="true" /> <Column Name="Discontinued" Type="System.Boolean" DbType="Bit NOT NULL"
CanBeNull="false" /> </ElementType> </Function>
  ElementType子项说明了其返回一个table.映射为类的名称为ProductsUnderThisUnitPriceResult。其映射的 code中,不光是一个函数,还有一个对应的返回类。当然,这个返回类的定义,可以由用户自己指定。此处不讲。我们使用默认的类。我们先来看一个from 的例子
var q = from p in db.ProductsUnderThisUnitPrice(10.25M) select p;
  你可以就把该udf当作一个普通的表来使用。再举一个join操作Linq To Sql的例子
var q = from c in db.Categories join p in db.ProductsUnderThisUnitPrice(8.50M) on c.CategoryID
equals p.CategoryID into prods from p in prods select new {c.CategoryID, c.CategoryName, p.ProductName, p.UnitPrice};
  因为,sql中支持TVF的in-line操作,所以Linq To Sql完全支持其对等的操作。他们所生成的sql语句不再列出。

  总结:
  通过本文,我们可以看出Linq To Sql完全融入了Sql中UDF,包括对其内联操作的支持。对于某些特殊需求,用户可以手工将函数映射为code,但这并不说明,任何函数都适用。
这是我整个csproj文件。里面显示的内容就是缺失{0}{{{<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{1A8E2AB2-6591-498F-9439-CF15A0C3C8E3}</ProjectGuid> <OutputType>Exe</OutputType> <RootNamespace>Vertical</RootNamespace> <AssemblyName>Vertical</AssemblyName> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <Deterministic>true</Deterministic> <NuGetPackageImportStamp> </NuGetPackageImportStamp> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PlatformTarget>x64</PlatformTarget> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PlatformTarget>AnyCPU</PlatformTarget> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> <PropertyGroup> <StartupObject /> </PropertyGroup> <ItemGroup> <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <HintPath>packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.ComponentModel.DataAnnotations" /> <Reference Include="System.Core" /> <Reference Include="System.Data.SQLite, Version=1.0.119.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> <HintPath>packages\System.Data.SQLite.2.0.1\lib\net471\System.Data.SQLite.dll</HintPath> </Reference> <Reference Include="System.Transactions" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Data" /> <Reference Include="System.Deployment" /> <Reference Include="System.Drawing" /> <Reference Include="System.Net.Http" /> <Reference Include="System.Windows.Forms" /> <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> <Compile Include="Dal\Configurations\ConfigInitializer.cs" /> <Compile Include="Dal\DataContext.cs" /> <Compile Include="Dal\Configurations\FilePathConfig.cs" /> <Compile Include="Dal\Configurations\DatabaseConfig.cs" /> <Compile Include="Dal\Repositories\UserRepository.cs" /> <Compile Include="Dal\Repositories\JsonConfigRepository.cs" /> <Compile Include="Dal\Repositories\SqliteDataRepository.cs" /> <Compile Include="Models\FunctionValueProcessor.cs" /> <Compile Include="Models\ListValueProcessor.cs" /> <Compile Include="Models\MappingValueProcessor.cs" /> <Compile Include="question.cs" /> <Compile Include="Commands\ICommands.cs" /> <Compile Include="Commands\RelayCommand.cs" /> <Compile Include="Dal\Entities\LogEntity.cs" /> <Compile Include="Dal\Entities\UserEntity.cs" /> <Compile Include="Dal\Entities\RowDataEntity.cs" /> <Compile Include="Dal\Interfaces\IUserRepository.cs" /> <Compile Include="Dal\Interfaces\IConfigRepository.cs" /> <Compile Include="Dal\Interfaces\IDataRepository.cs" /> <Compile Include="Dal\Interfaces\IRepository.cs" /> <Compile Include="Dal\Mappers\DataMapper.cs" /> <Compile Include="Infrastructure\DataBinding.cs" /> <Compile Include="Infrastructure\INotifyPropertyChangedBase.cs" /> <Compile Include="Models\CheckBoxControlModel.cs" /> <Compile Include="Models\ApplicationDataModel.cs" /> <Compile Include="Models\AttributeValueConfig.cs" /> <Compile Include="Models\DataGridViewModel.cs" /> <Compile Include="Models\NavigationState.cs" /> <Compile Include="Models\TransformFunction.cs" /> <Compile Include="Models\UserModel.cs" /> <Compile Include="Models\PageModel.cs" /> <Compile Include="Models\NavigationModel.cs" /> <Compile Include="Models\RowItemModel.cs" /> <Compile Include="Models\ComboBoxControlModel.cs" /> <Compile Include="Models\ComboBoxItem.cs" /> <Compile Include="Models\TextBoxControlModel.cs" /> <Compile Include="Models\InputControlBase.cs" /> <Compile Include="Models\IControlData.cs" /> <Compile Include="Models\ValidationRule.cs" /> <Compile Include="Models\FontInfoModel.cs" /> <Compile Include="Models\ControlTypeEnum.cs" /> <Compile Include="Test\ExtendedTest.cs" /> <Compile Include="Test\TestHelpers.cs" /> <Compile Include="Test\TestProgram.cs" /> <Compile Include="Views\BindingManagers\IBindingManager.cs" /> <Compile Include="Views\Form1.cs"> <SubType>Form</SubType> </Compile> <Compile Include="Views\Form1.Designer.cs"> <DependentUpon>Form1.cs</DependentUpon> </Compile> <Compile Include="Models\ModelBase.cs" /> <Compile Include="Program.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="ViewModels\MainViewModel.cs" /> <Compile Include="ViewModels\ViewModelBase.cs" /> <EmbeddedResource Include="Views\Form1.resx"> <DependentUpon>Form1.cs</DependentUpon> </EmbeddedResource> <EmbeddedResource Include="Properties\Resources.resx"> <Generator>ResXFileCodeGenerator</Generator> <LastGenOutput>Resources.Designer.cs</LastGenOutput> <SubType>Designer</SubType> </EmbeddedResource> <Compile Include="Properties\Resources.Designer.cs"> <AutoGen>True</AutoGen> <DependentUpon>Resources.resx</DependentUpon> </Compile> <None Include="packages.config" /> <None Include="Properties\Settings.settings"> <Generator>SettingsSingleFileGenerator</Generator> <LastGenOutput>Settings.Designer.cs</LastGenOutput> </None> <Compile Include="Properties\Settings.Designer.cs"> <AutoGen>True</AutoGen> <DependentUpon>Settings.settings</DependentUpon> <DesignTimeSharedInput>True</DesignTimeSharedInput> </Compile> </ItemGroup> <ItemGroup> <None Include="App.config" /> </ItemGroup> <ItemGroup /> <ItemGroup> <EmbeddedResource Include="Resources\InitDatabase.sql" /> <None Update="Resources\InitDatabase.sql"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets" Condition="Exists('packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets')" /> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <PropertyGroup> <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText> </PropertyGroup> <Error Condition="!Exists('packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets'))" /> </Target> </Project>}}}帮我看看问题出在哪里?
09-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值