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
Related
I am using below code to join two tables based on officeId field. Its retuning 0 records.
IQueryable<Usage> usages = this.context.Usage;
usages = usages.Where(usage => usage.OfficeId == officeId);
var agencyList = this.context.Agencies.ToList();
var usage = usages.ToList();
var query = usage.Join(agencyList,
r => r.OfficeId,
a => a.OfficeId,
(r, a) => new UsageAgencyApiModel () {
Id = r.Id,
Product = r.Product,
Chain = a.Chain,
Name = a.Name
}).ToList();
I have 1000+ records in agencies table and 26 records in usage table.
I am expecting 26 records as a result with chain and name colums attached to result from agency table.
Its not returning anything. I am new to .net please guide me if I am missing anything
EDIT
#Tim Schmelter's solution works fine if I get both table context while executing join. But I need to add filter on top of usage table before applying join
IQueryable<Usage> usages = this.context.Usage;
usages = usages.Where(usage => usage.OfficeId == officeId);
var query = from a in usages
// works with this.context.usages instead of usages
join u in this.context.Agencies on a.OfficeId equals u.OfficeId
select new
{
Id = a.Id,
Product = a.Product,
Chain = u.Chain,
Name = u.Name
};
return query.ToList();
Attaching screenshot here
same join query works fine with in memory data as you see below
Both ways works fine if I add in memory datasource or both datasource directly. But not working if I add filter on usages based on officeId before applying join query
One problem ist that you load all into memory first(ToList()).
With joins i prefer query syntax, it is less verbose:
var query = from a in this.context.Agencies
join u in this.context.Usage on a.OfficeId equals u.OfficeId
select new UsageAgencyApiModel()
{
Id = u.Id,
Product = u.Product,
Chain = a.Chain,
Name = a.Name
};
List<UsageAgencyApiModel> resultList = query.ToList();
Edit: You should be able to apply the Where after the Join. If you still don't get records there are no matching:
var query = from a in this.context.Agencies
join u in this.context.Usage on a.OfficeId equals u.OfficeId
where u.OfficeId == officeId
select new UsageAgencyApiModel{ ... };
The following code can help to get the output based on the ID value.
Of course, I wrote with Lambda.
var officeId = 1;
var query = context.Agencies // your starting point - table in the "from" statement
.Join(database.context.Usage, // the source table of the inner join
agency => agency.OfficeId, // Select the primary key (the first part of the "on" clause in an sql "join" statement)
usage => usage.OfficeId , // Select the foreign key (the second part of the "on" clause)
(agency, usage) => new {Agency = agency, Usage = usage }) // selection
.Where(x => x.Agency.OfficeId == id); // where statement
Aim
I'd like to filter EF queries dynamically based on joined models (/tables in SQL thinking) as I already can with a single model.
Working Code with a Single Model
Given a searchParam I know the type of where clauses I need to do based on its searchId.
var initialSet = db.Files;
foreach (var searchParam in searchParams)
{
if (searchParam.SearchId == "Id")
{
var searchContent = (int)Convert.ChangeType(searchParam.SearchContent, typeof(int));
initialSet = initialSet.Where(m => m.Id == searchContent);
}
else if (searchParam.SearchId == "Name")
{
initialSet = initialSet.Where(m => m.Name == searchParam.SearchContent);
}
else if (searchParam.SearchId == "ImportDate")
{
var searchContent = DateTime.Parse(searchParam.SearchContent);
initialSet = initialSet.Where(m => m.ImportDate == searchContent);
}
}
return initialSet;
Problem with Joined Models
Without thinking about clauses for the moment, I have this initial join in mind.
var results = (from r1 in db.Runs
join rf1 in db.RunFiles on r1 equals rf1.Run
select new { r1, rf1 });
However when taking into account where clauses, since the join requires a select to occur, I can't simply join the tables, iterate the for loop like above and then select after. If there is a way to pass multiple prepared where clauses to a single query, I'm unaware of it.
I'd like to handle 0-to-many search parameters with these joined models. Any ideas?
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) &&
I'm trying to write a LINQ-to-entities query that will take an ICollection navigation property of my main object and attach some metadata to each of them which is determined through joining each of them to another DB table and using an aggregate function. So the main object is like this:
public class Plan
{
...
public virtual ICollection<Room> Rooms { get; set; }
}
And my query is this:
var roomData = (
from rm in plan.Rooms
join conf in context.Conferences on rm.Id equals conf.RoomId into cjConf
select new {
RoomId = rm.Id,
LastUsedDate = cjConf.Count() == 0 ? (DateTime?)null : cjConf.Max(conf => conf.EndTime)
}
).ToList();
What I want is for it to generate some efficient SQL that uses the aggregate function MAX to calculate the LastUsedDate, like this:
SELECT
rm.Id, MAX(conf.EndTime) AS LastUsedDate
FROM
Room rm
LEFT OUTER JOIN
Conference conf ON rm.Id = conf.RoomId
WHERE
rm.Id IN ('a967c9ce-5608-40d0-a586-e3297135d847', '2dd6a82d-3e76-4441-9a40-133663343d2b', 'bb302bdb-6db6-4470-a24c-f1546d3e6191')
GROUP BY
rm.id
But when I profile SQL Server it shows this query from EF:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[RoomId] AS [RoomId],
[Extent1].[ProviderId] AS [ProviderId],
[Extent1].[StartTime] AS [StartTime],
[Extent1].[EndTime] AS [EndTime],
[Extent1].[Duration] AS [Duration],
[Extent1].[ParticipantCount] AS [ParticipantCount],
[Extent1].[Name] AS [Name],
[Extent1].[ServiceType] AS [ServiceType],
[Extent1].[Tag] AS [Tag],
[Extent1].[InstantMessageCount] AS [InstantMessageCount]
FROM [dbo].[Conference] AS [Extent1]
So it is selecting everything from Conference and doing the Max() calculation in memory, which is very inefficient. How can I get EF to generate the proper SQL query with the aggregate function in?
The equivalent LINQ to Entities query which closely translates to the SQL query you are after is like this:
var roomIds = plan.Rooms.Select(rm => rm.Id);
var query =
from rm in context.Rooms
join conf in context.Conferences on rm.Id equals conf.RoomId
into rmConf from rm in rmConf.DefaultIfEmpty() // left join
where roomIds.Contains(rm.Id)
group conf by rm.Id into g
select new
{
RoomId = g.Key,
LastUsedDate = g.Max(conf => (DateTime?)conf.EndTime)
};
The trick is to start the query from EF IQueryable, thus allowing it to be fully translated to SQL, rather than from plan.Rooms as in the query in question which is IEnumerable and makes the whole query execute in memory (context.Conferences is treated as IEnumerable and causes loading the whole table in memory).
The SQL IN clause is achieved by in memory IEnumerable<Guid> and Contains method.
Finally, there is no need to check the count. SQL naturally handles nulls, all you need is to make sure to call the nullable Max overload, which is achieved with the (DateTime?)conf.EndTime cast. There is no need to check conf for null as in LINQ to Objects because LINQ to Entities/SQL handles that naturally as well (as soon the receiver variable is nullable).
Since plan.Rooms isn't IQueryable with a query provider attached, the join statement is compiled as Enumarable.Join. This means that context.Conferences is implicitly cast to IEumerable and its content is pulled into memory before other operators are applied to it.
You can fix this by not using join:
var roomIds = plan.Rooms.Select(r => r.Id).ToList();
var maxPerRoom = context.Conferences
.Where(conf => roomIds.Contains(conf.RoomId))
.GroupBy(conf => conf.RoomId)
.Select(g => new
{
RoomId = g.Key,
LastUsedDate = g.Select(conf => conf.EndTime)
.DefaultIfEmpty()
.Max()
}
).ToList();
var roomData = (
from rm in plan.Rooms
join mx in maxPerRoom on rm.Id equals mx.RoomId
select new
{
RoomId = rm.Id,
LastUsedDate = mx.LastUsedDate
}
).ToList();
This first step collects the LastUsedDate data from the context and then joins with the plan.Rooms collection in memory. This last step isn't even necessary if you're not interested in returning/displaying anything else than the room's Id, but that's up to you.
I have SQL like this:
SELECT * FROM [dbo].[INSTANCE]
INNER JOIN dbo.TIME_INSTANCE on dbo.INSTANCE.INSTANCE_ID = dbo.TIME_INSTANCE.INSTANCE_ID
where customer_id=15 and TIME_ID = 1013
And I'm trying to use Entity framework to get this. I can pull back the instances from the instances table like this:
DBContext.Instances.Where(x => x.CustomerID == id && x.StartDateTime >= dateRange).OrderBy(x => x.StartDateTime).AsQueryable();
How do I perform the join like in the SQL above so that I do an inner join on the time_instance table?
quietgrit...I assume that your looking for a Lambda that will do the same thing?
I was able to mock this up and run it against my database. I copied the names of your objects into the below query. I used <theNameOfYourDataBase>Container instead of DbContext but the jest of the Lambda is the same. Let me know if this helps.
private myDbContainer db = new myDbContainer();
var myJoin = db.INSTANCE
.Join(db.TIME_INSTANCE , i => i.PK_Category, ti => ti.FK_INSTANCE, ((i, ti) => new { INSTANCE = i,TIME_INSTANCE = ti }))
.Select(joinedStuff => new {
joinedStuff.INSTANCE.customer_id,//Fill in the rest of the properties you want in your query
joinedStuff.TIME_INSTANCE.TIME_ID
}).Where(n => n.TIME_ID == 1013 && n.customer_id == 15);//Add an .OrderBy() and or .GroupBy()
Best Wishes,
Bill