How to do GroupBy on complex object with an IQueryable - c#

I'm looking for a way to do a GroupBy on a complex object, instead of just one property. The trouble is that I want to do this on an IQueryable, because getting all the data from the table is a really bad idea in this case.
We're using Entity Framework 6.1.
The class looks like this:
public class Pin {
public Guid Id {get;set;}
public Guid PageId {get;set;} /* this is the foreign key to our Pages-table */
public PageClass Page {get;set;} /* this is a relation */
}
I need to report on the times a certain page has been "pinned", printing the name of the page as well.
Right now my code looks like this:
var pinnedPages = GetAll().GroupBy(x => x, comparer);
foreach (var pinnedPage in pinnedPages)
{
var numberOfTimesPinned = pinnedPage.Count();
var pin = pinnedPage.Key;
//write a line to the report
}
But if I group on PageId, the pinnedPage.Key returns a Guid, obviously, while I need the whole Page object for my reporting needs.
I have tried implementing a custom comparer as well, but this cannot be translated to SQL, obviously which is why this doesn't work either.

GetAll().GroupBy(x => x.pageId).Select(_ => new {key = _.Key, page = _.FirstOrDefault().Page, count = _.Count()});
This will group by on the pageId, however the select will create a new anonymous object which will contain the key (pageId) and select the first PageClass object

