Linq Grouping looses the child entities - asp.net-mvc-3

I have the following query:
var _customers = (from c in _db.UserProfiles.Include(x=>x.ParentCompanies).Include(x=>x.cProfile).Include(x=>x.cProfile.PhoneNumbers).Include(x=>x.cProfile.Addresses)
where (c.ParentCompanies.Any(pc => pc.CompanyUsers.Any(cu => cu.UserName == userName)) && c.cProfile != null)
group c by c.FirstName.Substring(0, 1).ToUpper() into customerGroup
select new ContactsViewModel
{
FirstLetter = customerGroup.Key,
Customers = customerGroup
}).OrderBy(letter => letter.FirstLetter);
if I take out the group, it works well and includes all the children (parentCompanies, cProfile, ...) as soon as I put the group back in it looses all of the children. How do I solve this issue?
update
I guess I should also include the view model that I'm usign to put the result in.
public class ContactsViewModel
{
public string FirstLetter { get; set; }
public IEnumerable<UserProfile> Customers { get; set; }
}

Include only applies to items in the query results (i.e. the final projection) and cannot contain operations that change the type of the result between Include and the outermost operation (e.g. GroupBy())
http://wildermuth.com/2008/12/28/Caution_when_Eager_Loading_in_the_Entity_Framework
If you want to eager load, do the grouping client-side (i.e. enumerate the query then call the GroupBy method on the results)

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);
});

Adding select distinct to linq query

My Web API Controller has a method that retrieves a specified number of decriptions from a database table. There are duplicate descriptions with different IDs, so sometimes the query returns duplicates when I use SELECT TOP. I also added random (ORDER BY NEWID) to lessen the chances of getting dups but duplicates still get returned sometimes. I want to change the query to SELECT DISTINCT but not sure how to do that in my particular case. Using First() seems to be complicated here. Can anyone help? My method is below:
public List<String> GetRandomDescriptions(string cat, string subcat, int n)
{
using (MyContext ctx = new MyContext())
{
var temp = ctx.Interactions.Where(d => (d.Category.Equals(cat) && d.Subcategory.Equals(subcat)))).OrderBy(d=>Guid.NewGuid()).Take(n).Select(d=>d.Description).ToList();
return temp;
}
}
Here is my class:
[Table("[Records]")]
public class Interaction
{
[Key, Column("RECORD_ID")]
public string DescId { get; set;}
public string Category { get; set; }
public string Subcategory { get; set; }
public string Description{get; set;}
}
You can use something like this
var result = ctx.Interactions
.Where(d => d.Category == cat && d.Subcategory == subcat)
.Select(d => d.Description)
.Distinct()
.Take(n)
.ToList();
The key points are - first apply the filter, then select the description, then make it distinct and finally take the required number of items.
If you really need to pick a random items, then just insert your OrderBy before Take.
You don't need to do the funny OrderBy construct. Try something like this:
public List<String> GetRandomDescriptions(string cat, string subcat, int n)
{
using (MyContext ctx = new MyContext())
{
var temp = ctx.Interactions
.Where(d => d.Category.Equals(cat) && d.Subcategory.Equals(subcat)))
.Select(d=>d.Description)
.Distinct()
.ToList();
return temp;
}
}

not getting child category when returning parent category in linq entity framework

