ServiceStack ORMLite JoinAlias on a Where clause - c#

I'm trying to add a Where clause to a table joined with a JoinAlias, but there doesn't appear to be a way to specify the JoinAlias on the where clause.
I'm trying to join to the same table multiple times, then add a variable number of where clauses to the join, based on user input:
var userFilterList = new List<Expression<Func<LocationDb, LocationAttributesDateTimeDb, bool>>>();
Expression <Func<LocationDb, LocationAttributesDateTimeDb, bool>> joinPredicate = (loc, ext) =>
loc.LocationId == ext.LocationId && ext.AttributeId == attributeId;
query = query.Join<LocationAttributesDateTimeDb>(joinPredicate, ctx.JoinAlias($"ext{attributeId}"));
foreach (var item in userFilterList)
{
query = query.Where<LocationDb, LocationAttributesDateTimeDb>(item);
}
The main problem is, there doesn't appear to be a way to add the JoinAlias onto the Where clause. If I try to run the query as is, I get an exception regarding the missing alias.
If I try the following code, I get a compile exception:
query = query.Where<LocationDb, LocationAttributesDateTimeDb>(item, ctx.JoinAlias($"ext{attributeId}"));
Is there a way to add the JoinAlias to a where clause without resorting to writing the Where clauses as manual SQL?
Or, is there an alternative method I can use to stitch my multiple requests together into the single Join predicate?

Note that in the latest v5.4.1 pre-release on MyGet JoinAlias() has been deprecated and replaced with TableAlias() which uses a different implementation that substitutes the alias whilst walking the expression tree whilst generating SQL Statements whereas JoinAlias() worked by post string substitution on the generated SQL which was more fragile.
There's no TableAlias() in WHERE statements as it wouldn't be possible to determine where the alias should be used, instead here are some examples of how to use TableAlias in WHERE conditions:
q.Where<Team, Teamuser>((t, u) => t.Id == Sql.TableAlias(u.TeamId, "Leader"));
q.Where<Teamuser>(u => Sql.TableAlias(u.Id, "Leader") == 1);
q.Where<Team, Teamuser>((t, u) => Sql.TableAlias(t.Id, q.DialectProvider.GetQuotedTableName(ModelDefinition<Team>.Definition)) == Sql.TableAlias(u.TeamId, "Leader")); // Workaround, but only works for fields, not constants
q.Where<Team, Teamuser>((user, leader) => Sql.TableAlias(user.Id, "Teamuser") < Sql.TableAlias(leader.Id, "Leader"));

Related

Is it possible to set a column alias before a Select is done, with Linq to SQL

I am new to c#, and am using Linq To SQL in Linqpad to fetch data from the database. In my database I have many columns which for legacy reasons, are names Col1, Col2, Col3 etc.
At the moment I have to remember which Column references which value, to enable me to extract data.
Instead of having to write:
Clients.Where(c => c.Col1443 == true)
I would like to be able to do something like the following:
var HasRedEyes= "Col1443";
Clients.Where(c => c.HasRedEyes == true)
Please note that I need this to translate to SQL, as I need to fetch this data from the database, this data is not localised.
You really should find a way to A. Rename your columns and B. Reduce the number of them (1,000+ is a bit excessive). It's been a while since I used LINQ to SQL but if all you need is querying abilities, could you define a view with the columns aliased instead?
That aside, one way to solve this would be to define named expressions that represent predefined filters. For example, assuming the class is named Client:
Expression<Func<Client, bool>> hasRedEyes = c => c.Col1443; // == true is implicit
var query = Clients.Where(hasRedRyes).ToList();
But note those are only composable if you AND them via successive calls* to Where:
Expression<Func<Client, bool>> hasRedEyes = c => c.Col1443;
Expression<Func<Client, bool>> hasBrownHair = c => c.Col1567 == "brown";
var query = Clients.Where(hasRedEyes).Where(hasBrownHair).ToList();
If you need an OR you'd have to define the or'd filter as its own predefined expression*, for example:
Expression<Func<Client, bool>> hasRedEyesOrBrownHair = c => c.Col1443 || c.Col1567 == "brown";
var query = Clients.Where(hasRedEyesOrBrownHair).ToList();
(* The alternative to the last bit would be to either use a tool like LINQKit or to manually use the Expression API. With the latter you'd have to take the two seperate conditions, retrieve the body of the individual lambdas expressions, use an expression visitor to replace the parameter of one of them to match the other, call Expression.Or and finally Expression.Lambda to create a new filter you could pass to Where. This same concept can be applied to and'ing as well, though it all gets a bit tricky.)

LINQ nested array and the ternary operator. The nested query is not supported. Operation1='Case' Operation2='Collect'