You don't need any grouping if you query the pages directly and use a navigation property that I assume exist (or else should be added):
var pinnedPages = context.Pages
.Select(p => new
{
Page = p
Pins = p.Pins.Count()
});
foreach (var pinnedPage in pinnedPages)
{
var numberOfTimesPinned = pinnedPage.Pins;
var pin = pinnedPage.Page;
//write a line to the report
}
I use context.Pages because the source of the statement should be IQueryable. GetAll returns IEnumerable (apparently, otherwise the GroupBy overload with a comparer wouldn't work).

Related

Dynamically get a DbSet<T> by Entity class name

I'm trying to use System.Reflections to get a DbSet<T> dynamically from its name.
What I've got right now is:
The DbSet name
The DbSet's Type stored on a variable
The issue I'm facing comes out when trying to use the dbcontext.Set<T>() method, since (these are my tries so far):
When I try to assign to <T> my DbSet Type, it throws me the following compilation error:
"XXX is a variable but is used like a type"
If I try with using both the Extension methods that you will find below in my code (which I made in order to try to get an IQueryable<T>), it returns a IQueryable<object>, which unfortunately is not what I am looking for, since of course when I try to manipulate it with further Reflections, it lacks of all the properties that the original class has…
What am I doing wrong? How can I get a DbSet<T>?
My code is the following, but of course, let me know if you need more infos, clarifications or code snippets.
My Controller's Method:
public bool MyMethod (string t, int id, string jsonupdate)
{
string _tableName = t;
Type _type = TypeFinder.FindType(_tableName); //returns the correct type
//FIRST TRY
//throws error: "_type is a variable but is used like a type"
var tableSet = _context.Set<_type>();
//SECOND TRY
//returns me an IQueryable<object>, I need an IQueryable<MyType>
var tableSet2 = _context.Set(_type);
//THIRD TRY
//always returns me am IQueryable<object>, I need an IQueryable<MyType>
var calcInstance = Activator.CreateInstance(_type);
var _tableSet3 = _context.Set2(calcInstance);
//...
}
Class ContextSetExtension
public static class ContextSetExtension
{
public static IQueryable<object> Set(this DbContext _context, Type t)
{
var res= _context.GetType().GetMethod("Set").MakeGenericMethod(t).Invoke(_context, null);
return (IQueryable<object>)res;
}
public static IQueryable<T>Set2<T>(this DbContext _context, T t)
{
var typo = t.GetType();
return (IQueryable<T>)_context.GetType().GetMethod("Set").MakeGenericMethod(typo).Invoke(_context, null);
}
}
EDIT Added TypeFinder's inner code.
In brief, this method does the same of Type.GetType, but searches Type on ALL the generated assemblies
public class TypeFinder
{
public TypeFinder()
{
}
public static Type FindType(string name)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
var result = (from elem in (from app in assemblies
select (from tip in app.GetTypes()
where tip.Name == name.Trim()
select tip).FirstOrDefault())
where elem != null
select elem).FirstOrDefault();
return result;
}
}
UPDATE as requested in the comments, here's the specific case:
In my DB i've got some tables which are really similar each other, so the idea was to create a dynamic table-update method which would be good for every table, just passing to this method the table name, the ID of the row to update and the JSON containing data to update.
So, in brief, I would perform some updates on the table given in input as DbSet type, updating the row with ID==id in input with the data contained inside the JSON, which will be parsed inside an object of type X(the same of dbset)/into a dictionary.
In pseudo-code:
public bool MyMethod (string t, int id, string jsonupdate)
{
string _tableName = t;
Type _type = TypeFinder.FindType(_tableName); //returns the correct type
//THIS DOESN'T WORKS, of course, since as said above:
//<<throws error: "_type is a variable but is used like a type">>
var tableSet = _context.Set<_type>();
//parsing the JSON
var newObj = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonupdate, _type);
//THIS OF COURSE DOESN'T WORKS TOO
//selecting the row to update:
var toUpdate = tableSet.Where(x => x.Id == id).FirstOrDefault();
if(toUpdate!=null)
{
var newProperties = newObj.GetType().GetProperties();
var toUpdateProperties = toUpdate.GetType().GetProperties();
foreach(var item in properties)
{
var temp = toUpdateProperties.Where(p => p.Name==item.Name)
{
//I write it really in briefand fast, without lots of checks.
//I think this is enough, I hope
temp.SetValue(toUpdate, item.GetValue());
}
}
_context.SaveChanges();
}
return false;
}
returns me an IQueryable<object>, I need an IQueryable<MyType>
Well, that will never work. Your IQueryable cannot be of type IQueryable<MyType>because that would mean the compiler would need to know what MyType is and that is not possible, because the whole point of this exercise is to decide that on runtime.
Maybe it's enough to know that those objects are in fact instances of MyType?
If not, I think you have painted yourself into a corner here and you are trying to figure out what paint to use to get out of there. Take a step back, it's probably not a technical problem. Why do you need to do this? Why do you have the conflicting needs of knowing the type at runtime only and knowing it at compile time?
You need to think about your requirements, not about the technical details.
I needed to dynamically load a single record from the database for each type in a list of known types, to print a test email when an admin is editing the template, so I did this:
List<object> args = new List<object>();
//...
//other stuff happens that isn't relevant to the OP, including adding a couple fixed items to args
//...
foreach (Type type in EmailSender.GetParameterTypes())
{
//skip anything already in the list
if (args.Any(a => a.GetType().IsAssignableFrom(type))) continue;
//dynamically get an item from the database for this type, safely assume that 1st column is the PK
string sql = dbContext.Set(type).Sql.Replace("SELECT", "SELECT TOP 1") + " ORDER BY 1 DESC";
var biff = dbContext.Set(type).SqlQuery(sql).AsNoTracking().ToListAsync().Result.First();
args.Add(biff);
}
Caveat: I know at least one record will exist for all entities I'm doing this for, and only one instance of each type may be passed to the email generator (which has a number of Debug.Asserts to test validity of implementation).
If you know the record ID you're looking for, rather than the entire table, you can use dbContext.Set(type).Find(). If you want the entire table of whatever type you've sussed out, you can just do this:
string sql = dbContext.Set(type).Sql; //append a WHERE clause here if needed/feasible, use reflection?
var biff = dbContext.Set(type).SqlQuery(sql).ToListAsync().Result;
Feels a little clunky, but it works. There is strangely no ToList without Async, but I can run synchronously here. In my case, it was essential to turn off Proxy Creation, but you look like you want to maintain a contextful state so you can write back to db. I'm doing a bunch of reflection later, so I don't really care about strong typing such a resulting collection (hence a List<object>). But once you have the collection (even just as object), you should be able to use System.Reflection as you are doing in your UPDATE sample code, since you know the type and can use SetValue with known/given property names in such a manner.
And I'm using .NET Framework, but hopefully this may translate over to .NET Core.
EDIT: tested and working:
public async Task<bool> MyMethod(string _type)
{
Type type = Type.GetType(_type);
var tableSet = _context.Set(type);
var list = await db.ToListAsync();
// do something
}
// pass the full namespace of class
var result = await MyMethod("Namespace.Models.MyClass")
IMPORTANT NOTE: your DbContext need to have the DbSet declared to work!
public class MyContext : DbContext
{
public DbSet<MyClass> MyClasses { get; set; }
}

