MVC EF - Inner Join - c#

I checked the questions that may already have my answer but it seems that none of them addresses the issue I have:
var semesters = db.Semesters.Where(e => e.ID == semester).ToList();
var semestersbyfaculty = db.SemestersByFaculty.Where(e => e.FacultyID == id).ToList();
How do I inner join these two queries like if I do the following in SQL:
SELECT
Fac.*, Sem.SemesterText
FROM
SemestersByFaculty AS Fac
INNER JOIN
Semesters AS Sem
ON
Fac.SemesterID = Sem.ID
WHERE id
Fac.FacultyID = id

Inner Join
The following example shows a simple inner equijoin.
var query = from fac in db.SemesterByFaculty
join sem in db.Semester on fac.SemesterID equals sem.ID
where fac.FacultyID == id
select new { Faculty = fac, SemesterText = sem.SemesterText };
For more information, see How to: Perform Inner Joins (C# Programming Guide).
UPDATE:
from comments
Models
class MyModel {
public MitModel.SemestersByFacul‌​ty Faculty{ get; set; }
public string SemesterText { get; set; }
}
class MyViewModel {
public List<MyModel> SemesterFaculties { get; set; }
}
Action:
public ActionResult SomeAction(string id) {
var query = from fac in db.SemesterByFaculty
join sem in db.Semester on fac.SemesterID equals sem.ID
where fac.FacultyID == id
select new MyModel{ Faculty = fac, SemesterTest = sem.SemesterTest };
var viewModel = new MyViewModel { SemesterFaculties = query.ToList() };
return View(viewModel);
}
View
#Model MyViewModel

Suppose you have following fields in SemestersByFaculty
class SemestersByFaculty
{
string FacultyName;
int FacultyID;
int SemesterID;
}
Note : If you have more fields in SemestersByFaculty class then you can list them in new{} in query below:
var query = semesters.Join(semestersbyfaculty,
sem => sem.ID,
fac => fac.SemesterID,
(sem, fac) =>
new { facName = fac.FacultyName, facId = fac.FacultyID,semText = sem.SemesterText }).Where(e=> e.fac.FacultyId = id);
Now , what you have got yourself is an enumerable. You can iterate over it and retrieve the values. It will be like:
foreach (var obj in query){
Console.writeln("{0}-{1}-{2}",obj.facName,obj.facId,obj.semText);
}

Here is solution with non-LINQ syntax. Also you can perform 'Where' filter on 'db.Semesters' and 'db.SemestersByFaculty' before they are passed into 'join'.
var result = db.SemestersByFaculty
.Where(
x_ => x_.FacultyID == id)
.Join(
db.Semesters.Where(x_ => x_.ID == semester),
x_ => x_.SemesterID,
x_ => x_.ID,
(x_, y_) => new
{
FacultyID = x_.ID,
SemesterID = y_.SemesterID,
Sem = y_.SemesterText
})
.ToList();

Related

Fill DTO from two distinct objects

I have the following DTO:
public int IdTableOne { get; set; }
public string ValueTableOne { get; set; }
public int IdTableTwo { get; set; }
public string ValueTableTwo { get; set; }
Also, I have two Models (TableOne & TableTwo) and I fill this models in my repository doing the following code:
return dbContext.TableOne;
At this point everything it's okay. TableOne & TableTwo are populated, but now I want to return the combination of these values into my DTO object (TableOneId is equal to TableTwoId, it's a relationship between both tables) for doing this I'm trying something like this:
public IEnumerable<TableOneAndTwoDTO> GetTableOneAndTwo()
{
List<TableOneAndTwoDTO> combination = new List<TableOneAndTwoDto>();
var t1 = myRepository.GetTableOne();
var t2 = myRepository.GetTableTwo();
var query = from p in t1
select new {
IdTableOne = p.Id,
ValueTableOne = p.Value,
};
foreach (var item in query)
{
combination.Add(new TableOneAndTwoDTO { IdTableOne = item.IdTableOne, ValueTableOne = item.ValueTableOne });
}
}
So my question is, how can I add the TableTwo values to my DTO only when IdTableOne = IdTableTwo.
You can join your table results. Something like this:
var query = from p in t1
join j in t2 on p.IdTableOne equals j.IdTableTwo
select new { p, j };
And then you can add the join values to your DTO using something like this:
foreach (var item in query)
{
combination.Add(new TableOnwAndTwoDTO { IdTableOne = item.p.IdTableOne, IdTableTwo = item.j.IdTableTwo... })
}
Just do a LINQ Join. You can avoid a lot of the ceremony from your original code by putting the whole thing into one query and then calling .ToList() before returning.
public IEnumerable<TableOneAndTwoDTO> GetTableOneAndTwo()
{
var t1 = myRepository.GetTableOne();
var t2 = myRepository.GetTableTwo();
var combination =
from p in t1
join j in t2 on p.IdTableOne equals j.IdTableTwo
select new {
IdTableOne = p.Id,
ValueTableOne = p.Value,
IdTableTwo = j.Id,
ValueTableTwo = j.Value,
};
return combination.ToList();
}