The following code produces the error
The nested query is not supported. Operation1='Case' Operation2='Collect'
The question is what am i doing so terribly wrong? How can i fix that?
IQueryable<Map.League> v = from ul in userLeagues
select new Map.League
{
id = ul.LeagueID,
seasons =
inc.Seasons ? (from ss in ul.Standings
where ss.LeagueID == ul.LeagueID
select new Map.Season
{
seasonId = ss.Season.SeasonId,
seasonName = ss.Season.SeasonName
}).ToList() : null,
};
Update
what i cannot follow is why this is working as a charm
seasons = (from ss in ul.Standings
where ss.LeagueID == ul.LeagueID
select new Map.Season
{
seasonId = ss.Season.SeasonId,
seasonName = ss.Season.SeasonName
}).Distinct(),
what is wrong with the ternary operator?
The exception indicates that you're using Entity Framework. Always good to mention the LINQ implementation in questions.
When LINQ runs against a SQL backend, the SQL provider tries to translate the whole statement into one SQL statement. This greatly reduces the types of operations that are supported, because SQL is far more restricted than LINQ. Note that the variable inc.Seasons should also part of the SQL statement. Now the problem is that a SQL can't return two different result set depending on a variable that's part of itself: there is always one fixed SELECT clause.
So there is a Case method in the expression in a place where it's not supported (and I guess that hence the subsequent Collect isn't supported either).
You could solve this by making the inclusion part of the where clause:
from ul in userLeagues
select new Map.League
{
id = ul.LeagueID,
seasons = from ss in ul.Standings
where inc.Seasons // here
&& ss.LeagueID == ul.LeagueID
select new Map.Season
{
seasonId = ss.Season.SeasonId,
seasonName = ss.Season.SeasonName
})
}
I think you simply can't put an if-else inside a linq query, at least not in that spot.
See this post for detailed explanation and discussion.
Oh, and specially look at the "hack" by user AyCabron, I think that could resolve your situation neatly (depending on what you exactly want and why you choose to have null pointers).
The problem is not with Linq in general but with Linq to objects.
since you are using IQueryable you expect the query to run in the DB,
in that context, you cannot use many operators including the ternary operator.
if you tried the same code using Linq to objects (i.e Enumerable) it will succeed.
see example here: working example
Error The nested query is not supported. Operation1='Case' Operation2='Collect' is generated by EF when you use null within a ? statement.
EF can not convert statements like condition ? object/list : null.
In your specific example, remove .ToList() as it will also produce error when there is no rows return. EF will automatically give you null when there is no items to select.

Buffering an expression via property in LINQ (vs Lambda)

I have a large select expression to reuse in several classes. For the DRY principle I have chosen to create a property that returns the Expression to the caller code
protected virtual Expression<Func<SezioneJoin, QueryRow>> Select
{
get
{
return sj => new QueryRow
{
A01 = sj.A.A01,
A01a = sj.A.A01a,
A01b = sj.A.A01b,
A02 = sj.A.A02,
A03 = sj.A.A03,
A11 = sj.A.A11,
A12 = sj.A.A12,
A12a = sj.A.A12a,
A12b = sj.A.A12b,
A12c = sj.A.A12c,
A21 = sj.A.A21,
A22 = sj.A.A22,
..............
Lots of assignements
};
}
}
Now I can successfully use the property if I do
var query = dataContext.entity.Join(...).Where(x => ...).Select(Select);
But the following will not compile:
from SezioneJoin sj in (
from A a in ...
join D d in ... on new { ... } equals new { ... }
where
d.D13 == "086" &&
!String.IsNullOrEmpty(a.A32) && a.A32 != "086"
orderby a.A21
orderby a.prog
select new SezioneJoin{...})
select Select
Error is
Unable to cast 'System.Linq.IQueryable<System.Linq.Expressions.Expression<System.Func<DiagnosticoSite.Data.Query.SezioneJoin,DiagnosticoSite.Data.Query.QueryRow>>>' into 'System.Linq.IQueryable<DiagnosticoSite.Data.Query.QueryRow>'
I can understand that the LINQ syntax requires the body of the select statement to be the inner type of the IQueryable that it returns, so the compiler is fooled into returning a list of expressions. With the Lambda syntax, the expression is a parameter that is either compiled in-line or returned by some other method (even dynamically!).
I would like to ask if there is any way to circumvent this and avoid defining large select expressions inline
protected virtual Expression> Select
I'd avoid using the names of any of the Linq-mapped methods (Select, Where, GroupBy, OrderBy, OrderByDescending) as member names. It works in this case, but when it causes problems by matching the definitions for those it can be confusing if you aren't in the habit of just not using those names unless you deliberately want to override Linq.
On a related note. Consider that:
from var item in source select item.Something
is equivalent to:
source.Select(item => item.Something);
Therefore:
from SezioneJoin sj in (/*…*/) select Select;
is equivalent to:
(/*…*/).Select(sj => Select);
That is, you arent' creating a query that executes the expression in Select, but one that returns the expression itself.
You should either just use the form .Select(Select) or use select sj => (Select)(sj) but that second one will (if I even have the parentheses correct to stop it clashing with Queryable.Select, I haven't tested that) call the Select property every time so is at best wasteful and at worse not going to be something a query provider can make use of, so it will fail with most linq-providers. In all, use the .Select(Select) form (and change the name).
(On a separate note, if you're going to buffer an expression, actually buffer it; create a private Expression<Func<SezioneJoin, QueryRow>> once and return it in the property's getter, rather than creating it every time).
Simply use extension method in place of last LINQ select statement:
var query = from SezioneJoin sj ... select new SezioneJoin{...});
var projection = query.Select(Select);

