【发布时间】:2016-06-15 19:01:21
【问题描述】:
我一直在尝试创建关联和导航属性来稍微简化我的 LINQ 查询;但是,由于我正在使用复杂的专有数据库,这真的很困难。
在这个过程的大部分时间里,我一直在 EF5 上使用数据库优先方法。我无法先使用数据库创建关联和导航属性,所以我尝试在 SQL Server 上创建外键,但没有成功。
下一步,我尝试使用代码优先方法创建表、关联和导航属性;然而,同样,由于我正在使用的数据库和服务器的必要性,我未能成功简化我的查询。
我最后的手段就是做我最初在这个问题上想做的事情。也就是得到这个LINQ查询:
from item in db2.OrderFormDump
join icp in db2.IcPricP on item.NODASHITEMNO equals icp.ITEMNO into icpGroup
from iG in icpGroup.DefaultIfEmpty()
join itemInfo in db2.WebItemInfo on item.ITEMNO equals itemInfo.ITEMNO into itemInfoGroup
from iIG in itemInfoGroup.DefaultIfEmpty()
join weboeordh in db2.WebOEOrdH on "brian" equals weboeordh.USER into weboeordhGroup
from wOEODHG in weboeordhGroup.DefaultIfEmpty()
join weboeordd in db2.WebOEOrdD on new { itemno = item.NODASHITEMNO, orduniq = wOEODHG.ORDUNIQ } equals new { itemno = weboeordd.ITEMNO, orduniq = weboeordd.ORDUNIQ } into weboeorddGroup
from wOEODG in weboeorddGroup.DefaultIfEmpty()
join weboeordsubmit in db2.WebOEOrdSubmit on wOEODG.ORDUNIQ equals weboeordsubmit.ORDUNIQ into weboeordsubmitGroup
from wOEOSG in weboeordsubmitGroup.DefaultIfEmpty()
join webloginaccess in db2.WebLoginAccess on "brian" equals webloginaccess.USER into webloginaccessGroup
from wLAG in webloginaccessGroup.DefaultIfEmpty()
join arcus in db2.Arcus on wLAG.CUSTID equals arcus.IDCUST into arcusGroup
from aG in arcusGroup
where (item.ALLOWINBC == "Yes" && item.ALLOWINAB == "Yes")
&& (item.BASEDESCRIPTION.Contains("dude") || item.DESCRIPTION.Contains("dude") || item.CATEGORY.Contains("dude") || item.FOODACCSPEC.Contains("dude") || item.ITEMBRAND.Contains("dude") || item.ITEMGROUP.Contains("dude") || item.ITEMNO.Contains("dude") || item.ITEMSUBTYPE.Contains("dude") || item.ITEMTYPE.Contains("dude") || iIG.INFO.Contains("dude") || item.UPC.Contains("dude") || item.UPC.Substring(2, 10).Contains("dude"))
&& (iG.CURRENCY == "CDN" && iG.DPRICETYPE == 1)
&& wOEODG.ORDUNIQ != wOEODHG.ORDUNIQ
&& iG.PRICELIST == aG.PRICLIST
orderby item.BASEDESCRIPTION
select new { item.ITEMNO, item.BASEDESCRIPTION, iIG.INFO, item.UPC, iG.UNITPRICE, item.CASEQTY, wOEODG.QTY } into x
group x by new { x.ITEMNO, x.BASEDESCRIPTION, x.INFO, x.UPC, x.UNITPRICE, x.CASEQTY, x.QTY } into items
select items;
要获得与此 SQL 查询相同的结果:
DECLARE @search varchar(50) = 'dude'
SELECT orderformdump.itemno,basedescription,info,upc,CAST(UNITPRICE AS DECIMAL(18,2)),caseqty, sum(qty) AS userquantity
FROM PPPLTD.[dbo].[ORDERFORMDUMP]
LEFT JOIN PPPLTD.dbo.ICPRICP ON replace(PPPLTD.[dbo].[ORDERFORMDUMP].[ITEMNO],'-','') = ICPRICP.ITEMNO
LEFT JOIN PPPLTD.dbo.WEBITEMINFO ON ORDERFORMDUMP.ITEMNO = WEBITEMINFO.ITEMNO
LEFT JOIN pppltd.dbo.weboeordh ON [user] = 'brian'
LEFT JOIN pppltd.dbo.weboeordd ON pppltd.dbo.WEBOEORDD.ITEMNO = REPLACE(pppltd.dbo.ORDERFORMDUMP.ITEMNO,'-','') and weboeordd.ORDUNIQ = weboeordh.orduniq
Left JOIN pppltd.dbo.weboeordsubmit ON weboeordsubmit.orduniq = weboeordd.ORDUNIQ and weboeordd.ORDUNIQ != weboeordsubmit.orduniq
LEFT JOIN PPPLTD.dbo.WEBLOGINACCESS ON WEBLOGINACCESS.[USER] = 'brian'
LEFT JOIN PPPLTD.dbo.ARCUS ON ARCUS.IDCUST = WEBLOGINACCESS.CUSTID
where (allowinbc = 'Yes' or allowinab = 'Yes')
AND [PRICELIST] = ARCUS.PRICLIST
and [CURRENCY] = 'CDN' and DPRICETYPE = 1
and (itemgroup like '%' + @search + '%' or itemtype like '%' + @search + '%' or itembrand like '%' + @search + '%'
or subcat like '%' + @search + '%' or orderformdump.description like '%' + @search + '%' or basedescription like '%'+ @search + '%'
or orderformdump.ITEMNO like '%'+@search+'%' or UPC like '%'+@search+'%' or (select top 1 1 from pppltd.dbo.ICITEMO where OPTFIELD like 'UPC%' and VALUE like '%'+@search+'%'
and ITEMNO = pppltd.dbo.ORDERFORMDUMP.itemno) is not null)
group by ORDERFORMDUMP.ITEMNO,BASEDESCRIPTION,info,UPC,CAST(UNITPRICE AS DECIMAL(18,2)),caseqty
order by basedescription
当我在 LINQPad 上执行 LINQ 时,它会生成以下 SQL:
DECLARE @p0 NVarChar(1000) = '-'
DECLARE @p1 NVarChar(1000) = ''
DECLARE @p2 VarChar(1000) = 'brian'
DECLARE @p3 NVarChar(1000) = '-'
DECLARE @p4 NVarChar(1000) = ''
DECLARE @p5 VarChar(1000) = 'brian'
DECLARE @p6 VarChar(1000) = 'Yes'
DECLARE @p7 VarChar(1000) = 'Yes'
DECLARE @p8 VarChar(1000) = '%dude%'
DECLARE @p9 VarChar(1000) = '%dude%'
DECLARE @p10 VarChar(1000) = '%dude%'
DECLARE @p11 VarChar(1000) = '%dude%'
DECLARE @p12 VarChar(1000) = '%dude%'
DECLARE @p13 VarChar(1000) = '%dude%'
DECLARE @p14 VarChar(1000) = '%dude%'
DECLARE @p15 VarChar(1000) = '%dude%'
DECLARE @p16 VarChar(1000) = '%dude%'
DECLARE @p17 VarChar(1000) = '%dude%'
DECLARE @p18 VarChar(1000) = '%dude%'
DECLARE @p19 Int = 2
DECLARE @p20 Int = 10
DECLARE @p21 VarChar(1000) = '%dude%'
DECLARE @p22 VarChar(1000) = 'CDN'
DECLARE @p23 Int = 1
-- EndRegion
SELECT [t10].[ITEMNO], [t10].[BASEDESCRIPTION], [t10].[value] AS [INFO], [t10].[UPC], [t10].[value2] AS [UNITPRICE], [t10].[CASEQTY], [t10].[value3] AS [QTY]
FROM (
SELECT [t9].[ITEMNO], [t9].[BASEDESCRIPTION], [t9].[value], [t9].[UPC], [t9].[value2], [t9].[CASEQTY], [t9].[value3]
FROM (
SELECT [t0].[ITEMNO], [t0].[BASEDESCRIPTION], [t2].[INFO] AS [value], [t0].[UPC], [t1].[UNITPRICE] AS [value2], [t0].[CASEQTY], [t5].[QTY] AS [value3], [t0].[ALLOWINBC], [t0].[ALLOWINAB], [t0].[DESCRIPTION], [t0].[CATEGORY], [t0].[FOODACCSPEC], [t0].[ITEMBRAND], [t0].[ITEMGROUP], [t0].[ITEMSUBTYPE], [t0].[ITEMTYPE], [t1].[CURRENCY], [t1].[DPRICETYPE], [t5].[ORDUNIQ], [t4].[ORDUNIQ] AS [ORDUNIQ2], [t1].[PRICELIST], [t8].[PRICLIST]
FROM [ORDERFORMDUMP] AS [t0]
LEFT OUTER JOIN [ICPRICP] AS [t1] ON REPLACE([t0].[ITEMNO], @p0, @p1) = [t1].[ITEMNO]
LEFT OUTER JOIN [WEBITEMINFO] AS [t2] ON [t0].[ITEMNO] = [t2].[ITEMNO]
LEFT OUTER JOIN (
SELECT [t3].[ORDUNIQ]
FROM [WEBOEORDH] AS [t3]
WHERE @p2 = [t3].[USER]
) AS [t4] ON 1=1
LEFT OUTER JOIN [WEBOEORDD] AS [t5] ON (REPLACE([t0].[ITEMNO], @p3, @p4) = [t5].[ITEMNO]) AND ([t4].[ORDUNIQ] = [t5].[ORDUNIQ])
LEFT OUTER JOIN (
SELECT [t6].[CUSTID]
FROM [WEBLOGINACCESS] AS [t6]
WHERE @p5 = [t6].[USER]
) AS [t7] ON 1=1
LEFT OUTER JOIN [ARCUS] AS [t8] ON [t7].[CUSTID] = [t8].[IDCUST]
) AS [t9]
WHERE ([t9].[ALLOWINBC] = @p6) AND ([t9].[ALLOWINAB] = @p7) AND (([t9].[BASEDESCRIPTION] LIKE @p8) OR ([t9].[DESCRIPTION] LIKE @p9) OR ([t9].[CATEGORY] LIKE @p10) OR ([t9].[FOODACCSPEC] LIKE @p11) OR ([t9].[ITEMBRAND] LIKE @p12) OR ([t9].[ITEMGROUP] LIKE @p13) OR ([t9].[ITEMNO] LIKE @p14) OR ([t9].[ITEMSUBTYPE] LIKE @p15) OR ([t9].[ITEMTYPE] LIKE @p16) OR ([t9].[value] LIKE @p17) OR ([t9].[UPC] LIKE @p18) OR (SUBSTRING([t9].[UPC], @p19 + 1, @p20) LIKE @p21)) AND ([t9].[CURRENCY] = @p22) AND ([t9].[DPRICETYPE] = @p23) AND ([t9].[ORDUNIQ] <> [t9].[ORDUNIQ2]) AND ([t9].[PRICELIST] = [t9].[PRICLIST])
GROUP BY [t9].[ITEMNO], [t9].[BASEDESCRIPTION], [t9].[value], [t9].[UPC], [t9].[value2], [t9].[CASEQTY], [t9].[value3]
) AS [t10]
ORDER BY [t10].[BASEDESCRIPTION]
更新
根据 HBomb 的回答,我决定创建一个带参数的存储过程,而不是进行多次连接:
CREATE PROCEDURE PRODUCT_PROCEDURE
@USERID VARCHAR(MAX)
AS
BEGIN
SELECT distinct datawarehouse.dbo.orderformdump.itemno, basedescription,info,upc,CAST((SELECT [UNITPRICE] FROM PPPLTD.dbo.[ICPRICP] WHERE [ITEMNO] = replace([DataWarehouse].[dbo].[ORDERFORMDUMP].[ITEMNO],'-','') AND [PRICELIST] = (select top 1 priclist from PPPLTD.dbo.ARCUS where IDCUST = (select top 1 CUSTID from PPPLTD.dbo.WEBLOGINACCESS where [USER] = @USERID)) and [CURRENCY] = 'CDN' and DPRICETYPE = 1) AS DECIMAL(18,2))as price,caseqty, qty AS userquantity FROM [DataWarehouse].[dbo].[ORDERFORMDUMP] LEFT JOIN pppltd.dbo.weboeordd ON pppltd.dbo.WEBOEORDD.ITEMNO = REPLACE(datawarehouse.dbo.ORDERFORMDUMP.ITEMNO,'-','') and orduniq not in (select orduniq from pppltd.dbo.weboeordsubmit) and WEBOEORDD.ORDUNIQ in (select orduniq from pppltd.dbo.weboeordh where [user] = @USERID) LEFT JOIN DATAWAREHOUSE.dbo.webiteminfo on webiteminfo.itemno = orderformdump.itemno where (allowinbc = 'Yes' or allowinab = 'Yes') order by BASEDESCRIPTION
END
然后我使用 Entity Framework 的 Database First 方法来添加我的存储过程,它创建了一个新的 DbContext,该方法使用在我的存储过程中设置“USERID”参数的方法:
public partial class DataWarehouseEntities : DbContext
{
public DataWarehouseEntities()
: base("name=DataWarehouseEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual ObjectResult<PRODUCT_PROCEDURE_Result> PRODUCT_PROCEDURE(string USERID)
{
var USERIDParameter = USERID != null ?
new ObjectParameter("USERID", USERID) :
new ObjectParameter("USERID", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<PRODUCT_PROCEDURE_Result>("PRODUCT_PROCEDURE", USERIDParameter);
}
}
我也试过了:
var USERIDParameter = USERID != null ? new SqlParameter("USERID", USERID) : new SqlParameter("USERID", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteStoreQuery<PRODUCT_PROCEDURE_Result>("PRODUCT_PROCEDURE @USERID", USERIDParameter);
最后,我尝试对存储过程的结果运行更简化的 LINQ 查询:
var products = db2.PRODUCT_PROCEDURE(username).Where
(item => item.basedescription.Contains(searchword)
|| item.info.Contains(searchword)
|| item.itemno.Contains(searchword)
|| item.itemno.Contains(searchword.Replace("-", ""))
|| item.upc.Contains(searchword));
但是,现在我收到 NullReferenceException,因为查询没有返回任何结果。
更新 #2
执行存储过程不会导致 NullReferenceException。问题在于 LINQ 查询。
我发现当我只有var products = db2.PRODUCT_PROCEDURE(username).ToList() 时,它会返回结果,但是一旦我尝试在其上添加where 子句,它就会返回null。
解决方案
在 HBomb 的帮助下,我解决了这个问题。首先,不是在 LINQ 中创建多个连接,或者创建关联和导航属性,而是在数据库中创建视图或存储过程然后使用其结果编写简单的 LINQ 查询要容易得多(如何做的示例以上)。
我发现我得到了我的 NullReferenceException,因为我的 info 属性的数据库中的某些值为空。为了解决这个问题,我所要做的就是修改存储过程以将info 列更改为isnull(info,'') as info。
最后,为了更好的搜索结果,我更改了查询:
var searchWords = searchword.ToLower().Split(' ');
var products = db2.PRODUCT_PROCEDURE(username).ToList()
.Where
(item => item.basedescription.ToLower().Contains(searchWords[0])
|| item.info.ToLower().Contains(searchWords[0])
|| item.itemno.Contains(searchword)
|| item.itemno.Contains(searchword.Replace("-", ""))
|| item.upc.Contains(searchword)
|| (item.price.ToString() == searchword
&& item.price.ToString() != null));
if (searchWords.Length > 1)
{
for (int x = 0; x < searchWords.Length-1; x++)
{
products = products.Where(i => i.basedescription.ToLower().Contains(searchWords[x]) || i.info.ToLower().Contains(searchWords[x]));
}
}
谢谢。
【问题讨论】:
-
你在使用实体框架吗?或者你用的是什么ORM?使用 EF,您无需手动连接,您可以使用导航属性。
-
仅供参考 - 在索引列 (item.ITEMNO.Replace("-", "")) 上使用 REPLACE 会消耗大量 CPU。最好添加一个带有连字符和索引的计算字段并在其上匹配。
-
@bkhosh2 与 EF,LINQ 直接转换为 SQL。这意味着您不能使用不属于模型的东西,或者 EF 不知道如何转换为 SQL。在您的情况下,
ICPRICP属性 看起来 像导航属性,但它不能正确映射到 EF 模型或其他东西中。此外,如果您有有效的导航属性,您仍然有类似连接的 where 子句 (item.WEBITEMINFO.ITEMNO == item.ITEMNO)。您需要在某些联接中使用NODASHITEMNO可能也会使这些特定关联难以建立。 -
您有 .edmx 文件吗?在图形模型上,您的实体(关联)之间应该有线。如果不这样做,您可以右键单击添加关联。关联可以在每一端具有导航属性,并且可以具有引用约束。不过,我不完全确定如何处理它,因为您首先是在做数据库。我自己几乎总是先做模特。
-
我在 SQL 中看到
where (allowinbc = 'Yes' or allowinab = 'Yes'),但 LINQ 版本似乎使用&&而不是||。这很容易产生重大影响。
标签: c# sql-server entity-framework linq