Add an inner or outer join based on a parameter - c#

This is probably an easy/stupid question but is their a way to add an inner or outer join to a SQL query (with entity framework) based on data you receive?
Example
public bool method(int? typeId, int? categoryId){
var query = from o in _dbContext.SomeObjects;
//JOIN type
if(typeId != null){
//Add inner join with table types to query
//Something like:
//query += join type in _dbContext.Types on o.TypeId equals type.ID
}else{
//Add outer join with table types to query
//query += join type in _dbContext.Types on o.TypeId equals type.ID into types
// from type in types.DefaultIfEmpty()
}
//Do same for category
...
//Filters
if(typeId != null){
query += where type.ID == typeId
}
if(categoryId != null){
query += where category.ID == categoryId
}
}

I think your main issue here is simply typing. Using var to store the initial query will type it as DbSet<SomeObject>. To build queries, you need IQueryable<SomeObject>. In other words, change your initial line to:
IQueryable<SomeObject> query = from o in _dbContext.SomeObjects;
I don't use LINQ-to-SQL myself, so this may be a little off, but I think then you'd just do something like:
query = query join type in _dbContext.Types on o.TypeId equals type.ID;
I know it works with LINQ-to-Entities like:
query = query.Include(x => x.Types);

Fixed it by doing the following:
var query = from o in _dbContext.SomeObjects
join type in _dbContext.Types on o.TypeId equals type.ID
where (typeId == null || type.ID == typeId) &&

Related

Convert C# Entity Framework linq query to SQL