Linq To SQL Contains

How can I go about converting this SQL statement to LINQ:
SELECT [Content].[Content], [Content].ListOrder, [Content].ContentTypeId,
[Content].ContentId
FROM [Content] INNER JOIN
GroupContentPermission ON [Content].ContentId = GroupContentPermission.ContentId
WHERE GroupContentPermission.GroupId IN
(SELECT GroupId FROM GroupUser WHERE GroupUser.UserId = 169)
Translation to LINQ is generally pretty straightforward except for one special trick in the case of your query. You can translate your select, where, and from statements in a natural way as shown below. In the case of an IN statement though, you have to get the results from the inner subquery first, and then check if the inner subquery .Contains the value you want to check.
var groups =
(from gu in GroupUser
where gu.UserId == 169
select gu.GroupId).ToList();
var result =
from p in GroupContentPermission
join c in Content on p.ContentId equals c.ContentId
where groups.Contains(p.GroupId)
select new { c.Content, c.ListOrder, c.ContentTypeID, c.ContentId };
// result should contain the same results as the SQL query
Here are some other resources you may find helpful as well (you can find many more resources and tutorials on LINQ if you do a quick google search. There are literally thousands):
Linqer, a SQL to LINQ converter.
LinqPAD, a simple .NET/LINQ tester for rapid experimentation
ScottGu's definitive guide to Using LINQ To SQL
Related SO question: What are some good LINQ resources?, which references a tutorial called 101 LINQ Samples.
Assuming you already link the tables with foreign keys in your model (DBML/EntityFrameworks):
Contents.Where(x => x.GroupContentPermission.GroupUser.UserId == 169).Select(x => new {
x.Content,
x.ListOrder,
x.ContentTypeId,
x.ContentId })
or preferrably just grab the full Content object, and use any column you want:
var contents = Contents.Where(x => x.GroupContentPermission.GroupUser.UserId == 169).ToList();
foreach (var content in contents)
Console.Write(content.Content);

Using conditionals in Linq Programmatically

I was just reading a recent question on using conditionals in Linq and it reminded me of an issue I have not been able to resolve. When building Linq to SQL queries programmatically how can this be done when the number of conditionals is not known until runtime?
For instance in the code below the first clause creates an IQueryable that, if executed, would select all the tasks (called issues) in the database, the 2nd clause will refine that to just issues assigned to one department if one has been selected in a combobox (Which has it's selected item bound to the departmentToShow property).
How could I do this using the selectedItems collection instead?
IQueryable<Issue> issuesQuery;
// Will select all tasks
issuesQuery = from i in db.Issues
orderby i.IssDueDate, i.IssUrgency
select i;
// Filters out all other Departments if one is selected
if (departmentToShow != "All")
{
issuesQuery = from i in issuesQuery
where i.IssDepartment == departmentToShow
select i;
}
By the way, the above code is simplified, in the actual code there are about a dozen clauses that refine the query based on the users search and filter settings.
If the number of conditions is unknown then it's easier to use lambda syntax instead of query comprehension, i.e.:
IQueryable<Issue> issues = db.Issues;
if (departmentToShow != "All")
{
issues = issues.Where(i => i.IssDepartment == departmentToShow);
}
issues = issues.OrderBy(i => i.IssDueDate).ThenBy(i => i.IssUrgency);
(Assuming you want the ordering to happen after the filtering, which ought to be the case - I'm not sure if Linq will generate an optimized query if you try to do the ordering first).
If you've got a very large number of optional conditions then you can clean it up with predicates:
List<Predicate<Issue>> conditions = new List<Predicate<Issue>>();
if (departmentToShow != "All")
conditions.Add(i => i.IssDepartment == departmentToShow);
if (someOtherThing)
conditions.Add(anotherPredicate);
// etc. snip adding conditions
var issues = from i in issues
where conditions.All(c => c(i))
orderby i.IssDueDate, i.IssUrgency;
Or just use PredicateBuilder which is probably easier.

Categories