将具有多个左外连接和各种子句的SQL语句转换为LINQ

时间:2016-06-15 19:01:22

标签: c# sql-server entity-framework linq

我一直在尝试创建关联和导航属性以简化我的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属性的数据库中的某些值为null。我需要做的就是修复该问题,修改存储过程以将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]));
                }
            }

谢谢。

2 个答案:

答案 0 :(得分:1)

如果将Sql包装在存储过程中,则可以使用EF来调用该过程。这是一种方式:

 var searchParameter = new SqlParameter("@search", mySearchValue);
    this.Database.SqlQuery<YourEntityTypeForReturnVal>("MyProcedureName, @search", searchParameter);

您可以创建一个参数化存储过程作为Sql的包装器:

CREATE PROCEDURE MyProcedureName
    @search varchar(50)
AS
BEGIN
-- Copy and paste your existing Sql Query here, minus the variable declaration

END

有时我们作为开发人员可以做的最好(也是最难)的事情是退后一步,然后问“应该我这样做”,而不是问“如何

答案 1 :(得分:1)

我在战斗结束后抵达,但这可能是QueryFirst的工作吗?您可以将SQL粘贴到查询模板中,但它会保留在您的应用程序中(更容易进行源代码控制,版本控制,维护)。生成的类使它可以直接从您的应用程序代码中使用。