Using the Geneic Unit of Work and Repository Framework, I am trying to navigate navigation properties with Linq to Entities.
In my controller, I am using a repository pattern and unit of work. In this controller, the _repository is Websites.
Given the following three tables:
I am trying to get the Website entity, as well as a distinct list of ContentTypes.Description for all SourceUrls for a specific WebsiteID and UserId.
I can get the initial part of my data:
var website = _repository
.Query()
.Select()
.Single(u => u.UserId == userId && u.WebsiteGuid == websiteGuid);
I have had several messy attempts, including something like this:
var website = _repository
.Query()
.Select()
.Single(u => u.UserId == userId && u.WebsiteGuid == websiteGuid)
.SourceUrls.Any(s => s.ContentTypeId == s.ContentType.ContentTypeId)
.Select(new ContentType());
The framework gives me the ability to write out a SQL query through .SelectQuery(...), however I'm trying to avoid this.
I can create a new DTO and cast it to this as well.
Suggestions on how to get this Linq query to work?
Thanks.
--Update--
Trying this through the Website entity seems to be difficult as there is no direct navigation property Websites--ContentTypes.
So, Starting from SourceUrls, I tried the following:
var rep2 = _unitOfWork.Repository<SourceUrl>()
.Query(q => q.UserId == userId)
.Include(i => i.ContentType.Description.Distinct())
.Include(i => i.Website)
.Select().Where(w => w.Website.WebsiteGuid == websiteGuid).ToList();
However I'm getting this new error:
"The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties."
I also tried doing the linking through the .Select() but not quite there...
I don't have your data model to play with but this appears to be working on my local system:
using System.Linq;
using System.Data.Entity;
public class Class1
{
public Class1()
{
var website = _repository
.Where(w => w.UserId == userIdArg && w.WebsiteGuidArg == websiteGuidArg)
.
}
}
Your model may give you varying amounts of success with that Distinct() but the important things is that you can use include to make sure you get the collections loaded.
I've made this answer community wiki in case any new discoveries require a change to it.
Related
I've been applying for a while DTOs in the Repository (MVC 5 + EF 6.1) because they are the only way for me to extract partial data (selecting certain columns) from the Database. To my surprise, from some recent research, this is bad
The only solution I could think of, is using IQueryable (and let's not start with this) (Plus, it's very probable that I'll get the context disposed before mapping it to a DTO) :
The question would be, how can I retrieve partial data from the database without using IQueryable?
This is how I've been doing it without problems:
public async Task<List<ParticipantIdsOnly>> GetRegisteredParticipantsIdsOnlyAsync(int tournamentId)
{
return await _dbRepositories.TournamentRepository.Where(x => x.TournamentId == tournamentId)
.SelectMany(y => y.Participants.Where(x => x.Status == ParticipantStatus.Active)
.Select(x => new ParticipantIdsOnly { Id = x.Id, ProviderPlayerId = x.ProviderPlayerId }))
.ToListAsync();
}
BTW, if I try "newing" the Entity using a select, I get a LINQ to Entities error.
That's my code:
ProjetoTipoCargaModelo projAux =
dbContext.ProjetoTipoCargaDbSet.Find(idProjetoTipoCarga);
ICollection<ProjetoTipoCargaRegraModelo> regras =
projAux.ListaRegra.Where(x => x.Ativo).ToList();
IQueryable<ProjetoTipoCargaRegraModelo> pr =
dbContext.ProjetoTipoCargaDbSet.Select(
x => regras.FirstOrDefault(y => y.IdProjetoTipoCarga == x.IdProjetoTipoCarga));
var projetoCompleto = pr.
Include(x => x.ListaRegraLiberacaoInicioViagem).
Include(x => x.ListaRegraTecnologiaAceita).
Include(x => x.RegraAreaSombra).
Include(x => x.RegraAtuadorNecessario)
It's showing an error at first include, but I'm trying to do it on Iquerable object!
What's wrong where?
My problem is make this include in a filtered set of results.
[Edit]
Error:
Cannot convert lambda expression to type 'string' because it is not a delegate type
It's not a runtime error, it's a compilation error.
[Edit 2]
My usings:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
[Edit 3]
Answer:
ICollection<ProjetoTipoCargaRegraModelo> regras = projAux.ListaRegra.Where(x => x.Ativo).ToList();
IQueryable<ProjetoTipoCargaModelo> pr = dbContext.ProjetoTipoCargaDbSet.Where(x => x.IdProjetoTipoCarga == regras.FirstOrDefault(y => y.IdProjetoTipoCarga == x.IdProjetoTipoCarga).IdProjetoTipoCarga);
var projetoCompleto = pr.
Include(x => x.ListaRegraLiberacaoInicioViagem).
Include(x => x.ListaRegraTecnologiaAceita).
Include(x => x.RegraAreaSombra).
The .Include() method only works on ObjectQuery<TEntity>
Try:
context.EntitySet.Include(...).Select(...)
instead of:
context.EntitySet.Select(...).Include(...)
or use an extenion method like this:
public static class MyExtensions
{
public static IQueryable<TEntity> Include<TEntity>(
this IQueryable<TEntity> query, string path)
{
var efQuery = query as ObjectQuery<TEntity>;
if (efQuery == null)
return query;
return efQuery.Include(path);
}
}
or better yet, use the already available extension method that supports lambda expressions instead of strings as paths.
Also, do not use so many includes unless most are 1:1 or :1 relationships, 1: (or :) relationships greatly increase the IO from the database, resulting in bad performance.
Consider using multiple queries with .Future() to enable a single access to the database instead.
It's not related to the Includes.
You can't use regas in a LINQ-to-entities query because it's an ICollection. EF can't translate that into SQL.
Your "answer" can't possibly work with EF. The object regas is an in-memory list of Regra entities (I guess). If you use that directly in...
var pr = dbContext.ProjetoTipoCargaDbSet
.Where(x => x.IdProjetoTipoCarga == regras.FirstOrDefault(y => y.IdProjetoTipoCarga == x.IdProjetoTipoCarga).IdProjetoTipoCarga);
...you should get an exception like
Unable to create a constant value of type 'Regra'. Only primitive types or enumeration types are supported...
But, boy, what a tortuous way to get where you want to be! First you get a ProjetoTipoCargaModelo object by an idProjetoTipoCarga. Then you fetch its active Regras. Then you basically use the IdProjetoTipoCarga values of the Regras to see if one of them is equal to the original idProjetoTipoCarga and if so, you use its value to get a ProjetoTipoCargaModelo object.
If you remove all the redundancies, what's left is:
var pr = dbContext.ProjetoTipoCargaDbSet
.Where(x => x.IdProjetoTipoCarga == idProjetoTipoCarga
&& x.ListaRegra.Any(r => r.Ativo));
I you us this LINQ statement, you append your includes to pr.
So I am converting a old project with ordinary SQL queries to a ORM using the Entity Framework. So I have created database model like this:
So I had this old query which I want to translate to a linq expression
SELECT UGLINK.USERNAME
FROM GMLINK
INNER JOIN UGLINK
ON GMLINK.GROUPID = UGLINK.GROUPID
WHERE (((GMLINK.MODULEID)=%ID%))
And the problem I have is that I can't figure out how to do a join query using the objects.
Instead I have to go though the properties like this (which seems to be working):
// So this is one of the module objects that is located in a listView in the GUI
Module m = ModuleList.selectedItem as Module;
/* Now I want to fetch all the User objects that,
* via a group, is connected to a certain module */
var query = context.gmLink
.Join(context.ugLink,
gmlink => gmlink.GroupId,
uglink => uglink.GroupId,
(gmlink, uglink) => new { gmLink = gmlink, ugLink = uglink })
.Where(gmlink => gmlink.gmLink.ModuleId == m.ModuleId)
.Select(x => x.ugLink.User);
So as I said this works, but as you see I kind of have to connect the modules via the link tables properties .GroupId and .ModuleId and so on. Instead I would like to go through the objects created by EF.
I wanted to write a question a bit like this, but can't figure out how to do it, is it at all possible?
var query = context.User
.Select(u => u.ugLink
.Select(uglink => uglink.Group.gmLink
.Where(gmLink => gmLink.Module == m)));
This should be working:
var query = context.gmLink
.Where(gmlink => gmlink.ModuleId == m.ModuleId)
.SelectMany(gmlink => gmlink.Group.ugLink)
.Select(uglink => uglink.User);
It's impossible to filter gmLinks using .Where(gmlink => gmlink.Module == m) in EF, so this comparison needs to be done using identifiers. Another option is .Where(gmlink => gmlink.Module.ModuleId == m.ModuleId)
If you have lazy loading enabled, you do not need to apply specific join notation (you can access the navigation properties directly) - but the queries that are ran against SQL are inefficient (generally the results are returned in a number of different select statements).
My preference is to disable lazy loading on the context, and use .Include() notation to join tables together manually, resulting in generally more efficient queries. .Include() is used to explicitly join entities in Entity Framework.
Join() is misleading, and not appropriate for joining tables in EF.
So, to replicate this statement:
SELECT UGLINK.USERNAME
FROM GMLINK
INNER JOIN UGLINK
ON GMLINK.GROUPID = UGLINK.GROUPID
WHERE (((GMLINK.MODULEID)=%ID%))
You would use the following:
var query = context.gmLink
.Include(x => x.Group.gmLink)
.Where(x => x.ModuleId == myIdVariable)
.Select(x => new {
UserName = x.Group.ugLink.UserName
});
Assuming that your navigation properties are correctly set up. I have not tested this, so I'm not 100% on the syntax.
You should really run SQL profiler while you write and run LINQ to Entity queries against your database, so you can understand what's actually being generated and run against your database. A lot of the time, an EF query may be functioning correctly, but you may experience performance issues when deployed to a production system.
This whitepaper might help you out.
I haven't tested it, but something like this:
var users = context.User
.Where(x => x.ugLink
.Any(y => context.gmLink
.Where(z => z.ModuleId == m)
.Select(z => z.GroupId)
.Contains(y.GroupId)
)
)
.ToList();
I'm doing the mvcmusicstore practice tutorial. I noticed something when creating the scaffold for the album manager (add delete edit).
I want to write code elegantly, so i'm looking for the clean way to write this.
FYI i'm making the store more generic:
Albums = Items
Genres = Categories
Artist = Brand
Here is how the index is retrieved (generated by MVC):
var items = db.Items.Include(i => i.Category).Include(i => i.Brand);
Here is how the item for delete is retrieved:
Item item = db.Items.Find(id);
The first one brings back all the items and populates the category and brand models inside the item model. The second one, doesn't populate the category and brand.
How can i write the second one to do the find AND populate whats inside (preferably in 1 line)... theoretically - something like:
Item item = db.Items.Find(id).Include(i => i.Category).Include(i => i.Brand);
You can use Include() first, then retrieve a single object from the resulting query:
Item item = db.Items
.Include(i => i.Category)
.Include(i => i.Brand)
.FirstOrDefault(x => x.ItemId == id);
Dennis' answer is using Include and SingleOrDefault. The latter goes round-tripping to database.
An alternative, is to use Find, in combination with Load, for explicit loading of related entities...
Below an MSDN example:
using (var context = new BloggingContext())
{
var post = context.Posts.Find(2);
// Load the blog related to a given post
context.Entry(post).Reference(p => p.Blog).Load();
// Load the blog related to a given post using a string
context.Entry(post).Reference("Blog").Load();
var blog = context.Blogs.Find(1);
// Load the posts related to a given blog
context.Entry(blog).Collection(p => p.Posts).Load();
// Load the posts related to a given blog
// using a string to specify the relationship
context.Entry(blog).Collection("Posts").Load();
}
Of course, Find returns immediately without making a request to the store, if that entity is already loaded by the context.
There's no real easy way to filter with a find. But I've come up with a close way to replicate the functionality but please take note of a few things for my solution.
This Solutions allows you to filter generically without knowning the primary key in .net-core
Find is fundamentally different because it obtains the the entity if it's present in the tracking before Querying the database.
Additionally It can filter by an Object so the user does not have to know the primary key.
This solution is for EntityFramework Core.
This requires access to the context
Here are some extension methods to add which will help you filter by primary key so
public static IReadOnlyList<IProperty> GetPrimaryKeyProperties<T>(this DbContext dbContext)
{
return dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties;
}
//TODO Precompile expression so this doesn't happen everytime
public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
{
var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
var parameter = Expression.Parameter(typeof(T), "e");
var body = keyProperties
// e => e.PK[i] == id[i]
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Convert(
Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
p.ClrType)))
.Aggregate(Expression.AndAlso);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
public static Expression<Func<T, object[]>> GetPrimaryKeyExpression<T>(this DbContext context)
{
var keyProperties = context.GetPrimaryKeyProperties<T>();
var parameter = Expression.Parameter(typeof(T), "e");
var keyPropertyAccessExpression = keyProperties.Select((p, i) => Expression.Convert(Expression.Property(parameter, p.Name), typeof(object))).ToArray();
var selectPrimaryKeyExpressionBody = Expression.NewArrayInit(typeof(object), keyPropertyAccessExpression);
return Expression.Lambda<Func<T, object[]>>(selectPrimaryKeyExpressionBody, parameter);
}
public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this DbSet<TEntity> dbSet, DbContext context, object[] id)
where TEntity : class
{
return FilterByPrimaryKey(dbSet.AsQueryable(), context, id);
}
public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this IQueryable<TEntity> queryable, DbContext context, object[] id)
where TEntity : class
{
return queryable.Where(context.FilterByPrimaryKeyPredicate<TEntity>(id));
}
Once you have these extension methods you can filter like so:
query.FilterByPrimaryKey(this._context, id);
Didnt work for me. But I solved it by doing like this.
var item = db.Items
.Include(i => i.Category)
.Include(i => i.Brand)
.Where(x => x.ItemId == id)
.First();
Dont know if thats a ok solution. But the other one Dennis gave gave me a bool error in .SingleOrDefault(x => x.ItemId = id);
In this scenario you must use DbSet<T>.Local.
You cannot combine DbSet<T>.Find(object[] params) to do what you want because it will query the database if the entity is not currently attached and tracked by the context.
Implementations of DbSet<T>.SingleOrDefault<T>, DbSet<T>.FirstOrDefault<T> and related methods will also query the database immediately upon invocation.
Assuming you have type MyEntity with property Id returning int you could create a method like the following, or adapt it to meet your specific need.
public MyEntity FindLocalOrRemote(int id)
{
MyEntity entity =
context.MyEntities
.Local
.SingleOrDefault(p => p.Id == id)
??
context.MyEntities
.Include(p => p.PackItems)
.SingleOrDefault(p => p.PackId == id);
return entity;
}
A drawback of this approach, and quite possibly why there is no built-in method for this, might be due to the challenge of designing an API around key values or because using DbSet<T>.Local there is no guarantee that the attached and tracked entity has the related navigation property populated from the database.
This question is really old, but not a single person gave either a simple or correct answer to the question.
This would work with Entity Framework 6 or Entity Framework Core.
You have to cast IQueryable to DbSet
var dbSet = (DbSet<Item>) db.Set<Item>().Include("");
return dbSet.Find(id);
I have a situation where I am calling an entity and putting in two includes in the ria services call.
public IQueryable<Position> GetPositions(int programID)
{
return _positionRepository.All()
.Where(x => x.ProgramID == programID)
.Include("RecPositions.Person");
}
Id like to get a handle on the Person entity on the front end. I have this working..the code below gives me a handle on the recPositions and in the intellisence I can see the Person object. id like to abstract that entity.
var test = _allRec.Select(x => x.RecPositions).ToList();
test now has my RecPosition...but i want to know how to write a lambda express so i can get a handle on the person object.
I came up with this..does anyone have any objections to this or a better way..
var test = _allRec.SelectMany(x => x.RecPositions)
.Select(p => p.Person)
.ToList();
this seems to give me what I want.