Entity Framework Many to Many works but Include does not - linq

I have a typical many-to-many relationship with these 3 tables
[Post] (
[PostId] int, (PK)
[Content] nvarchar(max)
...
)
[Tag] (
[TagId] int, (PK)
[Name] nvarchar
...
)
[TagPost] (
[TagId] int, (PK, FK)
[PostId] int (PK, FK)
)
And, TagId and PostId are the PK and FK set on the tables accordingly etc. Then I have these classes and mapping in c#
public class Post {
public Post()
{
this.Tags = new HashSet<Tag>();
}
[Key]
public int PostId { get; set; }
...
public virtual ICollection<Tag> Tags { get; private set; }
}
public class Tag {
public Tag()
{
this.Posts = new HashSet<Post>();
}
[Key]
public int TagId { get; set; }
...
public virtual ICollection<Post> Posts { get; private set; }
}
internal class MyDbContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Post>().ToTable("Post");
modelBuilder.Entity<Tag>().ToTable("Tag");
modelBuilder.Entity<Post>()
.HasMany(x => x.Tags)
.WithMany(x => x.Posts)
.Map(x =>
{
x.ToTable("TagPost");
x.MapLeftKey("PostId");
x.MapRightKey("TagId");
});
}
Then I have this code to query them
var list = (from p in ctx.Posts.Include(p => p.Tags)
from t in p.Tags
where ... // some of my filter conditions
select p).ToList();
This join does return the posts I was looking for, however the returned posts don't their associated tags filled in even though I have the Include there. Could someone help point out what I'm missing so that I could have the tags also return with the posts?
Thanks a lot.

The double from is a manual Join which causes the Include to be ignored as mentioned here and here. Include is also ignored for other LINQ methods like grouping and projections.
Relationship fixup generally does not work for many-to-many relationships, only for relationships which have at least one single reference at one of the ends - one-to-many or one-to-one. If you project the Posts and related Tags into another type (anonymous or named) the data will be loaded correctly but because the relationship is many-to-many EF won't create the relationship in memory automatically so that the post.Tags collection will stay empty.
To get the Include working you must remove the second from from your query and apply the where clause directly to the Post entity parameter, for example like so:
var list = (from p in ctx.Posts.Include(p => p.Tags)
where p.Tags.Any(t => t.TagId == 1)
select p).ToList();
The filter by a Tag property is specified in the expression passed into .Any which is an expression with a Tag (t) as parameter.

try selecting everything into an anonymous object (something like this)
var list = (
from p in ctx.Posts
from t in p.Tags
where ... // some of my filter conditions
select new {
Posts = p,
Tags = p.Tags
})
.ToList();
Based on the feedback to my initial answer and the fact that EF can find the related entities but it is failing to populate the Tags collection I believe the issue lies in the definition of the Tags entity in the Post class.
Try removing the Hashset<> initialiser from the constructors and private from the set declaration:
public virtual ICollection<Tag> Tags { get; set; }

Related

Is there a way I can paginate the included list in a linq query

Hope you're doing well,
I was trying to optimize my reads with entity framework, where I arrived at a position, where I get a record from database by id, and I want to include a one-to-many related list, but I don't want to get all data of the list, just a few, so I want to kind of paginate it.
I want to do this process as long as data is in IQueryable state, I don't want to load all data of list in memory and that paginate it as enumerable.
Let's say the query is like below:
var author = await _dbContext.Authors.Where(x => x.Id == id)
.Include(x => x.Books) // <-- paginate this !!??
.FirstOrDefaultAsync();
Entities represent Data state. Pagination and presentation concerns are View state. Entity Framework can help bridge that gap, but it does so by enabling projection so that you can build View state from Data state. Don't pass entities to views, instead build and pass ViewModels to represent the data in accordance to translations and limitations you want for the view.
For instance if you want to pass Author details with their 5 most recent books:
public class AuthorViewModel
{
public int Id { get; set; }
public string Name { get; set; }
// Any other relevant fields...
public ICollection<BookViewModel> RecentBooks = new List<BookViewModel>();
}
public class BookViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime PublishedDate { get; set; }
// Any other relevant fields...
}
var author = await _dbContext.Authors
.Where(x => x.Id == id)
.Select( x => new AuthorViewModel
{
Id = x.Id,
Name = x.Name,
RecentBooks = x.Books
.OrderByDescending(b => b.PublishedDate)
.Select(b => new BookViewModel
{
Id = b.Id,
Name = b.Name,
PublishedDate = b.PublishedDate
}).Take(5)
.ToList()
}).SingleOrDefault();
This gives you the benefit of structuring the data how you want to present it, while generating efficient queries against the database. You can configure tools like Automapper to perform this kind of mapping to use it's ProjectTo<AuthorViewModel>() as a more succinct alternative.

