Here is my code the issue I have is the less than comparison in the On clause ... Since Linq doesn't allow this .... Migrating down into the where clause wont work as I am comparing one of the fields to null.
Here is the sql query (THE a.UserID= is hardcoded for now)
SELECT A.Policy, A.Comments, A.EventDTTM, A.Status, A.Reason, A.FollowUp
FROM PP_PolicyActivity A
LEFT JOIN PP_PolicyActivity B
ON(A.Policy = B.Policy AND A.EventDTTM < B.EventDTTM)
WHERE A.UserID = 'Ixxxxxx'
AND B.EventDTTM IS NULL AND a.status = 'open - Pending'
order by A.EventDTTM DESC
I need the result set from the above query as an IEnumerable list to populate a view
I'm tasked with rebuilding an old VB ASP NET that has a set of standing production databases behind it ... i don't have the option of changing the db design. I connecting to the server and database and this query was going against a table on that database.. the model also reflects the layout of the actual table.
The problem is with A.EventDTTM < B.EventDTTM - I can't move this to the where clause as I also have to deal with B.EventDTTM IS NULL in the where clause.
I need to retool the query someway so that it is 'linq' friendly
public class PolicyActivityModel
{
public string Policy { get; set; }
public int PolicyID { get; set; }
public string Status { get; set; }
public string Reason { get; set; }
public string Comments { get; set; }
public DateTime EventDTTM { get; set; }
public string UserID { get; set; }
public DateTime FollowUp { get; set; }
}
Company policy prohibits me from showing the connection string.
I am extremely new to Linq, Any help greatly appreciated
thank you
You can use the navigation property after you get the policy from the database.
var policy = DbContext.First(x => x.Id == 1000);
var otherPolicies = policy.ConnectedPolicies.Where(p => ...);
It's weird being a self-join but this is the most direct translation to Linq:
var query = from leftPP in PP_PolicyActivity
join rightPP in PP_PolicyActivity
on new { Policy = leftPP.Policy, EventDTTM = leftPP.EventDTTM }
equals new { Policy = rightPP.Policy, EventDTTM = rightPP.EventDTTM }
into pp from joinedRecords.DefaultIfEmpty()
where leftPP.UserId == 1
&& leftPP.EventDTTM < rightPP.DTTM)
&& rightPP.EventDTTM == null
&& leftPP.status = "open - Pending"
select new
{
leftPP,
rightPP
}
I free typed this, without models or Intellisense, thus there might be some smaller errors.
You could add the order by in that clause, but it's also still an IQUeryable, so I'd leave it.
And then, to get a List of models:
var results = query.OrderByDescending(x => x.EventDTTM).ToList();
The actual join is lines 2,3,4 and 5. It's verbose and "backwards" from SQL, and most importantly uses anonymous types. Accessing indidual properties will look something like:
results[0].leftPP.PolicyId
Related
I'm having an issue where objects are coming back as null even if they passed linq tests and I can see the values in the db, and I am stuck unsure where to go to fix this. I'm not normally a c# developer so this is new territory for me.
My table looks like
Class Meeting {
...
public virtual List<MeetingParticipant> Participants { get; set; }
...
}
Class MeetingParticipant {
public bool isOrganiser { get; set; }
public Account Participant { get; set; }
public ParticipatingState ParticipatingState { get; set; }
public string responseText { get; set; }
}
the only bind i have is: modelBuilder.Entity<Meeting>().OwnsMany(meeting => meeting.Participants);
and my linq command is:
var meetings = (from m in _context.Meetings
where m.Participants.Any(val => val.Participant.PhoneNumber == passedInPhoneNumber && val.ParticipatingState == ParticipatingState.Pending)
select m);
Annoyingly when I dig into the meetup objects that are returned, there is participants however their Account object is null. However, for the meetup to pass the linq request, it had to exist so I could compare its phone number.
What am I missing?
A simple adjustment to your Linq command should get you the results you want:
var meetings = from m in _context.Meetings.Include(val => val.Participant)
where m.Participants.Any(val => val.Participant.PhoneNumber == passedInPhoneNumber && val.ParticipatingState == ParticipatingState.Pending)
select m;
The .Include(val => val.Participant) is the magic here - it tells EF to "eagerly" load and populate that entity in your results.
Learn more about eager loading here: https://www.entityframeworktutorial.net/eager-loading-in-entity-framework.aspx
Edit: As mentioned in Beau's comment, for this to work, you need to add the following using statement:
using System.Data.Entity;
I'm trying to select some data from a table in my database using a join in a linq query, but I can't seem to grasp how to save it to the list of DTO's that I would like to return.
I've been looking at this post for directions at using the lambda expression: C# Joins/Where with Linq and Lambda but it seems like that guy is trying to accomplish something slightly different than me; I want to compare the value CPR (from the table Coworkers) and the value CPR (From the table Duties) and select all of those where the Projektname (from the table Duties) are equal to the string projektname.
What I've written so far of the method is this:
public List<CoworkerDTO> GetCoworkers(string projektname)
{
_coworkerlist = new List<CoworkerDTO>();
using (var context = new F17ST2ITS2201608275Entities())
{
var dataset =
from co in context.Coworkers
join du in context.Duties on co.CPR equals du.CPR
where du.Projektname == projektname
select new {Coworkers = co};
foreach (var element in dataset.ToList())
{
_coworkerlist.Add(element);
}
}
return _coworkerlist;
}
The CoworkerDTO looks like this:
class CoWorkerDTO
{
public string Fornavn { get; set; }
public string Efternavn { get; set; }
public int Alder { get; set; }
public string CPR { get; set; }
public decimal AntalTimer { get; set; }
}
The table Coworkers has a column that corresponds to each of the properties above, so I guess my question is how to somehow convert the selection that I get into a list of the CoworkerDTOs.
Sorry for the long post, and if my english is a bit confusing, as it's not my first language.
Thanks in advance :)
You should convert Coworkers entity into CoWorkerDTO. You can do it manually (assume properties have same names and types):
var dtos =
from co in context.Coworkers
join du in context.Duties on co.CPR equals du.CPR
where du.Projektname == projektname
select new CoWorkerDTO {
Fornavn = co.Fornavn,
Efternavn = co.Efternavn,
Alder = co.Alder,
CPR = co.CPR,
AntalTimer = co.AntalTimer
};
return dtos.ToList();
Or you can use something like AutoMapper Queryable Extensions to do that projection automatically:
Mapper.Initialize(cfg =>
cfg.CreateMap<Coworkers, CoWorkerDTO>());
And query with projection will look like
var entities =
from co in context.Coworkers
join du in context.Duties on co.CPR equals du.CPR
where du.Projektname == projektname
select co;
return entities.ProjectTo<CoWorkerDTO>().ToList();
I am having a terrible time trying to get a LINQ statement working.
I have tried using both SQL syntax and lambda following this post:
C# Joins/Where with Linq and Lambda
This is what my working SQL looks like:
SELECT ws_lookup_OccupationGroup.Code
FROM ws_lookup_OccupationGroup
INNER JOIN ws_lookup_Occupation ON
ws_lookup_OccupationGroup.Code = ws_lookup_Occupation.ws_lookup_OccupationGroup_Code
WHERE (ws_lookup_Occupation.Code = N'413')
This is my first attempt and it yields no results:
var query = from occupationGroup in db.ws_lookup_OccupationGroups
join occupations in db.ws_lookup_Occupations on occupationGroup.Code equals occupations.Code
where occupations.Code == model.Client.Client_Details_Enhanced.Occupation.Code
select new
{
OccupationGroup = occupationGroup,
Occupations = occupations
};
Here is my second attempt using Lamdba which also yields no results:
var queryLambda = db.ws_lookup_OccupationGroups
.Join(db.ws_lookup_Occupations,
occupation => occupation.Code,
occupationGroup => occupationGroup.Code,
(occupation, occupationGroup) => new
{
OCCUPATION = occupation,
OCCUPATIONGROUP = occupationGroup
})
.Where(all => all.OCCUPATION.Code == model.Client.Client_Details_Enhanced.Occupation.Code);
I just cannot see what is going wrong...
I don't know is this has any relevance but I am using Code First Entity Framework - he is my model for OccupationGroups & Occupations:
public class ws_lookup_OccupationGroup {
[Key]
[MaxLength(250)]
public string Code { get; set; }
[MaxLength(250)]
public string Name { get; set; }
public int SortOrder { get; set; }
public List<ws_lookup_Occupation> Occupations { get; set; }
}
public class ws_lookup_Occupation {
[Key]
[MaxLength(10)]
public string Code { get; set; }
[MaxLength(250)]
public string Name { get; set; }
[MaxLength(250)]
public string BarbadosMotorFactor { get; set; }
[MaxLength(250)]
public string TrinidadMotorFactor { get; set; }
[MaxLength(250)]
public string OtherRegionsMotorFactor { get; set; }
}
Instead of directly answering your question I will rather come with a suggestion of strategy. One strategy then is to add an extension method that will reveal the SQL your Entity Framework query or IQueryable will run. This can be done in such a manner that you create a unit test and do a Test Driven Development approach or TDD.
You know the SQL you want to get the expected result. It is better then to work with your EF query until you get a SQL that will deliver the result you are after. You can debug an integration test and then work your way towards the end result - the SQL you are after - written in Entity Framework Linq to Entities code.
First off, we can create the following extension method:
public static class IQueryableExtensions
{
/// <summary>
/// Shows the sql the IQueryable query will be generated into and executed on the DbServer
/// </summary>
/// <param name="query">The IQueryable to analyze</param>
/// <param name="decodeParameters">Set to true if this method should try decoding the parameters</param>
/// <remarks>This is the generated SQL query in use for Entity Framework</remarks>
public static string ShowSql(this IQueryable query, bool decodeParameters = false)
{
var objectQuery = (ObjectQuery)query;
string result = ((ObjectQuery)query).ToTraceString();
if (!decodeParameters)
return result;
foreach (var p in objectQuery.Parameters)
{
string valueString = p.Value != null ? p.Value.ToString() : string.Empty;
if (p.ParameterType == typeof(string) || p.ParameterType == typeof(DateTime))
valueString = "'" + valueString + "'";
result = result.Replace("#" +p.Name, p.Value != null ? valueString : string.Empty);
}
return result;
}
}
Then we need some integration test, sample from my own system:
[TestFixture]
public class IqueryableExtensionsTest
{
[Test]
public void QueryableReturnsSqlAndDoesNotThrow()
{
using (var dbContext = ObjectContextManager.ScopedOpPlanDataContext)
{
var operations = from operation in dbContext.Operations
where operation.Status == (int) OperationStatusDataContract.Postponed
&& operation.OperatingDate >= new DateTime(2015, 2, 12)
select operation;
string sql = operations.ShowSql();
Assert.IsNotNull(sql);
}
}
}
While you can of course use Linqpad to find the EF query and SQL you are after for, the benefit of this strategy is that you can use it inside Visual Studio for the more complex real world scenarios, you also get a better debugging experience than switching between VS and Linqpad.
If you debug such an integration test you can then watch the SQL being generated. Note that you also can do Console.WriteLine or Debug.WriteLine to watch the output if you want to run the Integration Test and not debug it.
In your SQL you are joining on the following
ws_lookup_OccupationGroup.Code = ws_lookup_Occupation.ws_lookup_OccupationGroup_Code
But in the Linq you join on
occupationGroup.Code equals occupations.Code
Depending on exactly what your entity looks like I would assume you actually need this
occupationGroup.Code = occupations.ws_lookup_OccupationGroup_Code
Based on your entity it looks like you can do the following with navigation properties instead of joins
var query = from occupationGroup in db.ws_lookup_OccupationGroups
where occupationGroup.Occupations.Any(
o => o.Code == model.Client.Client_Details_Enhanced.Occupation.Code)
select occupationGroup;
To get all the occupation groups that have at least one occupation with the desired code. Or if you just want a combination of group and occupation then you could do
var query = from occupationGroup in db.ws_lookup_OccupationGroups
from occupation in occupationGroup.Occupations
where occupation.Code == model.Client.Client_Details_Enhanced.Occupation.Code
select new
{
occupationGroup,
occupation
};
I would like to return some data from 2 related tables. I have a one to many relationship. One WebLead can have many Pricing records. I would like to return the data for the WebLead and the data for most recent record inserted into the Pricing table.
I am new to LINQ and EF. Here is what I have so far but this is only returning the WebLeads table data...What am I missing? Do I need to add a FirstOrDefault for the Pricing table?
var priceRefi = db.WebLeads.Include(p => p.Pricings)
.Where(l => l.LoanAgent.Equals(LoanAgent) && l.LeadStatus.Equals("Priced");
then to populate the view model:
PricedRefiLeads = priceRefi.ToList(),
UPDATE: I am sorry I left so much out. I updated my query to the following (LoanAgent is just a string parameter)
var priceRefi = from lead in db.WebLeads
where lead.LoanAgent == LoanAgent && lead.LeadStatus == "Priced"
select new LeadWithLastPricing()
{
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
I then want to take the results of that query and return it as a list to my view model:
var viewModel = new PipelineViewModel
{
////
PricedRefiLeads = priceRefi.ToList(),
}
I am seeing the following error on the priceRefi.ToList():
Cannot implicitly convert type
'System.Collections.Generic.List(LoanModule.ViewModels.LeadWithLastPricing)'
to 'System.Collections.Generic.List(LoanModule.Models.WebLead)
I am new to MVC. As I read this error, I understand that I must be missing something in my PipelineViewModel but I am not sure what that is.
In PipelineViewModel, I do have:
public List<WebLead> PricedRefiLeads { get; set; }
What am I missing? Forgive me if I left information out, I am struggling to wrap my head around this.
I am using a number of assumptions, for information not specifically mentioned in your question:
LoanAgent is a (local) string variable representing the agent you want to filter on.
Pricing has a field named PricingDate that is of type DateTime.
Then you can do it like this:
// I am assuming a Pricing has a DateTime field named "PricingDate"
var priceRefi =
from lead in WebLeads
where lead.LoanAgent == LoanAgent && lead.LeadStatus == "Priced"
select new {
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
Note that this returns an anonymous object as the projection result. If you want to pass this result on, you should create a class:
public class LeadWithLastPricing
{
public Lead Lead { get; set; }
public Pricing LastPricing { get; set; }
}
And do the select part like this:
// ...
select new LeadWithLastPricing() {
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
For your second error, change this:
public List<WebLead> PricedRefiLeads { get; set; }
To
public List<LeadWithLastPricing> PricedRefiLeads { get; set; }
And use it like:
var viewModel = new PipelineViewModel
{
PricedRefiLeads = priceRefi.ToList(),
}
You can try:
var query =
from l in db.WebLeads
let maxDate = l.Pricings.Max(p => p.InsertDate)
where l.LoanAgentID == someLoanAgentID
&& l.LeadStatus == "Priced"
select new { Lead = l, Pricing = l.Pricings.FirstOrDefault(x => x.InsertDate == maxDate) };
This will give objects with two properties: Lead and Pricing which are WebLead and Pricings objects. Please forgive syntax errors this is streight out of notepad.
Edit: I suppose I should tell you how to use the result object:
foreach (var MyLead in query)
{
string customerName = MyLead.Lead.CustomerName; // i.e. some property of WebLead
DateTime InsertDate = MyLead.Pricing.InsertDate; // i.e. some property of Pricings
}
You can project directly into a WebLead instead of creating a new class, by doing the following:
var priceRefi = db.WebLeads
.Include(p => p.Pricings)
.Where(l => l.LoanAgent == LoanAgent)
.Where(l => l.LeadStatus == "Priced")
.Select(lead=>new WebLead {
lead.LoanAgent,
lead.LeadStatus,
...,
Pricings=lead.Pricings.OrderByDescending(x=>x.PricingDate).Take(1)});
I have a problem trying to get the count out of the following query:
var usersView = PopulateUsersView(); //usersView is an IQueryable object
var foo = usersView.Where(fields => fields.ConferenceRole.ToLower().Contains("role"));
Where UsersView is a class which is populated from an EF entity called users (refer to the first line in the code above)
This is the class definition for the UsersView class:
public class UsersView
{
public int UserId { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string PostCode { get; set; }
public string CountryName { get; set; }
public string WorkPlaceName { get; set; }
public string Gender { get; set; }
public string EMail { get; set; }
public string Company { get; set; }
public string RoleName { get; set; }
public string ConferenceRole { get; set; }
}
As I said trying to execute the line foo.Count() returns Null Exception and this might be because the ConferenceRole column allows Null in the database.
Now what I can't understand is that when I invoke the same query directly on the ObjectQuery the Count of records (i.e. invoking foo2.Count()) is returned without any exceptions.
var foo2 = entities.users.Where(fields => fields.ConferenceRole.ToLower().Contains("role"));
Is it possible to the same query above but using the IQueryable usersView object instead?
(It is crucial for me to use the usersView object rather than directly querying the entities.users entity)
EDIT
Below is the code from the PopulateUsersView method
private IQueryable<UsersView> PopulateUsersView()
{
using (EBCPRegEntities entities = new EBCPRegEntities())
{
var users = entities.users.ToList();
List<UsersView> userViews = new List<UsersView>();
foreach (user u in users)
{
userViews.Add(new UsersView()
{
UserId = u.UserId,
Title = u.Title,
Name = u.Name,
Surname = u.Surname,
Street1 = u.Street1,
Street2 = u.Street2,
City = u.City,
PostCode = u.Post_Code,
CountryName = u.country.Name,
WorkPlaceName = u.workplace.Name,
Gender = u.Gender,
EMail = u.E_Mail,
Company = u.Company,
RoleName = u.roles.FirstOrDefault().Name,
ConferenceRole = u.ConferenceRole
});
}
return userViews.AsQueryable();
}
}
Thanks
UPDATE...
Thanks guys I finally found a good answer to the difference between the IQueryable and the ObjectQuery objects.
As a solution I am checking if the ConferenceRole is null and then checking with the contains method as many of you guys have said.
My guess is that your PopulateUsersView() method is actually executing a query and returning an IQueryable Linq-to-Objects object - while the foo2 line executes the query only in the SQL layer. If this is the case, the obviously PopulateUsersView() is going to be quite an inefficient way to perform the Count
To debug this:
can you post some code from PopulateUsersView()?
can you try running both sets of code through the EF tracing provider to see what is executed in SQL? (see http://code.msdn.microsoft.com/EFProviderWrappers)
Update
#Ryan - thanks for posting the code to PopulateUsersView
Looks like my guess was right - you are doing a query which gets the whole table back into a List - and its this list that you then query further using Linq2Objects.
#ntziolis has provided one solution to your problem - by testing for null before doing the ToLower(). However, if your only requirement is to Count the non-empty items list, then I recommend you look at changing the PopulateUsersView method or changing your overall design. If all you need is a Count then it would be much more efficient to ensure that the database does this work and not the C# code. This is espeically the case if the table has lots of rows - e.g. you definitely don't want to be pulling 1000s of rows back into memory from the database.
Update 2
Please do consider optimising this and not just doing a simple != null fix.
Looking at your code, there are several lines which will cause multiple sql calls:
CountryName = u.country.Name
WorkPlaceName = u.workplace.Name
RoleName = u.roles.FirstOrDefault().Name
Since these are called in a foreach loop, then to calculate a count of ~500 users, then you will probably make somewhere around 1501 SQL calls (although some roles and countries will hopefully be cached), returning perhaps a megabyte of data in total? All this just to calculate a single integer Count?
Try to check whether ConferenceRole is null before calling a method on it:
var foo = usersView.Where(fields => fields.ConferenceRole != null
&& fields.ConferenceRole.ToLower().Contains("role"));
This will enable you to call the count method on the user view.
So why does it work against the ObjectQuery?
When executing the query against the ObjectQuery, LinqToSql is converting your query into proper sql which does not have problems with null values, something like this (it's sample markup sql only the actual query looks much different, also '=' is used rather than checking for contains):
SELECT COUNT(*) from USERS U WHERE TOLOWER(U.CONFERENCEROLE) = 'role'
The difference to the :NET code is: It will not call a method on an object but merely call a method and pass in the value, therefore no NullReference can occur in this case.
In order to confirm this you can try to force the .NET runtime to execute the SQL prior to calling the where method, by simply adding a ToList() before the .Where()
var foo2 = entities.users.ToList()
.Where(fields => fields.ConferenceRole.ToLower().Contains("role"));
This should result in the exact same error you have seen with the UserView.
And yes this will return the entire user table first, so don't use it in live code ;)
UPDATE
I had to update the answer since I c&p the wrong query in the beginning, the above points still stand though.