I want to return the parent category along with the subcategory. There is a self join in my table. This is my class:
public partial class CategoryMaster
{
public int Id { get; set; }
public string Name { get; set; }
public Nullable<int> ParentId { get; set; }//parentid is link to Id
public virtual ICollection<CategoryMaster> CategoryMaster1 { get; set; }
public virtual CategoryMaster CategoryMaster2 { get; set; }
}
I use this query to return the parent category and the child category:
var GetallCategories = context.CategoryMaster.Include("CategoryMaster1")
.Where(d => d.ParentId == null)
.OrderBy(d => d.Name)
.Select(d => new SelectListItem { Text = d.Name,
Value = d.Id.ToString()
})
.ToList();
The problem is that it is just returning parent category. Whats wrong with my query?
The first problem is your where clause. You told it to ONLY bring back CategoryMasters with a null parent. So you won't get any children out of the DB.
We have a more rigid (read: slower) pattern we follow where I work with our EF queries, so I've never done exactly what you're doing here, but I think all you have to do is move the where clause, like this:
var GetallCategories = context.CategoryMaster.Include("CategoryMaster1")
.OrderBy(d => d.Name)
.ToList();
.Where(d => d.ParentId == null)
.Select(d => new SelectListItem { Text = d.Name,
Value = d.Id.ToString()
})
The ToList forces a DB call and then the where and select after it get processed after it comes back from the DB, at which point, it's gotten all the data with no where clause. If I understand correctly, you do want all the data, you just want your list to contain only the parents at the top level and then drill down to get to the children.
So if you add any where clause before the tolist, it's going to restrict what comes back from the DB before it even tries to build the hierarchy of objects. If you look at the actual sql generated by EF, you can see this. This means that if you legitimately need a where clause - say you want to bring back all parents with a lastname of "Smith" plus their children - you have to make it more complex. If you need a meaningful where caluse to filter parents in the DB (which you might for performance) and you have to allow for an unknown number of generations of children, this becomes VERY difficult to do with EF includes.

Coalesce fields in a .net MVC 4 model without getting "Only initializers, entity members, and entity navigation properties are supported" from LINQ

The answer to this question gave rise to this other question: How to use LINQ expressions as static members of classes in queries when the class is related multiple times to a second class
I have an existing ASP.net MVC 4 site which I need to modify.
The core entity within this site are Items that are for sale, which are created by several different companies and divided into several categories. My task is to allow each company its own optional alias for the global categories. Getting the two categories set up in the database and model was no problem, making the application use the new optional alias when it exists and default to the global otherwise is where I'm struggling to find the optimal approach.
Adding a coalesce statement to every LINQ query will clearly work, but there are several dozen locations where this logic would need to exist and it would be preferable to keep this logic in one place for when the inevitable changes come.
The following code is my attempt to store the coalesce in the model, but this causes the "Only initializers, entity members, and entity navigation properties are supported." error to be thrown when the LINQ query is executed. I'm unsure how I could achieve something similar with a different method that is more LINQ friendly.
Model:
public class Item
{
[StringLength(10)]
[Key]
public String ItemId { get; set; }
public String CompanyId { get; set; }
public Int32 CategoryId { get; set; }
[ForeignKey("CategoryId")]
public virtual GlobalCategory GlobalCategory { get; set; }
[ForeignKey("CompanyId, CategoryId")]
public virtual CompanyCategory CompanyCategory { get; set; }
public String PreferredCategoryName
{
get{
return (CompanyCategory.CategoryAlias == null || CompanyCategory.CategoryAlias == "") ? GlobalCategory.CategoryName : CompanyCategory.CategoryAlias;
}
}
}
Controller LINQ examples:
var categories = (from i in db.Items
where i.CompanyId == siteCompanyId
orderby i.PreferredCategoryName
select i.PreferredCategoryName).Distinct();
var itemsInCategory = (from i in db.Items
where i.CompanyId == siteCompanyId
&& i.PreferredCategoryName == categoryName
select i);
For one you are using a compiled function (getPreferredCategoryName) in the query, unless EF knows how to translate that you are in trouble.
Try the following in item definition:
public static Expression<Func<Item,String>> PreferredCategoryName
{
get
{
return i => (i.CompanyCategory.CategoryAlias == null || i.CompanyCategory.CategoryAlias == "") ?
i.GlobalCategory.CategoryName :
i.CompanyCategory.CategoryAlias;
}
}
Which is used as follows:
var categories = db.Items.Where(i => i.CompanyID == siteCompanyId)
.OrderBy(Item.PreferredCategoryName)
.Select(Item.PreferredCategoryName)
.Distinct();
This should work as you have a generically available uncompiled expression tree that EF can then parse.

Resources