Stuck into "Include" limitation (need to join by custom fields in addition to key fields)

I have the following class:
public class A {
public B B_a_1 { get; set; }
...
public B B_a_[N] { get; set; }
}
public class C {
public B B_c_1 { get; set; }
...
public B B_c_[M] { get; set; }
}
Class B is mapped to the table via OnModelCreating and has a composite key. But one of the fields in this key is a language code: in some cases it's a current thread language, in other cases all languages need to be selected - for instance, when admin is going to edit localizations (it's a client-created DB for many years and the structure is not going to be changed). This poses the problem of data selection for me. I cannot use Include as-is, because Language code needs to be joined conditionally. Due to large amount of different kinds of B entities (which differ by code - one of key fields) I cannot create a .NET class per each B entity, inheriting it from base class and use HasDiscriminator, HasQueryFilter in base class and stuff like that. In fact, what I now need is to select specific type of B entity by code, using some Extension method like that (pseudo-code is following):
DbSet<A>.Include(x => x.B_a_1).Where(x => x.B_a_1.LanguageCode = "E").Include(x => x.B_a_[N]).Where(x => x.B_a_[N].SomeProperty = "Something")
which would be translated to:
FROM Table_A a
LEFT JOIN Table_B b1 ON b1.Code = a.Code AND b1.LanguageCode = 'E'
LEFT JOIN Table_B b2 ON b2.Code = a.Code AND b2.SomeProperty = 'Something'
I need to 'group' include-where to be able to independently control JOIN conditions per each B-kind entity.
I ended up with the following approach.
Having for instance the class ModuleLanguage with child entity Language, which cannot be explicitly bound via key relationship:
public class ModuleLanguage
{
...
public string LanguageCode { get; set; }
public FixCodeValue Language { get; set; }
...
}
I ignore such property in model creation:
builder.Entity<ModuleLanguage>(b =>
{
b.Ignore(x => x.Language);
}
manual load this property (FixCodeValue class is actually used by many child entities and all of them need to be selected from the same DB table, but based on different criteria):
var currentLanguageCode = DbContext.CurrentLanguage;
await query.ForEachAsync(moduleLanguage =>
{
moduleLanguage.Language = DbContext.FixCodeValues
.FirstOrDefault(language =>
language.DomainId == CentralToolsDomainTypes.CentralTools
&& language.CodeName == CentralToolsFieldTypes.LANGUAGE
&& language.StringValue == moduleLanguage.LanguageCode
&& language.LanguageCode == currentLanguageCode);
});

Entity Framework 6 Add nested child object in Parent object in LINQ Query

I am working on .NET Core application; Entity Framework 6
I need to add child object which is collection is same parent object in LINQ query not lambda expression
User - parent entity
public class UserDataModel
{
public UserDataModel()
{
this.Roles = new HashSet<UserRoleDataModel>();
}
public Guid Id { get; set; }
public Guid IdentityProviderID {get; set;}
public ICollection<UserRoleDataModel> Roles { get; set; }
}
Child-entity
public class UserRoleDataModel
{
public Guid UserId { get; set; }
public UserDataModel Users { get; set; }
public Guid RoleId { get; set; }
public RoleDataModel Roles { get; set; }
}
LINQ
var uu = (from _user in db.Users
join _userRole in db.UserRoles on _user.Id equals _userRole.UserId
where _user.IdentityProviderID == AureID
select new {
_user,
_userRole
}
This is far I reached but Roles are collection where sub-query to select role separate are IQueryable hence throw conversion/ casting error
var uu = (from _user in db.Users
where _user.IdentityProviderID == AureID
select new UserDataModel
{
Id = _user.Id,
IdentityProviderID = _user.IdentityProviderID,
Roles = (from b in db.UserRoles where b.UserId == userId select b)
}
).ToList();
Addition after comment at the end
It seems you have a true one-to-many relationship between UserData and UserRoles: Every UserData has zero or more UserRoles, and every UserRole belongs to exactly one UserData.
Your class definitions deviate from the Entity Framework Code First conventions. If you'd had your classes configured like a standard Entity Framework One-to-many relationship, entity framework would recognize that you'd meant to design a one-to-many and know automatically when a join is needed in your queries. Reconsider rewriting your classes:
class UserDataModel
{
public GUID Id {get; set;} // primary key
// every UserDataModel has zero or more UserRoleDataModels:
public virtual ICollection<UserRoleDataModel> UserRoleDataModels {get; set;}
... // other properties
}
class UserRoleDataModel
{
public GUID Id {get; set;} // primary key
// every UserDataRoleModel belongs to exactly one UserDataModel
// using foreign key:
public Guid UserDataModelId {get; set;}
public virtual UserDataModel UserDataModel {get; set;}
... // other properties
}
The most important change is that I made the references between your UserDataModel and your UserDataRoleModels virtual. I changed the name of the foreign key, so Entity Framework knows without any attribut / fluent API that you meant to configure a one-to-many.
Back to your question
Given the Id of a UserDataModel, give me the UserDataModel with all
its UserDataRoleModels
After you've rewritten your classes with the proper virtual ICollection, your query would be easy:
var result = myDbContext.Users
.Where(user => user.Id == AureId)
.Select( user => new
{
User = user,
Roles = user.Roles.ToList(),
});
This requirement has the shortcoming that you tend to retrieve to much data. One of the slower parts when querying a database is the transfer of the queried data to your local process. Therefore it is wise not to fetch any more data than you want.
You plan to query a complete UserData with its complete UserRole objects. if the UserData that you fetch has Id XYZ, and it has 50 UserRole objects, then every one of its UserRole object would have a UserDataId with a value of XYZ, thus transferring it 50 times more than needed.
Therefore I advise you to transfer only the data that you actually plan to use:
var result = myDbContext.Users
.Where(user => user.Id == AureId)
.Select( user => new
{
// from the user take only the data you plan to use
Id = user.Id, // probably not, you know that it is AureId
Name = user.Name,
... // other user properties you plan to use
UserRoles = user.UserRoleDataModels.Select(roleData => new
{
// again: only the role data you plan to use
Name = roleData.Name,
Type = roleData.Type,
... // other properties
// certainly not this one:
UserDataId = roleData.UserDataId,
})
.ToList(),
});
This way your query is much faster, you'll only fetch the data you want, you can change the names of fields depending on your needs, and you can add new values if desired, composed from values within UserData
Because of your virtual ICollection you don't need to perform the join. Entity Framework knows your one-to-many and understands that your use of the ICollection should result in an inner join followed by a Group By
Summarized: if you think you'll need a join in entity framework, think twice, you'll probably be able to do it simpler by using the ICollection or the reference to the parent
Addition after comment
if we add role has definition table, how to I add further nested object .
I'm not sure what this means. I think you mean: what to do if a role has a definition table, or what to do if a role has zero or more definition tables.
class UserRoleDataModel
{
public GUID Id {get; set;} // primary key
// every UserDataRoleModel belongs to exactly one UserDataModel
// using foreign key:
public Guid UserDataModelId {get; set;}
public virtual UserDataModel UserDataModel {get; set;}
// every role has exactly one DefinitionTable:
public DefinitionTable DefinitionTable {get; set'}
}
var result = myDbContext.Users
.Where(user => user.Id == AureId)
.Select( user => new
{
// from the user take only the data you plan to use
...
UserRoles = user.UserRoleDataModels
.Where(role => role... == ...)
.Select(role => new
{
RoleDefinition = role.RoleDefinitiona,
...
}),
});
If your Role has zero or more RoleDefinitions, do the same as you did with Users and Roles: add the virtual ICollection and add the virtual Reference and foreign key and use the collections in a Select statement.

Counting in a Many-To-Many Relationship in Entity Framework

Using Entity Framework 4.1 Code First I have two objects with a many-to-many relationship:
public class Article
{
public int ID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag
{
[Key]
public string UrlSlug { get; set; }
public string Name { get; set; }
public ICollection<Article> Articles { get; set; }
}
I want to count the most common Tags applied to Articles. How do I do this in LINQ?
I tried the below code which only ordered the Tags:
var tagsFromDb = db.Tags
.GroupBy(q => q.UrlSlug)
.OrderByDescending(gp => gp.Count())
.Select(g => g.FirstOrDefault());
If I understand right you have a many-to-many relationship between articles and tags but the navigation property on the tag side is not exposed (there is no ICollection<Article> in Tag). I think in this case you have to start from the articles to get all used tags along with the information how often they are used in the articles:
var tagQuery = db.Articles
.SelectMany(a => a.Tags) // flat enumeration of all used tags
.GroupBy(t => t, (k, g) => new // k = key = Tag, g = group of same tags
{
Tag = k, // the tag
Count = g.Count() // how often does it occur in the articles
})
.OrderByDescending(g => g.Count); // most used tags first
If you want all tags sorted descending, call
var tagList = tagQuery.ToList();
If you just want the tag which is most often used in the articles, call
var mostUsedTag = tagQuery.FirstOrDefault();
In both cases the result is a collection/single object of an anonymous type which has the Tag and Count as members. If you just want the tag(s) and are not interested in the Count anymore you can project before apply ToList or FirstOrDefault: tagQuery.Select(anonymous => anonymous.Tag).....
Edit
Just saw the Edit in your question. Now, when you have an Article collection in the Tag entity it is easier:
var tagQuery = db.Tags
.Select(t => new
{
Tag = t, // the Tag
Count = t.Articles.Count() // just count the number of artices
// the tag is used in
})
.OrderByDescending(x => x.Count); // most used tags first

LinqToSQl and the Member access not legal on type exception

The basic problem...
I have a method which executes the following code:
IList<Gig> gigs = GetGigs().WithArtist(artistId).ToList();
The GetGigs() method gets Gigs from my database via LinqToSql...
So, when GetGigs().WithArtist(artistId).ToList() is executed I get the following exception:
Member access 'ListenTo.Shared.DO.Artist Artist' of 'ListenTo.Shared.DO.Act' not legal on type 'System.Collections.Generic.List`1[ListenTo.Shared.DO.Act]
Note that the extension function "WithArtist" looks like this:
public static IQueryable<Gig> WithArtist(this IQueryable<Gig> qry, Guid artistId)
{
return from gig in qry
where gig.Acts.Any(act => (null != act.Artist) && (act.Artist.ID == artistId))
orderby gig.StartDate
select gig;
}
If I replace the GetGigs() method with a method that constructs a collection of gigs in code (rather than from the DB via LinqToSQL) I do NOT get the exception.
So I'm fairly sure the problem is with my LinqToSQl code rather than the object structure.
However, I have NO IDEA why the LinqToSQl version isnt working, so I've included all the associated code below. Any help would be VERY gratefully receivced!!
The LinqToSQL code....
public IQueryable<ListenTo.Shared.DO.Gig> GetGigs()
{
return from g in DBContext.Gigs
let acts = GetActs(g.ID)
join venue in DBContext.Venues on g.VenueID equals venue.ID
select new ListenTo.Shared.DO.Gig
{
ID = g.ID,
Name = g.Name,
Acts = new List<ListenTo.Shared.DO.Act>(acts),
Description = g.Description,
StartDate = g.Date,
EndDate = g.EndDate,
IsDeleted = g.IsDeleted,
Created = g.Created,
TicketPrice = g.TicketPrice,
Venue = new ListenTo.Shared.DO.Venue {
ID = venue.ID,
Name = venue.Name,
Address = venue.Address,
Telephone = venue.Telephone,
URL = venue.Website
}
};
}
IQueryable<ListenTo.Shared.DO.Act> GetActs()
{
return from a in DBContext.Acts
join artist in DBContext.Artists on a.ArtistID equals artist.ID into art
from artist in art.DefaultIfEmpty()
select new ListenTo.Shared.DO.Act
{
ID = a.ID,
Name = a.Name,
Artist = artist == null ? null : new Shared.DO.Artist
{
ID = artist.ID,
Name = artist.Name
},
GigId = a.GigID
};
}
IQueryable<ListenTo.Shared.DO.Act> GetActs(Guid gigId)
{
return GetActs().WithGigID(gigId);
}
I have included the code for the Act, Artist and Gig objects below:
public class Gig : BaseDO
{
#region Accessors
public Venue Venue
{
get;
set;
}
public System.Nullable<DateTime> EndDate
{
get;
set;
}
public DateTime StartDate
{
get;
set;
}
public string Name
{
get;
set;
}
public string Description
{
get;
set;
}
public string TicketPrice
{
get;
set;
}
/// <summary>
/// The Act object does not exist outside the context of the Gig, therefore,
/// the full act object is loaded here.
/// </summary>
public IList<Act> Acts
{
get;
set;
}
#endregion
}
public class Act : BaseDO
{
public Guid GigId { get; set; }
public string Name { get; set; }
public Artist Artist { get; set; }
}
public class Artist : BaseDO
{
public string Name { get; set; }
public string Profile { get; set; }
public DateTime Formed { get; set; }
public Style Style { get; set; }
public Town Town { get; set; }
public string OfficalWebsiteURL { get; set; }
public string ProfileAddress { get; set; }
public string Email { get; set; }
public ImageMetaData ProfileImage { get; set; }
}
public class BaseDO: IDO
{
#region Properties
private Guid _id;
#endregion
#region IDO Members
public Guid ID
{
get
{
return this._id;
}
set
{
this._id = value;
}
}
}
}
I think the problem is the 'let' statement in GetGigs. Using 'let' means that you define a part of the final query separately from the main set to fetch. the problem is that 'let', if it's not a scalar, results in a nested query. Nested queries are not really Linq to sql's strongest point as they're executed deferred as well. In your query, you place the results of the nested query into the projection of the main set to return which is then further appended with linq operators.
When THAT happens, the nested query is buried deeper into the query which will be executed, and this leads to a situation where the nested query isn't in the outer projection of the query to execute and thus has to be merged into the SQL query ran onto the DB. This is not doable, as it's a nested query in a projection nested inside the main sql query and SQL doesn't have a concept like 'nested query in a projection', as you can't fetch a set of elements inside a projection in SQL, only scalars.
I had the same issue and what seemed to do the trick for me was separating out an inline static method call that returned IQueryable<> so that I stored this deferred query into a variable and referenced that.
I think this is a bug in Linq to SQL but at least there is a reasonable workaround. I haven't tested this out yet but my assumption is that this problem may arise only when referencing static methods of a different class within a query expression regardless of whether the return type of that function is IQueryable<>. So maybe it's the class that holds the method that is at the root of the problem. Like I said, I haven't been able to confirm this but it may be worth investigating.
UPDATE: Just in case the solution isn't clear I wanted to point it out in context of the example from the original post.
public IQueryable<ListenTo.Shared.DO.Gig> GetGigs()
{
var acts = GetActs(g.ID); // Don't worry this call is deferred
return from g in DBContext.Gigs
join venue in DBContext.Venues on g.VenueID equals venue.ID
select new ListenTo.Shared.DO.Gig
{
ID = g.ID,
Name = g.Name,
Acts = new List<ListenTo.Shared.DO.Act>(acts),
Description = g.Description,
StartDate = g.Date,
EndDate = g.EndDate,
IsDeleted = g.IsDeleted,
Created = g.Created,
TicketPrice = g.TicketPrice,
Venue = new ListenTo.Shared.DO.Venue {
ID = venue.ID,
Name = venue.Name,
Address = venue.Address,
Telephone = venue.Telephone,
URL = venue.Website
}
};
}
Note that while this should correct the issue at hand there also seems to be another issue in that the deferred acts query is being accessed in each element of the projection which I would guess would cause separate queries to be issued to the database per row in the outer projection.
I don't see anything in your classes to indicate how LINQ to SQL is meant to work out which column is which, etc.
Were you expecting the WithArtist method to be executed in .NET, or converted into SQL? If you expect it to be converted into SQL, you'll need to decorate your Gig class with appropriate LINQ to SQL attributes (or configure your data context some other way). If you want it to be executed in code, just change the first parameter type from IQueryable<Gig> to IEnumerable<Gig>.
I found out that an issue like this (which I also had recently) can be resolved, if you convert the IQueryable (or Table) variable Gigs into a list like so
return from g in DBContext.Gigs.ToList()
...
If that still doesn't work, do the same for all the IQueryables. The reason behind seems to me that some queries are too complex to be translated into SQL. But if you "materialize" it into a list, you can do every kind of query.
Be careful, you should add "filters" (where conditions) early because too much memory consumption can become a problem.

Resources