Convert SQL query to linq lambda Entity Framework Core

I need select top 10 LINQ query with group by, sum(quantity) and where clause using Entity Framework Core.
Could somebody help me please to convert this SQL code to linq lambda?
Thank you very much.
SELECT TOP 10
OrderItems.ProductName,
OrderItems.ProductId,
SUM(OrderItems.Units) AS Quantity
FROM
Orders, OrderItems
WHERE
OrderItems.OrderId = Orders.Id
AND Orders.OrderDate >= '2019-12-18 16:38:27'
AND Orders.OrderDate <= '2020-12-18 16:38:27'
AND Orders.OrderStatusId = 2
GROUP BY
ProductName, OrderItems.ProductId
ORDER BY
Quantity DESC
I tried this EF query:
var query = (from sta in _context.Set<OrderItem>().AsQueryable()
join rec in _context.Set<Order>().AsQueryable() on sta.OrderId equals rec.Id
where rec.OrderStatusId == Convert.ToInt32(orderStatusId) && rec.OrderDate >= Convert.ToDateTime(startDate) && rec.OrderDate <= Convert.ToDateTime(endDate)
group sta by new
{
sta.ProductName,
sta.ProductId
} into grp
select new OrderDto()
{
ProductName = grp.Key.ProductName,
ProductId = grp.Key.ProductId,
Quantity = grp.Max(t => t.Units),
}).OrderBy(x => x.Quantity).Take(Convert.ToInt32(top)).ToList();
I believe your statement SELECT ... FROM Orders, OrderItems... WHERE OrderItems.OrderId = Orders.Id while looking like a CROSS JOIN, ends up being optimised into an INNER JOIN.
So, assuming you've got your model set up with navigation properties you might be better off using .Include(). Apart from that I think you are pretty much there:
var query = _context.Set<OrderItem>().Include(o => o.Order)
.Where(rec => rec.Order.OrderStatusId == Convert.ToInt32(orderStatusId))
.Where(rec => rec.Order.OrderDate >= Convert.ToDateTime(startDate) && rec.Order.OrderDate <= Convert.ToDateTime(endDate))
.GroupBy(g => new { g.ProductName, g.ProductId })
.Select(grp => new OrderDto
{
ProductName = grp.Key.ProductName,
ProductId = grp.Key.ProductId,
Quantity = grp.Sum(t => t.Units)
})
.OrderBy(x => x.Quantity)
.Take(Convert.ToInt32(top));
This produces the following output:
SELECT TOP(#__p_3) [o].[ProductName], [o].[ProductId], SUM([o].[Units]) AS [Quantity]
FROM [OrderItems] AS [o]
INNER JOIN [Orders] AS [o0] ON [o].[OrderId] = [o0].[Id]
WHERE ([o0].[OrderStatusId] = #__ToInt32_0) AND (([o0].[OrderDate] >= #__ToDateTime_1) AND ([o0].[OrderDate] <= #__ToDateTime_2))
GROUP BY [o].[ProductName], [o].[ProductId]
ORDER BY SUM([o].[Units])
Suppose you can't add the navigation property to your OrderItem model, then your code seems pretty much there:
var query2 = (from sta in _context.Set<OrderItem>()
from rec in _context.Set<Order>()
where sta.OrderId == rec.Id && rec.OrderStatusId == Convert.ToInt32(orderStatusId)
&& rec.OrderDate >= Convert.ToDateTime(startDate) && rec.OrderDate <= Convert.ToDateTime(endDate)
group sta by new
{
sta.ProductName,
sta.ProductId
} into grp
select new OrderDto()
{
ProductName = grp.Key.ProductName,
ProductId = grp.Key.ProductId,
Quantity = grp.Max(t => t.Units),
}
)
.OrderBy(x => x.Quantity)
.Take(Convert.ToInt32(top));
This produces the following SQL:
SELECT TOP(#__p_3) [o].[ProductName], [o].[ProductId], MAX([o].[Units]) AS [Quantity]
FROM [OrderItems] AS [o]
CROSS JOIN [Orders] AS [o0]
WHERE ((([o].[OrderId] = [o0].[Id]) AND ([o0].[OrderStatusId] = #__ToInt32_0)) AND ([o0].[OrderDate] >= #__ToDateTime_1)) AND ([o0].[OrderDate] <= #__ToDateTime_2)
GROUP BY [o].[ProductName], [o].[ProductId]
ORDER BY MAX([o].[Units])
Here's my full test bench for reference
using Microsoft.EntityFrameworkCore
using Microsoft.EntityFrameworkCore.Query.SqlExpressions
using Microsoft.EntityFrameworkCore.Query
#region EF Core 3.1 .ToSql() helper method courtesy of https://stackoverflow.com/a/51583047/12339804
public static class IQueryableExtensions
{
public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
{
var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
var relationalCommandCache = enumerator.Private("_relationalCommandCache");
var selectExpression = relationalCommandCache.Private<SelectExpression>("_selectExpression");
var factory = relationalCommandCache.Private<IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");
var sqlGenerator = factory.Create();
var command = sqlGenerator.GetCommand(selectExpression);
string sql = command.CommandText;
return sql;
}
private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
}
#endregion
public class OrderItem
{
public int Id {get;set;}
public int OrderId {get;set;}
public int ProductName {get;set;}
public int ProductId {get;set;}
public int Units {get;set;}
public Order Order {get;set;} // added navigation property for .Include() to pick up on
}
public class Order {
public int Id {get;set;}
public int OrderStatusId {get;set;}
public DateTime OrderDate {get;set;}
}
public class OrderDto
{
public int ProductName { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
}
class Dbc : DbContext
{
public DbSet<Order> Orders {get;set;}
public DbSet<OrderItem> OrderItems {get;set;}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=.\SQLEXPRESS;Database=Test;Trusted_Connection=True");
base.OnConfiguring(optionsBuilder);
}
}
void Main()
{
var _context = new Dbc();
var orderStatusId = "2";
var top = "10";
var startDate = DateTime.Parse("2019-12-16 16:38:27");
var endDate = DateTime.Parse("2019-12-18 16:38:27");
var query = _context.Set<OrderItem>().Include(o => o.Order)
.Where(rec => rec.Order.OrderStatusId == Convert.ToInt32(orderStatusId))
.Where(rec => rec.Order.OrderDate >= Convert.ToDateTime(startDate) && rec.Order.OrderDate <= Convert.ToDateTime(endDate))
.GroupBy(g => new { g.ProductName, g.ProductId })
.Select(grp => new OrderDto
{
ProductName = grp.Key.ProductName,
ProductId = grp.Key.ProductId,
Quantity = grp.Sum(t => t.Units)
})
.OrderBy(x => x.Quantity)
.Take(Convert.ToInt32(top));
query.ToSql().Dump();
//alternatively, trying to force a cross join syntax with extra WHERE condition. This way you don't need `public Order Order {get;set;}` navigation property on `OrderItem`
var query2 = (from sta in _context.Set<OrderItem>()
from rec in _context.Set<Order>()
where sta.OrderId == rec.Id && rec.OrderStatusId == Convert.ToInt32(orderStatusId)
&& rec.OrderDate >= Convert.ToDateTime(startDate) && rec.OrderDate <= Convert.ToDateTime(endDate)
group sta by new
{
sta.ProductName,
sta.ProductId
} into grp
select new OrderDto()
{
ProductName = grp.Key.ProductName,
ProductId = grp.Key.ProductId,
Quantity = grp.Max(t => t.Units),
}
)
.OrderBy(x => x.Quantity)
.Take(Convert.ToInt32(top));
query2.ToSql().Dump();
}

C# LINQ nested select query

I have a scenario in following nested
--Orders (List)
----Products (List)
------Manufacturers (List)
FIELDS
-Name
-Address
-City
In this scenario, I would need to execute query which will filter on City of Manufacturers and returns Orders, Products & only matching city manufacturers
I tried to put following query, however I am getting all list of Products even though city doesn't match to Manufacturers.
var filteredOrders = from o in Orders
from t in o.Products
where t.Manufacturers.Any(v => v.City == "Hartford")
select o;
Or even if I change from select o to 'select t.Manufacturers' I am getting all list of Manufacturers irrespective of city filter.
Luckily I got W3school SQL sample which matches to my scenario.
https://www.w3schools.com/sql/trysql.asp?filename=trysql_op_or
SQL Query:
SELECT o.OrderId, p.ProductName, s.*
FROM [Orders] o
JOIN OrderDetails od ON o.OrderId = od.OrderId AND o.orderId = 10248
JOIN Products p ON od.ProductId = p.ProductId
JOIN Suppliers s ON p.SupplierId = s.SupplierId and s.City ='Singapore'
I would flatten everything and then only filter on cities you want:
class Manufacturer
{
public string Name;
public string Address;
public string City;
}
class Product
{
public Manufacturer[] Manufacturers;
}
class Order
{
public Product[] Products;
}
static void Main(string[] args)
{
var cities = new string[] { "a", "b" };
Order[] orders = null;
orders.SelectMany(o => o.Products.SelectMany(p => p.Manufacturers.Select(m => new { o, p, m })))
.Where(g => cities.Contains(g.m.City))
.ToList();
}
Alternatively, if you want to return new Orders (because they have a different Products, it MUST point to a newly allocated Object) you could have this instead:
var newOrders = orders.Select(o => new Order()
{
Products = o.Products
.Select(p => new Product()
{
Manufacturers = p.Manufacturers.Where(m => cities.Contains(m.City)).ToArray()
})
.Where(m => m.Manufacturers.Length > 0).ToArray()
}).Where(p => p.Products.Length > 0).ToArray();
I finally tried to put everything together and got the expected output.
var fp = orders.Select(o =>
{
o.products = o.products.Select(p =>
{
p.manufacturers.RemoveAll(m => m.City != "Hartford");
return p;
}).ToList();
return o;
});
Please suggest if anyone has better solution
You are applying your City filter wrong. It is this line.
where t.Manufacturers.Any(v => v.City == "Hartford")
Any return true, at least one of the manufacturers has City property as "Hartford" so basically your query is something like this
var filteredOrders = from o in Orders
from t in o.Products
where true//←This is the problem
select o;
What you need to do is in fact
where t.Manufacturers.City == "Hartford"
I hope this helps
Example:
var cityNames = new List<string> {"New York",
"Atlanta",
"Hartford",
"Chicago"
};
var anyResult = cityNames.Any(x=>x== "Hartford"); //TRUE
var whereResult = cityNames.Where(x => x == "Hartford"); //IEnumerable<string>, in this case only one element
I cannot think of a way which can completely avoid creating new objects, as the parent object's list property cannot be filtered directly. You can make use of the same class though.
Also I use two separate queries in order to create a new list in parent / grandparent object.
I have made a small demo to demonstrate the idea (below has equivalent code):
http://ideone.com/MO6M6t
The city I try to select is "tmp" which only under parent p3, which only belongs to grand parent g1, g3
The expected output is:
g1
p3
tmp
g3
p3
tmp
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
public class GrandParent{
public List<Parent> parentList{ get; set; }
public string name{ get; set; }
public GrandParent(string name){
this.name = name;
this.parentList = new List<Parent>();
}
}
public class Parent{
public List<Child> childList{ get; set;}
public string name{ get; set; }
public Parent(string name){
this.name = name;
this.childList = new List<Child>();
}
}
public class Child{
public string city{ get; set;}
public Child(string city){
this.city = city;
}
}
public static void Main()
{
Child c1 = new Child("ABC"), c2 = new Child("123"), c3 = new Child("tmp");
Parent p1 = new Parent("p1"), p2 = new Parent("p2"), p3 = new Parent("p3");
GrandParent g1 = new GrandParent("g1"), g2 = new GrandParent("g2"), g3 = new GrandParent("g3");
p1.childList.Add(c1); p1.childList.Add(c2);
p2.childList.Add(c2);
p3.childList.Add(c3);
g1.parentList.Add(p1); g1.parentList.Add(p2); g1.parentList.Add(p3);
g2.parentList.Add(p2);
g3.parentList.Add(p3);
List<GrandParent> repo = new List<GrandParent>{g1, g2, g3};
var filteredParents = from g in repo
from p in g.parentList
where p.childList.Any(c => c.city == "tmp")
select new Parent(p.name){
childList = p.childList.Where(c => c.city == "tmp").ToList()
};
var filteredGrandParents = from g in repo
from p in g.parentList
where filteredParents.Any(fp => fp.name == p.name)
select new GrandParent(g.name){
parentList = g.parentList.Where(pp => filteredParents.Any(fp => fp.name == pp.name)).ToList()
};
foreach(var g in filteredGrandParents){
Console.WriteLine(g.name);
foreach(var p in g.parentList){
Console.WriteLine("\t" + p.name);
foreach(var c in p.childList){
Console.WriteLine("\t\t" + c.city);
}
}
Console.WriteLine();
}
}
}

Using Join with IQueryable LINQ

I am trying to implement Inner join query on two tables opportunityProducts and Products where I am supposed to return Iqueryable element in my MVC web API service. But from below, I am not able to get result as it throws error for conversion.
public IQueryable<OpportunityProducts> GetProductsByShipID(int id)
{
IQueryable<OpportunityProducts> oppProductss =
from c in db.OpportunityProducts
from p in db.Products
where p.ProductID == c.ProductID
select new { c.Quantity,c.ProductDesc,c.RemainingQuantity, p.QtyInHand};
return oppProductss;
}
You need to fill the Type you wish to return instead of returning an anonymous type. Here since you are querying the OpportunityProducts, I think you don't have QtyInHand property. So you can either return a new type altogether or add this property.:-
IQueryable<ResultantProducts> oppProductss =
from c in db.OpportunityProducts
from p in db.Products
where p.ProductID == c.ProductID
select new ResultantProducts
{
Quantity = c.Quantity,
ProductDesc = c.ProductDesc,
RemainingQuantity = c.RemainingQuantity,
QtyInHand = p.QtyInHand
};
I see an error in your code. You should return objects of type OpportunityProducts, I mean:
public IQueryable<OpportunityProducts> GetProductsByShipID(int id)
{
IQueryable<OpportunityProducts> oppProductss = from c in db.OpportunityProducts
from p in db.Products
where p.ProductID == c.ProductID
select new OpportunityProducts // <---- THIS!
{
Quantity = c.Quantity,
ProductDesc = c.ProductDesc,
RemainingQuantity = c.RemainingQuantity,
QtyInHand = p.QtyInHand
};
return oppProductss;
}
I hope it helps you.
Regards,
Julio
I think you can create class called ResultProducts with all properties(same data type in the original table (nullable also need to put)) what you want to get. Then you can return that object.
public class ResultProducts
{
public int Quantity { get; set; }
public string ProductDesc { get; set; }
public int RemainingQuantity { get; set; }
public int QtyInHand { get; set; }
}
public IQueryable<ResultProducts> GetProductsByShipID(int id)
{
var oppProductss =from c in db.OpportunityProducts
from p in db.Products
where p.ProductID == c.ProductID
select new ResultProducts()
{
Quantity =c.Quantity,
ProductDesc= c.ProductDesc,
RemainingQuantity=c.RemainingQuantity,
QtyInHand=p.QtyInHand
};
return oppProductss ;
}
I hope this will work.

LINQ to Entities does not recognize the method, 2 repositories

I keep getting the error below on my code, and can't understand why it is having problems translating it to a query, it is pretty simple.
I have 2 repositories, Album and AlbumImage, when I fetch an album do I want a cover, that is a subselect in AlbumImages. What am I doing wrong here?
LINQ to Entities does not recognize the method
'System.Linq.IQueryable`1[Sogaard.us.Cosplay.Data.AlbumImage] Get()'
method, and this method cannot be translated into a store expression.
Album repository
public class AlbumRepository : IRepository<Album>
{
private CosplayEntities _entities;
private IRepository<AlbumImage> _imageRepository;
public AlbumRepository(CosplayEntities entities, IRepository<AlbumImage> imageRepository)
{
_entities = entities;
_imageRepository = imageRepository;
}
public IQueryable<Album> Get()
{
return (from a in _entities.Albums
select new Album()
{
Id = a.Id,
UserId = a.UserId,
Name = a.Name,
Created = a.Created,
LastEdit = a.LastEdit,
Description = a.Description,
Views = a.Views,
Location = a.Location,
Photoshoot = a.Photoshoot,
Cover = (from ai in _imageRepository.Get() where ai.AlbumId == a.Id orderby ai.Cover descending, ai.Id ascending select ai).FirstOrDefault(),
});
}
}
AlbumImage repository
public class AlbumImageRepository : IRepository<AlbumImage>
{
private CosplayEntities _entities;
public AlbumImageRepository(CosplayEntities entities)
{
_entities = entities;
}
public IQueryable<AlbumImage> Get()
{
return (from ai in _entities.AlbumImages
select new AlbumImage()
{
Id = ai.Id,
AlbumId = ai.AlbumId,
UserId = ai.UserId,
Type = ai.Type,
Width = ai.Width,
Height = ai.Height,
Description = ai.Description,
Views = ai.Views,
Uploadet = ai.Uploadet,
LastView = ai.LastView,
Thumblink = ai.Thumblink,
Imagelink = ai.Imagelink,
Cover = ai.Cover
});
}
This is the code i am getting the error on
_albumImageRepository = new AlbumImageRepository(_entities);
_albumRepository = new AlbumRepository(_entities, _albumImageRepository);
_albumImagesTagRepository = new AlbumImagesTagRepository(_entities);
....
var album = _albumRepository.Get().Where(x => x.Id == image.AlbumId).FirstOrDefault();
Update: I have commented the Cover = ... out in my IQueryable Get() so it is 2 simple select as object.
And i still get the error in something as simple as
model.Albums = (from a in _albumRepository.Get()
orderby a.Id descending
select new AlbumDisplayModel()
{
Album = a,
ImageCount = _albumImageRepository.Get().Where(x => x.AlbumId == a.Id).Count(),
User = _userRepository.Get().Where(x => x.Id == a.UserId).FirstOrDefault()
})
.Skip(AlbumsPrPage * (page - 1))
.Take(AlbumsPrPage).ToList();
Update 2: If i rewrite the IQueryable Get() to the following, do it work flawlessly, there there should really be no diffrence in how it is handled?
public IQueryable<Album> Get()
{
return (from a in _entities.Albums
select new Album()
{
Id = a.Id,
UserId = a.UserId,
Name = a.Name,
Created = a.Created,
LastEdit = a.LastEdit,
Description = a.Description,
Views = a.Views,
Location = a.Location,
Photoshoot = a.Photoshoot,
Cover = (from ai in _entities.AlbumImages where ai.AlbumId == a.Id orderby ai.Cover descending, ai.Id ascending select new AlbumImage()
{
Id = ai.Id,
AlbumId = ai.AlbumId,
UserId = ai.UserId,
Type = ai.Type,
Width = ai.Width,
Height = ai.Height,
Description = ai.Description,
Views = ai.Views,
Uploadet = ai.Uploadet,
LastView = ai.LastView,
Thumblink = ai.Thumblink,
Imagelink = ai.Imagelink,
Cover = ai.Cover
}).FirstOrDefault(),
});
}
Update 3: Did a little test, and the problem seems to be with Entity framework, se the following code, The var linqAlbum = testClass.LinqAlbumGet().ToList(); executes without any problems and return the correct data, var eeAlbum = testClass.EEAlbumGet().ToList(); fails with the exception
LINQ to Entities does not recognize the method
'System.Linq.IQueryable`1[RepositoryTest.TestAlbumCover] EEImageGet()'
method, and this method cannot be translated into a store expression.
My test script
class Program
{
static void Main(string[] args)
{
var linq = new LinqDataContext();
var ee = new NewCosplayEntities();
var testClass = new Test(linq, ee);
var linqAlbum = testClass.LinqAlbumGet().ToList();
var eeAlbum = testClass.EEAlbumGet().ToList();
}
}
public class Test
{
public NewCosplayEntities ee { get; set; }
public LinqDataContext linq { get; set; }
public Test(LinqDataContext linq, NewCosplayEntities ee)
{
this.linq = linq;
this.ee = ee;
}
public IQueryable<TestAlbum> LinqAlbumGet()
{
return from a in linq.Albums
select new TestAlbum
{
Id = a.Id,
Name = a.Name,
Cover = (from i in LinqImageGet() where i.AlbumId == a.Id select i).FirstOrDefault()
};
}
public IQueryable<TestAlbumCover> LinqImageGet()
{
return from i in linq.AlbumImages
select new TestAlbumCover()
{
Id = i.Id,
AlbumId = i.AlbumId
};
}
public IQueryable<TestAlbum> EEAlbumGet()
{
return from a in ee.Albums
select new TestAlbum
{
Id = a.Id,
Name = a.Name,
Cover = (from i in EEImageGet() where i.AlbumId == a.Id select i).FirstOrDefault()
};
}
public IQueryable<TestAlbumCover> EEImageGet()
{
return from i in ee.AlbumImages
select new TestAlbumCover()
{
Id = i.Id,
AlbumId = i.AlbumId
};
}
}
public class TestAlbum
{
public int Id { get; set; }
public string Name { get; set; }
public TestAlbumCover Cover { get; set; }
}
public class TestAlbumCover
{
public int Id { get; set; }
public int AlbumId { get; set; }
}
Your problem comes in the ItemRepository for Albumn. Specifically because _entities has no knowledge of the _imageRepository type, so it doesn't know how to translate that type into the appropriate TSQL script. You could cast the _entities.Albums.ToList() to force the IQueryable into an IEnumerable before you try to access the _ImageRepository.Get() from the scope of the hydrated object instead of directly on the database instance. Realize that you are then going to see a perf hit on the n+1 database requests for the AlbumImage child objects for each Album.
public IQueryable<Album> Get()
{
return (from a in _entities.Albums
select new Album()
{
Id = a.Id,
UserId = a.UserId,
Name = a.Name,
Created = a.Created,
LastEdit = a.LastEdit,
Description = a.Description,
Views = a.Views,
Location = a.Location,
Photoshoot = a.Photoshoot,
Cover = (from ai in _imageRepository.Get() where ai.AlbumId == a.Id orderby ai.Cover descending, ai.Id ascending select ai).FirstOrDefault(),
});
}
Ultimately, the problem is that your trying to use an ActiveRecord pattern rather than a true repository. Everything in a single IQueryable needs to be fetched through the same database context instance for parsing and tracking purposes.
Potentially its because you are wrapping the Album and AlbumImage in new references. I would remove that and do the projection after your query.
I don't think you can project into an entity and have each projection use a result from another IQueryable. If you replaced the contents of IQueryable<AlbumImage> Get() with this, it might work:
from a in _entities.Albums
join c in _imageRepository.Get() on a.Id equals c.AlbumId into acJoin
from ac in acJoin.DefaultIfEmpty()
select new Album()
{
Id = a.Id,
etc..,
etc..,
Cover = ac
}
I'm actually fairly certain that you will need to adjust this freehand query, but essentially it's joining the IQueryables, then projecting those results into your objects, instead of projecting to your objects then inserting an IQueryable into those results. Not the best explanation I know, but just look up "LINQ Left Join" or "Linq Left Outer Join" to see the syntax of what I'm describing here. Example

Categories

Resources