Improperly created an Apply Join - linq

Given the following sqlite table
CREATE TABLE `ComponentNameLookup` (
`Id` INTEGER PRIMARY KEY AUTOINCREMENT,
`ComponentId` INTEGER NOT NULL,
`ComponentName` TEXT NOT NULL,
`Culture` TEXT
);
insert into ComponentNameLookup
(Id,ComponentId,ComponentName,Culture)
values
(1, 0, 'Logger', NULL ),
(2, 1, 'Transport', NULL ),
(3, 2, 'Error Handler', NULL ),
(4, 3, 'Persistance', NULL ),
(5, 0, 'Registrador', 'es-ES'),
(6, 1, 'Transporte', 'es' ),
(7, 2, 'Controlador de errores', 'es-ES'),
(8, 3, 'Persistencia', 'es-ES'),
(9, 1, 'Транспорт', 'ru' ),
(10, 2, 'Обработчик ошибок', 'ru-RU')
And the Linq Query
void Main()
{
string cultureString = Thread.CurrentThread.CurrentCulture.Name;
string languageName = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
cultureString = "es-ES";
languageName = "es";
var localized = context.ComponentNameLookups
.Where(x => x.Culture == cultureString || x.Culture == languageName || x.Culture == null)
.GroupBy(c => c.ComponentId)
.Select(g => new KeyValue<int?, string>{
Key = g.Key,
Value = g
.OrderByDescending(x => x.Culture.Length)
.Select(c => c.ComponentName)
.FirstOrDefault(),
})
;
localized.ToArray();
}
public class KeyValue<T1, T2> {
public T1 Key;
public T2 Value;
}
I get the error
System.Data.Entity.Core.EntityCommandCompilationException: An error occurred while preparing the command definition. See the inner exception for details. ---> System.NotSupportedException: APPLY joins are not supported
Something like this shouldn't need to be an APPLY JOIN. Is there any other way I can do this query using LINQ?

I can't test on SqlLite, but the query has (although different) issue with MySQL (it runs fine with SqlServer), so you can try replacing
.OrderByDescending(x => x.Culture.Length)
with
.Where(c => c.Culture.Length == g.Max(e => e.Culture.Length))
which fixes MySQL issue and eliminates the OUTER APPLY from SqlServer query, so it might work for SqlLite as well.

Related

.net core linq select overload index does not work

I got a linq lambda select code that works before I added the Select index overload. Before, I got the list of records but I need the index which I use to assign a unique Id to each record. When I add with ToList(), I get an exception with no error/inner exception. Only way I can get the code to not throw an error is to use .AsEnumberable() but I need a list. I read many post that .ToList() works with the overload but I have been unsuccessful.
Here is my code and my attempt to fix this
var emps = this.DbContext.Employees
.GroupJoin(this.DbContext.Depts,
employee => employee.EmployeeId,
dept => dept.EmployeeId,
(employee, dept) => new { employee, dept }
)
.SelectMany(
employee_dept_left => employee_dept_left.dept.DefaultIfEmpty(),
(employee_dept_left, dept) => new { employee_dept_left, dept }
)
.Join(this.DbContext.Divs,
emp_emp_dept => emp_emp_dept.employee_dept_left.employee.DivId,
division => division.DivId,
(emp_emp_dept, division) => new { emp_emp_dept, division }
)
.Where(s => !string.IsNullOrEmpty(filter.selectedDiv))
.GroupBy(grouped => new
{
grouped.emp_emp_dept.employee_dept_left.employee.EmployeeId,
grouped.emp_emp_dept.employee_dept_left.employee.LastNm,
grouped.emp_emp_dept.employee_dept_left.employee.FirstNm,
grouped.emp_emp_dept.employee_dept_left.employee.DivId
})
.Select((joined, index) => new EmployeeViewModel
{
Id = index,
EmployeeId = joined.Key.EmployeeId,
LastNm = joined.Key.LastNm.Trim(),
FirstNm = joined.Key.FirstNm.Trim(),
DivisionId = joined.Key.DivId,
}).ToList();
The error message says
Could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
I tried using .AsEnumerable() instead of .ToList():
List<EmployeeViewModel> test = emps.Cast<EmployeeViewModel>().ToList();
but this throws an exception.
Any help is greatly appreciated.
Thanks in advance
Problem that this Select is not currently translatable to the SQL. You can make additional Select to solve issue with AsEnumerable().
...
.Select(joined => new
{
EmployeeId = joined.Key.EmployeeId,
LastNm = joined.Key.LastNm.Trim(),
FirstNm = joined.Key.FirstNm.Trim(),
DivisionId = joined.Key.DivisionId,
})
.AsEnumerable()
.Select((x, index) => new EmployeeViewModel
{
Id = index,
EmployeeId = x.EmployeeId,
LastNm = x.LastNm,
FirstNm = x.FirstNm,
DivisionId = x.DivisionId,
}).ToList();
And note that query is more readable in Query syntax when there are joins.
var query =
from employee in this.DbTracsContext.Employees
join dept in his.DbTracsContext.Depts on employee.EmployeeId equals dept.EmployeeId into employee_dept_left
from dept in employee_dept_left.DefaultIfEmpty()
join division in this.DbTracsContext.Depts on employee.DivisionId equals division.DivisionId
where string.IsNullOrEmpty(filter.DivisionSelection) || filter.DivisionSelection == "0" || employee.DivisionId == filter.DivisionSelection
group employee by new { employee.EmployeeId, employee.LastNm, employee.FirstNm, employee.DivisionId } into g
select new
{
EmployeeId = g.Key.EmployeeId,
LastNm = g.Key.LastNm.Trim(),
FirstNm = g.Key.FirstNm.Trim(),
DivisionId = g.Key.DivisionId,
};
var emps = query
.AsEnumerable()
.Select((x, index) => new EmployeeViewModel
{
Id = index,
EmployeeId = x.EmployeeId,
LastNm = x.LastNm,
FirstNm = x.FirstNm,
DivisionId = x.DivisionId,
}).ToList();