Why is Guid.ToString returning capitalised string in Linq?

I have encountered some weird/unexpected behaviour in which Guid.ToString() in a Linq expression returns a different result than Guid.ToString() in a foreach loop.
What the method is doing:
The method in question is simply taking an object and then creates a new view-model from the original object. The company I work for has decided that Guid's will not be allowed on view-models, due to one of our older JSON serializers having an bug in which Guid's were not serialized correctly.
The problem/unexpected result:
While debugging/testing my method I found that the Linq expression I created was returning a strange result. When converting my Guid to its string representation, the result was being automatically capitalised. I didn't believe that it was the Linq expression at first but once I had converted the logic into a foreach loop I got a lower-cased string representation of my Guid.
Example code:
Please note that the property types for lookupList (ID1, ID2, ID3) are all of type Guid and the properties on NewClass are all of type string.
Linq expression:
List<NewClass> returnList = lookupList.Select(i => new NewClass {
Property1 = i.ID1.ToString(),
Property2 = i.ID2.ToString(),
Property3 = i.ID3.ToString(),
.....
}).ToList();
Returns:
{
Property1=7081C549-64D6-458E-A693-0D2C9C47D183
Property2=06DD6A59-D339-4E15-89EA-48803DBA271E
Property3=9A876EDD-3B79-C27E-1680-E0820A0CD6EC
}
Foreach loop:
var returnList = new List<NewClass>();
foreach (var item in lookupList)
{
returnList.Add(new NewClass
{
Property1 = item.ID1.ToString(),
Property2 = item.ID2.ToString(),
Property3 = item.ID3.ToString(),
.....
});
}
Returns:
{
Property1=7081c549-64d6-458e-a693-0d2c9c47d183
Property2=06dd6a59-d339-4e15-89ea-48803dba271e
Property3=9a876edd-3b79-c27e-1680-e0820a0cd6ec
}
The question:
Why is this happening and is this expected behaviour? I would expect both the Linq expression and the foreach loop to return the same result when .ToString() is applied to my Guid but somehow it is not. I have also checked that there are no .ToString() overrides in either class.
Thank you in advance.
Update:
The lookupList has been handled by a .ToList() before it hits my method. LookupList is of type List<t> where t is a custom business entity which has additional properties that the database does not have. Apologies, I did not make this clear in my original question.
If lookupList is a IQueryable object, so that you are using LINQ to SQL, and not LINQ to object, then the two queries are not the same.
lookupList.Select(i => new NewClass { Property1 = i.ID1.ToString(), etc..
Will perform a SQL SELECT, the result will depend on your DBMS. My guess is that the ToString will be translate to something like CAST(Property1, varchar)
Whereas this query:
foreach (var item in lookupList)
{
returnList.Add(new NewClass { Property1 = item.ID1.ToString(), etc..
will first does a select in your database, and then calling ToString on it. So the method triggered is the ToString method of the C# object GUID.
Try this for example :
List<NewClass> returnList = lookupList.ToList().Select(i => new NewClass {
Property1 = i.ID1.ToString(),
Property2 = i.ID2.ToString(),
Property3 = i.ID3.ToString(),
.....
}).ToList();
This should return lowercase Properties.
The .ToString is not normally support in SQL, and the first query should exception. But my guess it that someone in your team read that blog post that explain how you can create a SQL function that will map an Entity Framework call.
Interesting fact : the example function create uppercase Guid strings.
EDIT : In pure LINQ to Object, this code return lowercase strings:
var lookupList = new[] { new Tuple<Guid, Guid>(Guid.NewGuid(), Guid.NewGuid()) };
var returnList = lookupList.Select(i => new
{
Property1 = i.Item1.ToString(),
Property2 = i.Item2.ToString(),
}).ToList();
We need more infos on lookupList object and those properties IDx. Are they pure C# Guid object?
You said that you already .ToListed your object. That will not work if your are doing it the wrong way :
BAD:
lookupList.ToList();
var returnList = lookupList.Select(i => new NewClass {
GOOD:
var purePOCOList = lookupList.ToList();
var returnList = purePOCOList.Select(i => new NewClass {

How to access NH QueryOver aliases from other functions and classes?

I want to be able to use one particular query in several other functions, I have a class that just creates a specialized QueryOver object for a particular domain.
But that function uses alias objects to create the joins. How can I access those aliases from another function?
For example say I have Course entities that each have a collection of students.
And I always want to only get Active ( a bool value) courses
public class QueryHelperClass
{
public QueryOver<Course, Course> GetQuery()
{
Address studentAlias = null;
QueryOver<Course, Course> query = QueryOver.Of<Course>(() => courseAlias)
.JoinAlias(x => cus.Student, () => studentAlias)
.Where(x => courseAlias.IsActive);
return query;
}
}
That works fine if all I need to do is GetExecutableQuery and return the results, but what do I do if I need to modify the query by accessing studentAlias?
Example:
public class SomeOtherClass
{
public List<Course> GetActiveCourseSummary(QueryOver<Course, Course> queryOver)
{
var query = queryOver.Where(a=> studentAlias.Name = "Bob");
...
}
}
From the SomeOtherClass.GetActiveCourseSummary I want to modify the query to only get courses where "Bob" is enrolled. But I can't access the studentAlias because it was defined in another function.
What can I do here, or am I setting this up all completely hard-core incorrectly?
In fact, we can re-declare the same variable in SomeOtherClass.
public List<Course> GetActiveCourseSummary(QueryOver<Course, Course> queryOver)
{
Address studentAlias = null;
var query = queryOver.Where(() => studentAlias.Name == "Bob");
...
}
The point is, that the name studentAlias (of the local variable Address) is the same as in the method GetQuery().
This will work, because what we pass in the .Where() method is the Expression. It is parsed and its string part "studentAlias" is used the same way as before, in GetQuery().
BUT
I would say, that this is not the way I would use. It is not clear what is passed into SomeOtherClass, how the query was built. There already could be an alias, but also it could be just a simple QueryOver<Course, Course> queryOver.
My approach is to do it different way. Collect all restrictions all the way down. Once there is e.g. set of restrictions IList<ICriterion>, call the DAO method, create query and append these restrictions at one place. But it is different story
If we would like to get some more checks into SomeOtherClass: we can use the Criteria API. Down side is that we have to usestring representation of properties "Student" and "Code" (not so clean as QueryOver API)
public List<Course> GetActiveCourseSummary(QueryOver<Course, Course> queryOver)
{
var criteria = query.UnderlyingCriteria;
var rootAlias = criteria.Alias; // will return "courseAlias"
var path = rootAlias + ".Student"; // the path
var student = criteria.GetCriteriaByPath(path)
?? criteria.CreateCriteria(path, path);
var studentAlias = student.Alias; // finally we do have existing alias
queryOver.And(Restrictions.Eq(studentAlias + ".Name ", "Bob"));
...

Validating ASP.NET MVC 3 controller's action params when using Dynamic Expressions API

I have a standard ASP.NET MVC 3 Controller with an action that has following signature:
public ActionResult Index(int? page, string sort, string sortDir)
My view is using WebGrid so parameters are produced by it automatically.
Next I use Dynamic Expressions API (aka Dynamic LINQ) to convert parameters into query. Example:
var customerSummary = CustomerManager.CustomerRepository.GetQuery()
.OrderBy(sort + " " + sortDir)
.Select(c => new CustomerSummaryViewModel()
{
Id = c.Id,
Name = c.Name,
IsActive = c.IsActive,
OrderCount = c.Orders.Count
})
.Skip(page.Value - 1 * 10) //10 is page size
.Take(10)
.ToList();
The Goal
What I would like to do is to use Dynamic Expressions API itself to validate parameters for sorting (and perhaps create a valid lambda). For example, I'd like to use DynamicExpression.Parse() or DynamicExpression.ParseLambda() methods to see if they produce ParseException, in which case I can replace erroneous params with default (e.g. sort by name ascending "Name ASC")...
The Problem
The problem is that IQueryable extensions take only a string
If I wanted to use ParseLambda and then feed it to .OrderBy I cannot use direction (it takes in only property name). For example, I can do this:
var se = DynamicExpression.ParseLambda<Customer, string>("Name"); // now I can use .OrderBy(se) which is same as .OrderBy(c=>c.Name)
but not this
var se = DynamicExpression.ParseLambda<Customer, string>("Name DESC");
Recap
I would like to use Dynamic LINQ to 1) validate and 2) build predicates (for sorting) based on action parameters
I'm not very familiar with Dymaic LINQ but you can do the following:
var customerSummary = CustomerManager.CustomerRepository.GetQuery();
if ("desc".Equals(sortDir, StringComparison.CurrentCultureIgnoreCase))
customerSummary = customerSummary.OrderByDescending(sort);
else
customerSummary = customerSummary.OrderBy(sort);
var pageNumber = page.GetValueOrDefault();
if (pageNumber < 1)
pageNumber = 1;
customerSummary = customerSummary
.Select(c => new CustomerSummaryViewModel()
{
Id = c.Id,
Name = c.Name,
IsActive = c.IsActive,
OrderCount = c.Orders.Count
})
.Skip((pageNumber - 1) * 10)
.Take(10)
.ToList();
I done something similar (except i used raw Expressions) in my project but gone even further.
I've created a base ViewModel like this:
class TableViewModel
{
public string SortColumn { get; set; }
public bool IsAsc { get; set; }
public int? PageNumber { get; set; }
}
And created a helper method that do all the paging/sorting work. Signature is like this:
public static IQueryable<T> TableHelper(this IQueryable<T> source, TableViewModel model) { ... }
And when i need to receive data from my table control and return a requested piece of data, controller action looks like this:
public ActionResult Index(TableViewModel model)
{
var data = _productRepository.AsQueryable().TableHelper(model);
... //Operation on data
}
Common thing that before and after call of the helper you are free to apply any filtering or smth.
It's very convinient.
When i need to extend my ViewModel, i inherit it and add new members to child model.
UPD: If you decide to stay DLINQ, try the following signature - OrderBy("Name", "ascending");
UPD2: If you want to validate your sort parameter, i think, reflection is the only choise. Something like this:
bool doSort = typeof(Product).GetProperty(sort, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy /*Or whatever flags you need*/) != null
Going this way, you should apply a OrderBy/OrderByDescending logic only if doSort is true. Otherwise, just skip sorting or apply any default logic.
In my oppinion, the more functionality you want from your code, the less DLINQ seems to be suitable for it. Once PropertyInfo is obtained the next reasonable step could be to use it in expression. Once we do it, where would be a place for DLINQ? :)
I agree that the expression and reflection code is very ugly in actions, but moving it outside, for example, to ExtensionMethod, as in my case, or in NonAction mehod of a controller base class, saves your eyes from seeing it:)

Calling custom (formatting) method on LINQ to Entities

I'm using EF 4.1 and I'm trying to enumerate a company list for a grid. I have two options in the current project: select all companies from the DbContext (Entities) and load them into an object from a non-anonymous type (let's say EmpresaGrid) or select all companies into anonymous type objects with the same structure like Empresa (which is the entity I'm selecting from).
The first option (creating a model class for that) would require a little more work, but can be, eventually, more readable. Still, I'm not sure about that. The second option is what I'm using right now.
So, first question: it's better to create a model class only for displaying data or use anonymous type? Doing a direct select is out of question: a SELECT * is too big and that might make everything damn slow (I guess). So selection into another type creates a custom query with only the needed fields.
Using the second option (anonymous type), I have this code (simplified version):
public static IEnumerable<object> Grid()
{
Entities db = new Entities();
var empresas = db.Empresas
.Select(e => new
{
Cgc = e.Cgc, // PK
(...)
Address = new
{
AddressLine = e.EnderecoSede.AddressLine,
(...)
}
},
Contato = e.Contato,
(...)
})
.ToList();
return empresas;
}
The anonymous type I'm creating has around 40 lines of code, so it's kinda big, but it recreates part of the Empresa class struct (since the grid is waiting for a Empresa object). Anyway, I have a problem with the data format. For example, I would like to format the Cgc property using a custom string format. I have a public method for this, FormataCgc. This method receives a string and returns it formatted using some internal conditions.
So, my problem is how to that. For example, I have tried this:
var empresas = db.Empresas
.Select(e => new
{
Cgc = FormataCgc(e.Cgc),
}
But that doesn't work because FormataCgc cannot be translated into SQL (and I don't want to convert it). I also tried this:
var empresas = db.Empresas
.Select(e => new
{
(...)
}
.ToList();
foreach (var e in empresas) {
e.Cgc = FormataCgc(e.Cgc);
}
But it cannot be done since anonymous types have only read-only properties.
So, my second question is: how exactly can I do that? I need to change the data after selecting it, but using anonymous types? I've done a little research, and the best thing I've found was this: Calling a custom method in LINQ query. In that solution, Ladislav suggested doing a second select from the IEnumerable, but since the grid is excepting Empresa I cannot do that (I need to change or add properties, not encapsulate them).
I'm not sure if I was clear enough, but feel free to ask any questions. Also, the grid I'm currently using is a Telerik ASP.NET MVC Grid, which receives a IEnumerable (where T is a class) as model data and them iterates each object, doing its magic.
Since you're already converting this into an IEnumerable<T>, you can do the custom formatting as you stream the results in the client. Do your db.Select, and then convert to the appropriate format afterwards, ie:
var empresas = db.Empresas
.Select(e => new
{
(...)
})
.ToList();
foreach (var e in empresas) {
yield return new {
Cgc = FormataCgc(e.Cgc),
// Copy other properties here, as needed...
};
}
That being said, I'd personally recommend making a custom class, and not return an anonymous type. Your conversion would then be:
foreach (var e in empresas) {
yield return new YourClass(FormataCgc(e.Cgc), ...); // Construct as needed
}
This will dramatically improve the usability of this method, as you will have proper, named access to your properties from the caller of the method.
I think the solution to both of your questions is to create a model class. Sure it is a little bit more work up front, but it will allow you greater flexibility in the long run. Your custom model class can then handle the formatting for you.
public class EmpresaGridModel
{
public string Cgc { get; set; }
public string CgcFormatted
{
return FormataCgc(this.Cgc);
}
//properties for the other fields will have to be created as well obviously
}
Your telerik grid can then bind directly to the CgcFormatted property

Categories