im using Entity Framework designer first and I need to create custom Model Objects starting from the db objects.
I don't want to use IEnumerable cause it will query too many fields.
The goal is to remove the inner select within this function:
using (var db = new dbEntities())
{
var departments= db.departments
.Include(p => p.employee)
.Where(...)
.Select(p => new CustomDepartmentModel()
{
ID = p.ID,
Employees = p.employee
.Select(q => new CustomEmployeeModel()
{
ID = q.ID,
Name= q.Name
}).ToList()
});
return departments.ToList();
}
by using this function:
public static IQueryable<CustomEmployeeModel> ToModel(this IQueryable<employee> Employee)
{
return Employee.Select(u => new CustomEmployeeModel()
{
ID = u.ID,
Name = u.Name
});
}
But I always get the error: "LINQ to Entities does not recognize the method ToModel".
I did try to use it in these ways without luck:
Employees = p.employee.AsQueryable().ToModel().ToList() //1
Employees = db.Entry(p).Collection(f => f.employee).Query().ToModel().ToList() //2
I think that I need to use something like this:
public static System.Linq.Expressions.Expression<Func<IQueryable<employee>, IQueryable<CustomEmployeeModel>>> ToModel()
{
return p => p.Select(u => new CustomEmployeeModel()
{
ID = u.ID,
Name = u.Name
});
}
but I really can't figure out how to use it.
I think that I need to use something like this: [snip] but I really can't figure out how to use it.
That's exactly what you need, but you also need LINQKit to make the query work. With it, your code would look like this:
var toModel = ToModel();
var departments2 = db.departments
.AsExpandable()
.Include(p => p.employee)
.Where(p => true)
.Select(p => new CustomDepartmentModel()
{
ID = p.ID,
Employees = toModel.Invoke(p.employee).ToList()
});
The problem is that LINQ to Entities is trying to translate your ToModel() method into a SQL query (since that's what LINQ to Entities is supposed to do), and it can't find a way to do it, hence the error that you're seeing.
In order for you to call ToModel() you'll need to have the information already come in from the database, which will then make any LINQ query a LINQ to Objects query, which will be more than able to do what you are asking for. You can do this by calling ToList() before calling ToModel().
Related
I have a problem trying to reuse some subqueries. I have the following situation:
var rooms = dbContext.Rooms.Select(r => new
{
RoomId = r.Id,
Zones = r.Zones.Select(zr => zr.Zone),
Name = r.Name,
Levels = r.Levels.Select(lr => lr.Level),
IdealSetpoint = (double?)r.Group.Setpoints.First(sp => sp.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId).Setpoint??int.MinValue,
Devices = r.Devices.Select(rd => rd.Device)
}).ToList();
var tagsTypes = rooms.Select(r => r.Devices.Select(d => GetSetpointTagTypeId(d.DeviceTypeId))).ToList().SelectMany(x => x).Distinct().ToList();
predicate = predicate.And(pv => tagsTypes.Contains(pv.TagSettings.TagTypeId) &&
pv.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId);
var setpoints = valuesSubquery.Include(t=>t.TagSettings).Where(predicate).ToList();
This works fine, and generates the exact queries as wanted. The problem is that I want to have this subquery dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId to be taken from a method and not repeat it every time I need it.
I've tested it with the database, where I have values in the corresponding tables, and I've tested the query with the database without any data in the corresponding tables. It works fine with no problems or exceptions.
But when I try to extract the repeating subquery in a separate method and execute it against empty database tables (no data) the .First() statement throws error. Here is the code:
protected long GetClimaticZoneId()
{
return dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId;
}
and the query generation:
var rooms = dbContext.Rooms.Select(r => new
{
RoomId = r.Id,
Zones = r.Zones.Select(zr => zr.Zone),
Name = r.Name,
Levels = r.Levels.Select(lr => lr.Level),
IdealSetpoint = (double?)r.Group.Setpoints.First(sp => sp.ClimaticZoneId == GetClimaticZoneId()).Setpoint??int.MinValue,
Devices = r.Devices.Select(rd => rd.Device)
}).ToList();
var tagsTypes = rooms.Select(r => r.Devices.Select(d => GetSetpointTagTypeId(d.DeviceTypeId))).ToList().SelectMany(x => x).Distinct().ToList();
predicate = predicate.And(pv => tagsTypes.Contains(pv.TagSettings.TagTypeId) &&
pv.ClimaticZoneId == GetClimaticZoneId());
var setpoints = valuesSubquery.Include(t=>t.TagSettings).Where(predicate).ToList();
After execution I get InvalidOperationException "Sequence do not contain any elements" exception in the GetClimaticZoneId method:
I'm sure that I'm not doing something right.
Please help!
Regards,
Julian
As #Gert Arnold suggested, I used the GetClimaticZoneId() method to make a separate call to the database, get the Id and use it in the other queries. I gust modified the query to not generate exception when there is no data in the corresponding table:
protected long GetClimaticZoneId()
{
return dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).FirstOrDefault()?.ClimaticZoneId??0;
}
So I have an scenario where I have an Opportunity table that has customer reference and customer Id.
And Customer table that has reference to ProjectManager's table.
Now, I have opportunityId and using that id I need to get Project Manager's information.
Below is my working code,
public async Task<Object> GetProjectManagerIdAgainstCustomer(int? opportunityId)
{
var customerId = await _context.Opportunities
.Where(Opportunity => Opportunity.Id == opportunityId)
.Include(Opp => Opp.Customer)
.Select(Opp => Opp.CustomerId)
.FirstOrDefaultAsync();
var ProjectManager = await _context.Customers
.Where(Customer => Customer.Id == customerId)
.Include(Customer => Customer.ProjectManager)
.Select(x => new {
email = x.ProjectManager.ProjectManagerEmail,
fullname = x.ProjectManager.ProjectManagerName,
})
.FirstOrDefaultAsync();
return (ProjectManager);
}
Now, one of my issues is that these two queries make two trips to the database, which takes me to the main aim of the question, how to optimise it so that all of this is done within one trip and if you guys can find any other issues with this code, that would be great?
Additionaly having any related documentation would help alot.
Secondly, is there any way I could have gotten the ProjectManager object within the attached Customer object, for eg. by using include?
It seems like you are looking for a join operation.
Try the one documented here:
https://learn.microsoft.com/en-us/dotnet/csharp/linq/perform-inner-joins
Using this you can create a join query that will be executed on your database and you will only receive the final output.
I hope you have navigation property Opportunities for Customer.
public async Task<Object> GetProjectManagerIdAgainstCustomer(int? opportunityId)
{
var query =
from c in _context.Customers
from o in c.Opportunities
where o.Id == opportunityId
select new
{
email = c.ProjectManager.ProjectManagerEmail,
fullname = c.ProjectManager.ProjectManagerName,
}
return await query.FirstOrDefaultAsync();
}
Method chain syntax
public async Task<Object> GetProjectManagerIdAgainstCustomer(int? opportunityId)
{
var query = _context.Customers
.SelectMany(c => c.Opportunities.Where(o => o.Id == opportunityId, (c, o) => c)
.Select(c => new
{
email = c.ProjectManager.ProjectManagerEmail,
fullname = c.ProjectManager.ProjectManagerName,
});
return await query.FirstOrDefaultAsync();
}
I want to create a linq to sql query that will return a list of objects with a sublist that has been filtered.
It sounds easy but I'm not sure how to make this to work
Here the SQL Query which returns what I want:
select * from Texts t inner join Translations tt on t.TranslationId = tt.Id
inner join Pages p on tt.Id = p.TranslationId and tt.NeutralText = p.TitleNeutralTextId
where t.LanguageId = 1
Now I have to write this with linq.
What I've done so far is:
var query = this.Queryable() // Page entity
.AsNoTracking()
.Include(x => x.TitleTranslation.Texts);
return (from m in query
from l in m.TitleTranslation.Texts
where m.TitleTranslation.Texts.Any(l => l.LanguageId == 1)
select m);
But it didn't work because I got the sublist with all languages instead of language with id #1 only.
Thanks for helping,
David
Any specific reason you are writing query? Either you can use Eager Loading of EF to load all the child tables, Or below Linq statement can fetch the required result
var result = texts.Join(translations, t => t.TranslationId, tt => tt.Id, (t, tt) => new {t, tt})
.Join(pages, ttt => new { Id = ttt.tt.Id, NeutralTextId = ttt.tt.NeutralText }, p => new { Id = p.TranslationId, NeutralTextId = p.TitleNeutralTextId }, (ttt, p) => new {ttt, p})
.Where(tttt => tttt.ttt.t.LanguageId == 1);
Here replace texts, translations and pages with actual dbContext entities collection property.
I think you must try lime this. this will work for you .
This will be similar to sql query
One way to do this .
var result = from m in Texts
join Translations on Texts.TranslationId = Translation.Id
Join Pages on Translations.NeutralText = Pages.NeutralText
where Texts.LanguageId = 1
select m
There an other way to do this using entity framework
var result =
this.Queryable().AsNoTracking().Include(x=>x.Translations).Where(x=>x.LanguageId= 1)
I found the solution I wanted thanks to Hasnain Bukhari.
The solution was to start from the text table, assign the filter, include the desired Entity (Page) and put the results into memory (ToList()). Then select pages. It will give the result I want in the order I have to.
var query = textService.Queryable()
.AsNoTracking()
.Include(x => x.Translation.Pages)
.Where(x => x.LanguageId == languageId).ToList();
return query.SelectMany(x => x.Translation.Pages);
I'm trying to get a list of people who have at least one family member associated in the database.
FamilyMembers is a related table, tied by a one-to-many foreign key.
[one] People.PersonId --> [0 or more] FamilyMembers.PersonId
I tried doing this, but Count() doesn't seem to work like I thought it would.
I get results containing both 0 and more FamilyMembers.
public IEnumerable<Person> GetPeopleWithFamilyMembers()
{
IQueryable<Person> query = Context.Persons;
query = query.OrderBy(x => x.FamilyMembers.Count());
query = query.Where(x => x.FamilyMembers.Count() > 0);
// Execute query and return result:
return query.Select(x => x);
}
I'm really not sure what to do here :-/
I struggled for a good bit on a elegant solution to this without using expanded query form.
The only way I could achieve this in SQL was using a Group By and a Join, but I have no idea if I translated it to LINQ correctly. Please feel free to point out my mistakes.
var query = Persons
.Join(FamilyMembers,
p => p.Id,
f => f.Id,
(p,f) => new { Person = p, FamilyMembers = f }
)
.GroupBy(g => g.FamilyMembers)
.Where(w => w.FamilyMembers.Count() > 0);
.Select(x => new {
Person = x.Person,
FamilyMembers = x.Count(y => y.FamilyMembers)
}
);
I have a query over a single entity (with some navigation properties) and need to project them into a model for consumption by API clients. It looks basically like this:
repository.CreateQuery<Reviews>()
.Where(/* criteria */)
.Select(m => new
{
ID = m.ID,
Reviewers = m.IsAnonymous
? m.Reviewers.Take(1).Select(r => new { Name = "Anonymous" })
: m.Reviewers.Select(r => new { Name = r.Name })
})
LINQ to Entities fails to execute this at runtime. In the Visual Studio debugger, the exception message is "Specified method is not supported". In LinqPad, the inner exception is "The nested query is not supported. Operation1='Case' Operations2='Collect'".
Any ideas how to work around this? I'd rather not force the query to execute to get the objects in memory because the point of this conditional query is to solve a performance issue, so as far as I can tell I really need to solve this within the scope of the L2E query.
Update: the projected types aren't anonymous in my real application. I just used anonymous ones here because it was convenient to contrive the example. In similar fashion, the real queries involve a number of additional properties which mean a great deal more data coming from the mapped tables.
You can use union to do what you want:
var query1 = repository.CreateQuery<Reviews>()
.Where(/* criteria */);
var queryAnonimous = query1.Where(m=>m.IsAnonymous)
.Select(m => new
{
ID = m.ID,
Reviewers = m.Reviewers.Take(1).Select(r => new { Name = "Anonymous" })
})
var queryNotAnonymous = query1.Where(m=>!m.IsAnonymous)
.Select(m => new
{
ID = m.ID,
Reviewers = m.Reviewers.Select(r => new { Name = r.Name })
})
var unionQuery = queryAnonimous.union(queryNotAnonymous);