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
};
Related
I'm using DynamicLinq on my Net Core Web API. The main purpose is to select data based on multiple columns filtered by OR
This is the Model
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public string Brand { get; set; }
}
The query looks like this. (I create generic method to handle all of the tables)
public static async Task<List<T>> Query<T>(this DbContext context, Dictionary<string, string> filter) where T : class
{
var query = context.Set<T>().AsQueryable();
string whereClause = "";
foreach(var d in filter)
{
if (whereClause != "")
whereClause += " || ";
whereClause += $"{d.Key}.Contains(\"{d.Value}\")";
}
return await query.Where(whereClause).ToListAsync();
}
This code generates no errors but the where clause is skipped which means it's executed like there's no Where and resulted all the data. The result query looks like SELECT Id, Name, Brand FROM Car where it should be like SELECT Id, Name, Brand FROM Car WHERE Name LIKE '%something%' OR Brand LIKE '%something%'
[EDIT]
Trying this online example works well when running customers.AsQueryable().Where("Name.Contains(\"David\") || Name.Contains(\"Gail\")")
I tested your generic method in both Dotnet 5 & Dotnet 6. everything was work correctly. I think your problem solve if you use the latest version of Dotnet & EfCore & Dynamic LINQ.
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;
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
I have the following code. It runs fine.
In the place I have marked I'd like to write a query (I assume with LINQ) which extracts the CompanyName where the MainKey == 3028
I suspect this is trivial but I'm new to LINQ and I've looked up some basic LINQ info on MSDN and can't seem to get it to work.
namespace EntityFrameworkExperiment {
class Program {
static void Main(string[] args) {
var models = SelectTop100Models("SELECT top 100 * FROM WH.dbo.vw_DimXXX");
Console.Write("hello world");
//<<<<<<<linq query to pull out companyname when MainKey == 3028
Console.Read();
}
static IEnumerable<MyModel> SelectTop100Models(string myCommandText) {
var connectionString = ConfigurationManager.ConnectionStrings["XXX"].ConnectionString;
using(var conn = new SqlConnection(connectionString))
using(var cmd = conn.CreateCommand()) {
conn.Open();
cmd.CommandText = myCommandText;
using(var reader = cmd.ExecuteReader()) {
while(reader.Read()) {
yield return new MyModel {
MainKey = reader.GetInt32(reader.GetOrdinal("MainKey")),
ServerId = reader.GetInt32(reader.GetOrdinal("ServerId")),
CompanyId = reader.GetInt32(reader.GetOrdinal("CompanyId")),
CompanyName = reader.GetString(reader.GetOrdinal("CompanyName")),
};
}
}
}
}
}
public class MyModel {
public int MainKey { get; set; }
public int ServerId { get; set; }
public int CompanyId { get; set; }
public string CompanyName { get; set; }
}
}
Add using System.Linq
The query should be
var companyName = models
.Where(o => o.MainKey == 3028) // apply the filter
.Select(o => o.CompanyName) // tell it you only need the one property
.FirstOrDefault(); // take the first result it finds or use 'null' if the MainKey does not exist
But there is one thing you have to remember - here you are not using LINQ queries to the SQL server - instead you are retrieving all data in memory and then filtering them in .NET. What this means is that if the database contains millions of rows, they will all be pulled from the SQL server. You are applying TOP 100 but that will get you into trouble if the key 3028 is not within the first 100.
What you should be doing is creating a model using Entity Framework (or a similar tool) and then writing a query that target the classes generated by it. The good thing though is that the LINQ query will be exactly the same - it will just be translated to SQL behind the scenes.
The linq query would be.
var result = from rec in ModelOfWHData.vw_DimCasinos
where (rec.MainKey == 3028)
select rec.CompanyName
The LINQ query below will post-process the IEnumerable you're generating from the T-SQL query, returning a single matching object, or null if not found:
MyModel result = (from m in MyModel
where m.MainKey == 3028
select m).SingleOrDefault();
string companyName = result.CompanyName;
However, I suspect you would be better off using LINQ-to-SQL and actually getting LINQ to generate and execute a T-SQL query for you.
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.