Issue On The Linq Query

I am trying to do a very simple task with Linq and already stuck. It's really horrible but I guess, it's better to know using this post. I've two tables. One is Products and another is Ratings. Here is the demo script:
CREATE TABLE [dbo].[Products](
[ProductID] [int] IDENTITY(1,1) PRIMARY KEY,
[ProductName] [varchar](40) NULL,
[Price] [float] NULL,
[Details] [varchar](max) NULL,
[CategoryID] [int] NULL
)
INSERT [dbo].[Products] ([ProductID], [ProductName], [Price], [Details], [CategoryID]) VALUES (1, N'Denim', 1200, NULL, 1)
INSERT [dbo].[Products] ([ProductID], [ProductName], [Price], [Details], [CategoryID]) VALUES (2, N'Denim 2', 220, NULL, 1)
INSERT [dbo].[Products] ([ProductID], [ProductName], [Price], [Details], [CategoryID]) VALUES (3, N'Pringles', 240, NULL, 2)
INSERT [dbo].[Products] ([ProductID], [ProductName], [Price], [Details], [CategoryID]) VALUES (4, N'Pringles 2', 260, NULL, 2)
INSERT [dbo].[Products] ([ProductID], [ProductName], [Price], [Details], [CategoryID]) VALUES (5, N'Pringles 3', 240, NULL, 2)
CREATE TABLE [dbo].[Ratings](
[AutoId] [int] IDENTITY(1,1) PRIMARY KEY,
[ProductId] [int] NOT NULL,
[UserRating] [float] NOT NULL
)
INSERT [dbo].[Ratings] ([AutoId], [ProductId], [UserRating]) VALUES (4, 2, 1.5)
INSERT [dbo].[Ratings] ([AutoId], [ProductId], [UserRating]) VALUES (5, 4, 2.5)
INSERT [dbo].[Ratings] ([AutoId], [ProductId], [UserRating]) VALUES (6, 1, 5)
INSERT [dbo].[Ratings] ([AutoId], [ProductId], [UserRating]) VALUES (7, 2, 2.5)
And the output should be the following:
ProductId - ProductName - User Rating
1 - Denim - 5
2 - Denim 2 - 2
3 - Pringles - 0
4 - Pringles 2 - 2.5
5 - Pringles 3 - 0
This is the rating system of a project and I am trying to get the above output using Linq. With the following Sql, I got the result:
SELECT m.ProductId, m.ProductName, ISNULL(SUM(k.UserRating) / COUNT(k.ProductId), 0) AS 'User Rating' FROM Products m
LEFT JOIN Ratings k ON k.ProductId = m.ProductID
GROUP BY m.ProductID, m.ProductName
Unfortunately, with the following Linq, I get only the rating details that exist in the Ratings table:
var con = (from c in db.Ratings
join d in db.Products on c.ProductId equals d.ProductID into ps
from rt in ps.DefaultIfEmpty()
group new { c, ps } by new { rt.ProductID, rt.ProductName } into g
select new ProductRating
{
ProductId = g.Key.ProductID,
ProductName = g.Key.ProductName,
Total = g.Sum(c => c.c.UserRating) / g.Count()
}).ToList();
Note: My goal is to get the rating details (sum of a product's rating) that don't exist in the Ratings table (Should return '0' if no data) along with the ratings details that exist.
Update 1 - Class ProductRating:
public class ProductRating {
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Total { get; set; } //Property for the rating
}
Update 2 - Class Products and Ratings:
public partial class Products
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public Nullable<double> Price { get; set; }
public string Details { get; set; }
public Nullable<int> CategoryID { get; set; }
public ICollection<Ratings> Rating { get; set; }
}
public partial class Ratings
{
public int AutoId { get; set; }
public int ProductId { get; set; }
public double UserRating { get; set; }
}
Used the following the Linq query and got this error - The specified type member 'Rating' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported:
var con = db.Products.Select(c => new ProductRating
{
ProductId = c.ProductID,
ProductName = c.ProductName,
Total = c.Rating.Average(d => (double?)d.UserRating) ?? 0
}).ToList();
Update 3: Now getting this error - error 2016: The value specified for the condition is not compatible with the type of the member and this is how I tried for the navigation property:
Note: I've modified Andrés Robinet's query and it worked though using the Join.
from c in db.Ratings
join d in db.Products on c.ProductId equals d.ProductID into ps
from rt in ps.DefaultIfEmpty()
is the LINQ equivalent of the SQL Ratings LEFT OUTER JOIN Products, i.e. exactly the opposite of your SQL query.
While the LINQ query can be modified to match the SQL query, it doesn't make sense because EF provides a much better way which totally eliminates the need of using joins in the LINQ query - the so called navigation properties (see Don’t use Linq’s Join. Navigate!).
Normally the Product class would have a collection navigation property
public ICollection<Rating> Ratings { get; set; }
representing the one to many relationship between Product and Rating, and the equivalent LINQ query using it would be simply:
var result = db.Products
.Select(p => new ProductRating
{
ProductId = p.ProductId,
ProductName = p.ProductName,
Total = p.Ratings.Average(r => (double?)r.UserRating) ?? 0
}).ToList();
Update: In case you haven't set up correctly the relationship in the entity model, you could use the manual join equivalent of the above query by replacing the navigation property accessor with group join:
var result = (
from p in db.Products
join r in db.Ratings on p.ProductId equals r.ProductId into ratings
select new ProductRating
{
ProductId = p.ProductId,
ProductName = p.ProductName,
Total = ratings.Average(r => (double?)r.UserRating) ?? 0
}).ToList();
You have to start with the Product. Also, the order of your joins in your Linq expression is the opposite than in your SQL. And, the group ... by ... needs just rt.UserRating as the value selector
var con = (from d in db.Products
join c in db.Ratings on d.ProductId equals c.ProductId into ps
from rt in ps.DefaultIfEmpty()
group new { rt.UserRating } by new { d.ProductId, d.ProductName } into g
select new ProductRating
{
ProductId = g.Key.ProductId,
ProductName = g.Key.ProductName,
Total = g.Sum(r => r.UserRating) / g.Count()
})
.ToList();
Also, this would break if g.Count() is zero. But you have a few choices anyways:
Add more complexity to the query and pray for EF to be able to translate it into SQL
select new ProductRating
{
ProductName = g.Key.ProductName,
Total = g.Count() > 0 ? (g.Sum(r => r.UserRating) / g.Count()) : 0
}
Redefine ProductRating or use a helper DTO, so that it takes Sum and Count properties (calculate the average on-demand, let's say, you can still have a Total property)
select new ProductRating
{
ProductName = g.Key.ProductName,
SumRating = g.Sum(r => r.UserRating),
CountRating = g.Count()
}
Your LINQ statement has an inner join, whereas your SQL has a left join.

linq query crossjoin groupby optimize

i have the following database-model: http://i.stack.imgur.com/gRtMD.png
the many to many relations for Kunde_Geraet/Kunde_Anwendung are in explicit Mapping-Table with additional Information.
i want to optimize the following LINQ-query:
var qkga = (from es in db.Eintrag_Systeme.Where(es => es.Eintrag_ID == id)
from kg in db.Kunde_Geraet.Where(kg => es.Geraet_ID == kg.Geraet_ID)
select new { Kunde = kg.Kunde, Geraet = es.Geraet, Anwendung = es.Anwendung })
.Union(
from es in db.Eintrag_Systeme.Where(es => es.Eintrag_ID == id)
from ka in db.Kunde_Anwendung.Where(ka => es.Anwendung_ID == ka.Anwendung_ID)
select new { Kunde = ka.Kunde, Geraet = es.Geraet, Anwendung = es.Anwendung })
.GroupBy(kga => kga.Kunde, kga => new {Geraet = kga.Geraet, Anwendung = kga.Anwendung});
it would be better, when the result is a IEnumerable(Kunde, IEnumerable(Geraet), IEnumerable(Anwendung)) without the null-Values for the union.
i try it as SQL command
select Count(es.Geraet_ID), null as Anwendung_ID
from Eintrag_Systeme es cross join Kunde_Geraet where es.Geraet_ID = Kunde_Geraet.Geraet_ID AND es.Eintrag_ID = #id
union
select null as Geraet_ID, Count(es.Anwendung_ID)
from Eintrag_Systeme es cross join Kunde_Anwendung where es.Anwendung_ID = Kunde_Anwendung.Anwendung_ID AND es.Eintrag_ID = #id
group by Kunde_ID
but don´t get the Count() of Anwendungen(Apps)/Geraete(Devices) to Lists grouped by Key Kunde(Client)
Don't use join but navigation properties:
from k in context.Kunden
select new
{
Kunde = k,
Geraete = k.Kunde_Geraete.Select(kg => kg.Geraet),
Anwendungen = k.Kunde_Anwendungen.Select(ka => ka.Anwendung)
}
Now you have a basis from which you get counts, etc.

Linq order by column, give priority to column with value

I have 3 tables,
Table Operation
Columns:
idOperation int cdLangPrimary char(2)
Table Languages
Columns:
cdLang char(2) nmLang nvarchar(10)
Table OperationLanguages
Columns: idOperation int cdLang char(2)
My code:
var jsonObject = dbContext.Operations
.Single(o => o.idOperation == idOperation)
.Languages
.Select(l => new { l.cdLang, l.nmLang });
What I was trying to do (with no success),
is Order the Languages by a-Z, but put the cdLangPrimary as the first.
I know it is possible if I create a List(or Dictionary) like so:
var languages = new List<Languages>();
var dLanguage = operation.Languages.Single(l => l.cdLang == operation.cdLangPRIMARY);
languages.Add(dLanguage);
languages.AddRange(operation.Languages.Where(l => l.cdLang != dLanguage.cdLang));
Just wondering if there is an option to the same with linq
or in a more effective way?
If I understand your question, the following should do it:
var result = dbContext.Languages
.Where(l => l.OperationId == idOperation)
.OrderByDescending(l => l.cdLang == l.Operation.cdLangPrimary)
.ThenBy(l => l.cdLang)
.Select(l => new { l.cdLang, l.nmLang });
If you don't have relationships set up in entity framework between an Operation and a Language then you can achieve the same result as follows:
var operation = dbContext.Operations
.SingleOrDefault(o => o.idOperation == idOperation);
if(operation != null)
{
var result = dbContext.Languages
.Where(l => l.OperationId == idOperation)
.OrderByDescending(l => l.cdLang == operation.cdLangPrimary)
.ThenBy(l => l.cdLang)
.Select(l => new { l.cdLang, l.nmLang });
}

Linq subselect filter

probably someone can help me with this (at least for me) complicated problem.
Lets say i have the following data (in DB)
Tab1 (id_t1): Item
(1)
(2)
(3)
Tab2 (id_t2, id_t1): Group
(4, 1)
(5, 1)
(6, 2)
(7, 3)
Tab3 (id_t3, id_t2, v): GroupField
(10, 4, 100)
(11, 4, 300)
(12, 5, 200)
(13, 6, 100)
(14, 6, 200)
(15, 7, 100)
(16, 7, 300)
Now i'd like to select all Items that include all of some specific GroupFields.
Eg. i have v = list(100,200)
and i like to get back 1,2 but not 3
1 because Group4 holds the Field10 with v=100 and Group5 holds Field12 with v=200
and 2 because Group6 holds Field13 with v=100 and Field14 with v=200
Is something like this possible in Linq? (i allready tried different ways (any/all) but without success so far.
I don't get the point how to overcome that "field can be in any Group and not all in one Group"...
I don't even know how to do this in SQL in one command without using temp-tables/cursors.
_rene
Try this:
var result =
groups.Join(fields, o => o.Id, i => i.GroupId,
(o, i) => new { Group = o, Field = i } )
.GroupBy(x => x.Group.ItemId)
.Where(x => values.All(y => x.Any(z => z.Field.Value == y)))
.Select(x => x.Key)
.Distinct();
The following classes are used:
class Group
{
public Group(int id, int itemId)
{
Id = id;
ItemId = itemId;
}
public int Id { get; set; }
public int ItemId { get; set; }
}
class GroupField
{
public GroupField(int id, int groupId, int value)
{
Id = id;
GroupId = groupId;
Value = value;
}
public int Id { get; set; }
public int GroupId { get; set; }
public int Value { get; set; }
}
and the following initialization:
var groups = new [] { new Group(4, 1), new Group(5, 1),
new Group(6, 2), new Group(7, 3) };
var fields = new [] { new GroupField(10, 4, 100),
new GroupField(11, 4, 300),
new GroupField(12, 5, 200),
new GroupField(13, 6, 100),
new GroupField(14, 6, 200),
new GroupField(15, 7, 100),
new GroupField(16, 7, 300)
};
var values = new [] { 100, 200 };

Resources