I am converting the C# linq query to SQL, LINQ never return the values.
But the same query I wrote in SQL returns some values. Can anyone help me find out what the issue is for my SQL query?
LINQ query:
var query = from l in _DbContext.Licenses
join lp in _DbContext.LicenseParts on l.PartNumber equals lp.PartNumber
join lpc in _DbContext.LicensePartConfigurations on lp.Id equals lpc.LicensePartId
join p in _DbContext.Products on lp.ProductId equals p.Id
join lsn in _DbContext.LicenseSerialNumbers on l.Id equals lsn.LicenseId
join lact in _DbContext.LicenseActivations on new { a = lsn.Id, b = lp.ProductId } equals new { a = lact.LicenseSerialNumberId, b = lact.ProductId }
where lact.AccountId == AccountId && JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.SubscriptionKey") !=
" " && (JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.IsConverted") == null || JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.IsConverted") == "0" || JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.IsConverted") == "false") && p.Name == "ClearPass Legacy"
select new SubscriptionKeys { SubscriptionKey = JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.SubscriptionKey"), CustomerMail = JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.CustomerMail"), CustomerName = JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.CustomerName") };
response.PageSize = pageSize;
response.PageNumber = pageNumber;
response.Model = await query.Distinct().ToListAsync();
response.ItemsCount = response.Model.Count();
SQL query:
SELECT
l.AccountId,CustomerMail,
JSON_VALUE(ActivationInfo, '$.SubscriptionKey')
FROM
Licenses l
JOIN
LicenseParts lp ON l.PartNumber = lp.PartNumber
JOIN
LicensePartConfigurations lpc ON lp.Id = lpc.LicensePartId
JOIN
Products p ON lp.ProductId = p.Id
JOIN
LicenseSerialNumbers lsn ON l.Id = lsn.LicenseId
JOIN
LicenseActivations lact ON lsn.Id = lact.LicenseSerialNumberId
AND lp.ProductId = lact.ProductId
WHERE
lact.AccountId = 'QWNjb3VudDoxNTMwNDAzMi00MWM2LTExZTktOWYzMy1kMzQxZjE5OWZlYjM='
AND JSON_VALUE(lact.ActivationInfo, '$.SubscriptionKey') != ' '
AND (JSON_VALUE(lact.ActivationInfo, '$.IsConverted') = NULL OR
JSON_VALUE(lact.ActivationInfo, '$.IsConverted') = 0 OR
JSON_VALUE(lact.ActivationInfo, '$.IsConverted') = 'false')
AND p.Name = 'ClearPass Legacy'
To start from a valid point, execute the code where it fires this linq query and use sql profiler to catch it up. That is a good way to find the exact equivalent sql statement it finally produces and executes. You need to set up a trace to sql profiler prior to execute the linq. Get the statement and then you can compare with the sql you already have.
this sql:
(JSON_VALUE(lact.ActivationInfo, '$.IsConverted') = NULL
is not equal to:
(JsonExtensions.JsonValue(lact.ActivationInfoJSON, "$.IsConverted") == null
as in first case you compare to database NULL value using '=' and this requires ansi_nulls off to work properly and it is not a good practice.
ORMs like EF Core are meant to Map Objects to Relational constructs. Instead of trying to write SQL in C# through LINQ, you should try to Map the attributes you need to entity properties.
In this case the SubscriptionKey and IsConverted fields should appear in the table itself, either as proper fields or computed columns. If that's not possible, you could use computed columns to map the SubscriptionKey and IsConverted attributes to entity properties so you can use them in queries.
In your LicenseActivation class add these properties:
public bool? IsConverted {get;set;}
public string? SubscriptionKey {get;set;}
public string? CustomerEmail {get;set;}
In your DbContext, you can specify computed columns with HasComputedColumnSql:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<LicenseActivation>()
.Property(b => b.SubscriptionKey)
.HasComputedColumnSql("JSON_VALUE(ActivationInfo, '$.SubscriptionKey')");
modelBuilder.Entity< LicenseActivations >()
.Property(b => b.IsConverted)
.HasComputedColumnSql("JSON_VALUE(ActivationInfo, '$.IsConverted')");
modelBuilder.Entity< LicenseActivations >()
.Property(b => b.CustomerEmail)
.HasComputedColumnSql("JSON_VALUE(ActivationInfo, '$.CustomerEmail')");
}
This will allow you to use the properties in LINQ queries.
You shouldn't have to use all those JOINs either. It's the ORM's job to generate JOINs from the relations between objects. If you add proper relations between the entities the query could be simplified to :
var binValue='QWNjb3VudDoxNTMwNDAzMi00MWM2LTExZTktOWYzMy1kMzQxZjE5OWZlYjM=';
var query=_dbContext.LicenseActivations
.Where(lact=>
lact.AccountId == binValue
&& (lact.IsConverted==null || !lact.IsConverted))
.Select(lact=>new {
lact.AccountId,
lact.SubscriptionKey,
lact.CustomerEmail});
or, if the AccountId fields don't hold the same data :
.Select(lact=>new {
AccountId =lact.LicensePart.License.AccountId,
lact.SubscriptionKey,
lact.CustomerEmail
});
EF Core will generate the appropriate SQL and JOIN clauses to get from LicenseActivation to License based on the relations between the entities

How to create a left join instead of inner join?

I have an SQL query which i am converting to Linq and i want to use Left Join instead of Inner Join.
I have tried DefaultIfEmpty() method but i haven't had any luck.
The Sql query:
SELECT t0.*, t1.* FROM entity AS t0
LEFT JOIN migration_logs AS t1 ON (CAST(t0.id AS CHAR) = t1.ObjectId and 'SASParty' = t1.ObjectType)
where t1.status is null || t1.Status <> '1' ORDER BY t0.id LIMIT 0, 10;
The Linq Query:
Entities
.Join(Migration_logs,
e => new { id = e.Id.ToString(), ObjectType = "SASParty" },
mlog => new { id = mlog.ObjectId, mlog.ObjectType },
(e, mlog) => new {e,mlog})
.Where(result => result.mlog.Status == null || result.mlog.Status != "1").DefaultIfEmpty().ToList()
I am using linqpad and when i execute the linq query it generates the following sql query:
SELECT t0.*
FROM entity AS t0
INNER JOIN migration_logs AS t1
ON ((CAST(t0.id AS CHAR) = t1.ObjectId) AND (#p0 = t1.ObjectType))
WHERE ((t1.Status IS NULL) OR (t1.Status <> #p1))
Some minor differences in the original query and generated sql query are there but i hope the problem statement is clear.
Any help would be appreciated.
I was able to find a solution with the linq to sql query and using into clause.
(from e in Entities
join mlog in Migration_logs
on new { id = e.Id.ToString(), ObjectType = "SASParty" }
equals new { id = mlog.ObjectId, mlog.ObjectType }
into results
from r in results.DefaultIfEmpty()
where r.Status == null || r.Status != "1"
select new
{
e
})
you want to perform the .DefaultIfEmpty() method on the quantity that you want to perform a left join onto. maybe this code snippet helps you
from e in Entities
join ml in Migration_lpgs on new { id=e.Id.ToString(), ObjectType="SASParty" } equals new { id=ml.Id.ToString(), mlog.ObjectType } into j
from e in j.DefaultIfEmpty()
where ml.Status == null || ml.Status != "1"
select e

LEFT JOIN in LINQ between dbContext and InMemory List

My tagNumbers is of List<string> type. I need to do a left join on this List with database. Currently when I start a trace on database, I see as many queries getting fired as many no of records are in this list. Also this LINQ query gives an error when using LEFT JOIN i.e. lj.DefaultIfEmpty() since l.TagId will be NULL in case of LEFT JOIN for some of the records
i.e.
SELECT * FROM tagNumbers LEFT JOIN TagCollections ON.....LEFT JOIN...
from t in tagNumbers
join tc in dbContext.TagCollections on t equals tc.TagNumber into lj
from l in lj.DefaultIfEmpty()
join m in dbContext.MapTagEntities on l.TagId equals m.TagId
select new GetItemByTagnumberResponse
{
//DO SOMETHING
}
How should I ensure only one query is fired on database no matter how long my list is.
How should I correct my LEFT JOIN from getting an exception
Error
Object reference not set to an instance of an object on join m in dbContext.MapTagEntities on l.TagId equals m.TagId l.TagId because "l" is NULL in case of LEFT JOIN where join condition is not matching.
This could be done using GroupJoin. Here is simple implementation.
// When
var results = fruitIds
.GroupJoin(db.Fruits, id => id, fruit => fruit.Id, (id, fruits) => new
{
id = id,
fruit = fruits.FirstOrDefault()
})
.GroupJoin(db.Attributes, f => f.fruit != null ? f.fruit.Id : 0, att => att.Id, (fruitContainer, attributes) => new
{
id = fruitContainer.id,
fruit = fruitContainer.fruit,
attribute = attributes.FirstOrDefault()
})
.ToList();
// Then
Assert.AreEqual(3, results.Count);
Assert.AreEqual(2, results.Where(r => r.fruit != null).Count());
Assert.AreEqual(1, results.Where(r => r.attribute != null).Count());
Sorry for fruit implementation, I had it already written, I just needed to do proper query.

Include("myTable") in Linq to Entities in Silverlight

I need help, I have a method to Access to my Orders table:
public IQueryable<Orders> GetOrders()
{
return this.ObjectContext.Orders.Include("UnitDetail");
}
it Works very well, and I can see it from the window Data Source, and can see Orders and UnitDetail from here
But I need make some considerations for the selected rows, so I made the next method:
public IQueryable<Orders> GetOpenOrders(string _iduni)
{
ObjectSet<Orders> orders = this.ObjectContext.Orders;
ObjectSet<Estatus> estatus = this.ObjectContext.Estatus;
ObjectSet<DetailUnit> units = this.ObjectContext.DetailsUnit;
ObjectSet<ServiceType> servicetype = this.ObjectContext.ServiceType;
var query =
(from o in orders
join e in estatus on o.ID equals e.IDOrder
join u in units on o.IDUni equals u.IDUni
join t in servicetype on u.IDType equals t.IDType
where o.IDUni.Equals(_iduni)
&& !estatus.Any(oe => (oe.Estatus == "CANCELADA" || oe.Estatus == "CERRADA")
&& oe.IDOrder == o.ID)
select o).Distinct();
return query.AsQueryable();
}
This show me the correct recs, but what happend? why I don't see UnitDetail, when I inspect the result UnitDetail is null, how I can do for put the Include clausule in this new method??
Thanks a lot in advance
Because you haven't put the Include in your new method anywhere.
EDITED: to remove the unused joins.
You should be able to just use Include as you did in your GetOrders method, and so have it as a part of your existing orders variable. You are not actually using those joins anywhere, are you intending to?
Like this:
public IQueryable<Orders> GetOpenOrders(string _iduni)
{
var query =
(from o in this.ObjectContext.Orders.Include("UnitDetail")
where o.IDUni.Equals(_iduni)
&& !this.ObjectContext.Estatus.Any(oe => (oe.Estatus == "CANCELADA" || oe.Estatus == "CERRADA")
&& oe.IDOrder == o.ID)
select o).Distinct();
return query.AsQueryable();
}

Why do I have do assign subquery to a variable outside of my main query

In the GetTransfers() method below I have to assign the result of GetAllocations() to a variable outside of my main query otherwise the query fails. Why do I have to do that? Is there a better way?
When the query fails I get this error:
{System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[XCBusinessLogic.Presentation.Allocation] GetAllocations()' method, and this method cannot be translated into a store expression.
This query works:
public IQueryable<Transfer> GetTransfers()
{
IQueryable<Allocation> wxyz = GetAllocations();
IQueryable<Transfer> query =
from transfer in Context.XC_TRANSFERS
//let wxyz = GetAllocations()
join trader in Context.MGRS on transfer.TRADER_ID equals trader.MGR_NO
join ssm in Context.SSM_CORES on transfer.SSM_ID equals ssm.SSM_ID
join desk in Context.XC_DESKS on transfer.DESK_ID equals desk.DESK_ID
select new Transfer
{
// snip
_AllocationList = wxyz.Where(x => x.TRANSFER_ID == transfer.TRANSFER_ID)
};
return query;
}
This query fails:
public IQueryable<Transfer> GetTransfers()
{
//IQueryable<Allocation> wxyz = GetAllocations();
IQueryable<Transfer> query =
from transfer in Context.XC_TRANSFERS
let wxyz = GetAllocations()
join trader in Context.MGRS on transfer.TRADER_ID equals trader.MGR_NO
join ssm in Context.SSM_CORES on transfer.SSM_ID equals ssm.SSM_ID
join desk in Context.XC_DESKS on transfer.DESK_ID equals desk.DESK_ID
select new Transfer
{
// snip
_AllocationList = wxyz.Where(x => x.TRANSFER_ID == transfer.TRANSFER_ID)
};
return query;
}
This query fails:
public IQueryable<Transfer> GetTransfers()
{
//IQueryable<Allocation> wxyz = GetAllocations();
IQueryable<Transfer> query =
from transfer in Context.XC_TRANSFERS
//let wxyz = GetAllocations()
join trader in Context.MGRS on transfer.TRADER_ID equals trader.MGR_NO
join ssm in Context.SSM_CORES on transfer.SSM_ID equals ssm.SSM_ID
join desk in Context.XC_DESKS on transfer.DESK_ID equals desk.DESK_ID
select new Transfer
{
// snip
_AllocationList = GetAllocations().Where(x => x.TRANSFER_ID == transfer.TRANSFER_ID)
};
return query;
}
GetAllocations Method:
public IQueryable<Allocation> GetAllocations()
{
IQueryable<Allocation> query =
from alloc in Context.XC_ALLOCATIONS
join acm in Context.ACMS on alloc.ACCT_NO equals acm.ACCT_NO
join b in Context.BUM_DETAILS.Where(x => x.FIRM_NO == 1 && x.CATEGORY_ID == 1937) on acm.ACCT_NO equals b.ACCT_NO into bumDetails
from bumDetail in bumDetails.DefaultIfEmpty()
where acm.FIRM_NO == 1
select new Allocation
{
AccountName = acm.ACCT_NAME
// snip
};
return query;
}
Linq to Entities translate everything in the query from transfer in Context.XC_TRANSFERS ... into SQL. So the only expressions that are allowed inside that query are ones that can easily be translated to SQL.
Linq to Entities cannot figure out how a .NET method like GetAllocations() works. How should it do that? There could be any form of crazy code inside a method. How could it turn that into SQL?
In your case the method actually contains another Linq to Entities query. Maybe you could copy-paste one query into the interior of the other. But I don't think that would improve your code!
So just keep the working solution you have.
You can get around the problem by using join with your method followed by an into
IQueryable<Transfer> query =
from transfer in Context.XC_TRANSFERS
join allocation in GetAllocations() on transfer.TRANSFER_ID equals allocation.TRANSFER_ID into allocationList
join trader in Context.MGRS on transfer.TRADER_ID equals trader.MGR_NO
join ssm in Context.SSM_CORES on transfer.SSM_ID equals ssm.SSM_ID
join desk in Context.XC_DESKS on transfer.DESK_ID equals desk.DESK_ID
select new Transfer
{
// snip
_AllocationList = allocationList
};
I was having a very similar issue and the answer from Aducci did it for me. This was what I was trying to do:
query = from x in query
where GetServicesQuery(db, options).Any(service => /*my criteria*/)
select x;
It was resolved by doing this as Aducci suggested:
query = from x in query
join service in GetServicesQuery(db, localOptions) on x.ID equals service.ID into services
where services.Any(service => /*my criteria*/)
select x;
I am posting this solution because my case was different from above (needing the subquery in the where not the select). If anyone ever stumbles onto this thread with the same issue as me, hopefully this will save them some searching around.
This one was stressing me out since GetServicesQuery has a lot of criteria that I don't want to keep repeating.

Categories

Resources