使用LINQ和存储过程,每行返回同一实体的多个实例

时间:2009-01-14 10:53:01

标签: linq stored-procedures

我们的开发策略规定所有数据库访问都是通过存储过程进行的,这在使用LINQ时会产生问题。

为了使解释更容易,下面讨论的场景有所简化。

考虑一个有2个表的数据库。

  • 订单(OrderID(PK),InvoiceAddressID(FK),DeliveryAddressID(FK))
  • 地址(AddresID(PK),Street,ZipCode)

存储过程返回的结果集必须重命名与地址相关的列,以便发票和传递地址彼此不同。

OrderID InvAddrID DelAddrID InvStreet DelStreet InvZipCode DelZipCode
1       27        46        Main St   Back St   abc123     xyz789

但是,这意味着LINQ不知道如何处理结果集中的这些列,因为它们不再匹配Address实体中的属性名称。

令人沮丧的是,似乎无法定义哪些结果集列映射到哪些实体属性,即使可能(在某种程度上)将实体属性映射到插入/的存储过程参数更新操作。

还有其他人有同样的问题吗?

我认为从架构的角度来看,这是一个相对常见的场景,但存储过程似乎是这里的关键因素。

5 个答案:

答案 0 :(得分:3)

您是否考虑过创建类似下面的视图供存储过程选择?这会增加复杂性,但允许LINQ以您想要的方式查看实体。

Create view OrderAddress as 
Select o.OrderID 
,i.AddressID as InvID
,d.AddressID as DelID
...
from Orders o
left join Addresses  i
on o.InvAddressID= i.AddressID
left join Addresses d
on o.DelAddressID = i.AddressID

答案 1 :(得分:2)

LINQ对于查询数据有点挑剔;它希望架构匹配。我怀疑你将不得不把它带回一个自动生成的类型,然后映射到你的实体类型,之后在LINQ对象(即在AsEnumerable()或类似之后) - 因为它不喜欢在查询中手动创建映射实体的实例。

实际上,我建议在一个方面挑战要求:而不是SP,考虑使用UDF来查询数据;它们在数据库拥有方面的工作方式类似,但它们可以在服务器上进行组合(分页,排序,连接等)。

(这一点有点随机 - 用一点盐)

如果模式匹配,UDF可以与实体类型相关联,所以另一个选项(我没有尝试过)将是一个GetAddress(id)udf和一个“main”udf,并加入它们:

var qry = from row in ctx.MainUdf(id)
          select new {
             Order = ctx.GetOrder(row.OrderId),
             InvoiceAddress = ctx.GetAddress(row.InvoiceAddressId),
             DeliveryAddress = ctx.GetAddress(row.DeliveryAddressId)) };

(其中udf只返回id - 实际上,你可能将 join 与其他udf相比,使其更糟糕。)

或者某些东西 - 可能会因为认真考虑而过于混乱。

答案 2 :(得分:1)

如果您确切知道结果集将包含哪些列,那么您应该能够创建一个新的实体类型,该类型具有结果集中每列的属性。例如,您可以将数据打包到Order,而不是尝试将数据打包到OrderWithAddresses,而{{1}}具有您的存储过程所期望的结构。如果您正在使用LINQ to Entities,您甚至应该能够在.edmx文件中指明OrderWithAddresses是具有两个附加属性的Order。在LINQ to SQL中,您必须将所有列指定为完全不相关的数据类型。

如果您的列是由存储过程动态生成的,则需要尝试不同的方法:创建一个仅从Orders表中提取数据的新存储过程,以及仅从地址表中提取数据的存储过程。设置LINQ映射以使用这些存储过程。 (当然,您使用存储过程的唯一原因是遵守您的公司政策)。然后,使用LINQ加入这些数据。效率应该略低,但它会更恰当地反映数据的实际结构,我认为这是更好的编程实践。

答案 3 :(得分:1)

我想我明白你所追求的是什么,但我可以疯狂......

如果您在DBML(右键单击 - > new - >类)中模拟与源表结构相同的类,则可以根据从存储过程中读取的内容创建新对象。使用LINQ to对象,您仍然可以查询您的选择。这是更多的代码,但它并不难做到。例如,像这样模拟你的DBML:

Pay attention to the associations http://geeksharp.com/screens/orders-dbml.png

确保你注意我添加的关联。您可以展开“父属性”并将这些关联的名称更改为“InvoiceAddress”和“DeliveryAddress”。我还将子属性名称分别更改为“InvoiceOrders”和“DeliveryOrders”。请注意顶部的存储过程称为“usp_GetOrders”。现在,通过一些代码,您可以手动映射列。我知道它并不理想,特别是如果存储过程不会暴露每个表的每个成员,但它可以让你关闭:

public List<Order> GetOrders()
{
    // our DBML classes
    List<Order> dbOrders = new List<Order>();

    using (OrderSystemDataContext db = new OrderSystemDataContext())
    {
        // call stored proc
        var spOrders = db.usp_GetOrders();

        foreach (var spOrder in spOrders)
        {
            Order ord = new Order();
            Address invAddr = new Address();
            Address delAddr = new Address();

            // set all the properties
            ord.OrderID = spOrder.OrderID;

            // add the invoice address
            invAddr.AddressID = spOrder.InvAddrID;
            invAddr.Street = spOrder.InvStreet;
            invAddr.ZipCode = spOrder.InvZipCode;
            ord.InvoiceAddress = invAddr;

            // add the delivery address
            delAddr.AddressID = spOrder.DelAddrID;
            delAddr.Street = spOrder.DelStreet;
            delAddr.ZipCode = spOrder.DelZipCode;
            ord.DeliveryAddress = delAddr;

            // add to the collection
            dbOrders.Add(ord);
        }
    }

    // at this point I have a List of orders I can query...
    return dbOrders;
}

同样,我意识到这看起来很麻烦,但我认为最终结果值得花费一些额外的代码。

答案 4 :(得分:0)

这根本不是很有效,但是如果其他所有方法都失败了,你可以尝试从应用程序中调用两个程序来获取发票地址,然后再使用另一个来获取发货地址。