我们的开发策略规定所有数据库访问都是通过存储过程进行的,这在使用LINQ时会产生问题。
为了使解释更容易,下面讨论的场景有所简化。
考虑一个有2个表的数据库。
存储过程返回的结果集必须重命名与地址相关的列,以便发票和传递地址彼此不同。
OrderID InvAddrID DelAddrID InvStreet DelStreet InvZipCode DelZipCode
1 27 46 Main St Back St abc123 xyz789
但是,这意味着LINQ不知道如何处理结果集中的这些列,因为它们不再匹配Address实体中的属性名称。
令人沮丧的是,似乎无法定义哪些结果集列映射到哪些实体属性,即使可能(在某种程度上)将实体属性映射到插入/的存储过程参数更新操作。
还有其他人有同样的问题吗?
我认为从架构的角度来看,这是一个相对常见的场景,但存储过程似乎是这里的关键因素。
答案 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)
这根本不是很有效,但是如果其他所有方法都失败了,你可以尝试从应用程序中调用两个程序来获取发票地址,然后再使用另一个来获取发货地址。