将Linq-to-SQL中的计算属性映射到实际的SQL语句

时间:2008-10-10 08:32:47

标签: c# .net linq linq-to-sql

我在Linq-to-SQL类上创建了一些额外的功能,以便在开发应用程序时更轻松。例如,我已经定义了一个从合同列表中检索活动合同的属性。但是,如果我尝试在lambda表达式中使用此属性,或者通常在查询中使用此属性,则会抛出一个异常,即没有与该属性匹配的SQL语句,或者每个项目生成一个查询(=向服务器进行大量往返)。

查询本身并不过分复杂f.ex:

var activeContracts = customer.Contracts.Where(w => w.ContractEndDate == null);

我希望它读作:

var activeContracts = customer.ActiveContracts;

我这样做的主要原因是因为它会最大限度地减少我的逻辑错误,如果我将来想要更改定义活动合约的内容,我就不必重做很多代码。

有没有办法在属性上指定它应该生成什么SQL。或者有没有办法确保它在下面的查询中可用?

var singleContractCustomers = db.Customers.Where(w => w.ActiveContracts.Count() == 1);

3 个答案:

答案 0 :(得分:3)

当单独访问时,我怀疑有一个返回IQueryable的查询会起作用 - 但是,我希望当这是一个更大的Expression的一部分时,表达式解释器会抱怨(这看起来就像你所描述的那样)。 p>

然而,我怀疑你可能会将其分解一下。尝试添加(对客户):

    public static Expression<Func<Customer, bool>> HasActiveContract
    {
        get { return cust => cust.Contracts.Count() == 1; }
    }

然后你应该可以使用:

    var filtered = db.Customers.Where(Customer.HasActiveContract);

显然很难运行它(从这里开始)看看它出现了什么样的TSQL,但是如果这样做的话,我会感到惊讶;我希望在TSQL中执行COUNT()。作为最顶层的查询,您还应该能够包装它:

    public IQueryable<Customer> CustomersWithActiveContract
    {
        get { return Customers.Where(Customer.HasActiveContract); }
    }

这有什么用吗?

答案 1 :(得分:1)

这就像一个魅力。 CustomersWithActiveContracts 生成的SQL语句对我来说很好。

{SELECT [t0].[CustomerID], [t0].[cFirstName], [t0].[cLastName]
FROM [dbo].[Customers] AS [t0]
WHERE ((
    SELECT COUNT(*)
     FROM [dbo].[Contracts] AS [t1]
    WHERE (([t1].[ContractEndDate] > @p0) OR ([t1].[ContractEndDate] IS NULL)) AND ([t1].[cId] = [t0].[cId])
    )) > @p1
}

它还应该意味着可以构建此查询,而不会产生更多的数据库访问。

答案 2 :(得分:1)

另一种选择是使用Microsoft.Linq.Translations库。这将允许您定义您的属性,它的Linq翻译如下:

partial class Customer
{
    private static readonly CompiledExpression<Employee,IEnumerable<Contract>> activeContractsExpression
        = DefaultTranslationOf<Customer>
          .Property(c => c.ActiveContracts)
          .Is(c => c.Contracts.Where(x => x.ContractEndDate == null));

    public IEnumerable<Contract> ActiveContracts
    {
        get 
        { 
            // This is only called when you access your property outside a query
            return activeContractsExpression.Evaluate(this);
        }
    }
}

然后您可以这样查询:

var singleContractCustomers = db.Customers.WithTranslations()
                              .Where(w => w.ActiveContracts.Count() == 1);

请注意对WithTranslations()的通话。这将创建一个特殊的包装IQueryable,它将在将查询表达式传递给Linq to SQL之前用其转换替换对计算属性的所有引用。

此外,如果您包含Microsoft.Linq.Translations.Auto命名空间而不是System.Linq,那么您甚至无需致电WithTranslations()