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

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"));
...

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

How to do GroupBy on complex object with an IQueryable

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).

How to return a string and a list of elements together in c#?

Is there any way to return a string parameter and a list to the method in c#?
List<cCountry> countries = new List<cCountry>();
countries = GetCountries(userProfile.Country);
private List<cCountry> GetCountries(string p)
{
inputCollection = new cDTOCollection<cDTOBase>();
outputCollection = new cDTOCollection<cDTOBase>();
outputCollection = UpdateProfileBizobj.ProcessRequest(ActionConstants.ActionGetCountriesList, null);
List<cCountry> countries = new List<cCountry>();
cDTOSingleValue SelectedCountryID = new cDTOSingleValue();
foreach (cDTOCountry countryItem in outputCollection)
{
if (p == countryItem.CountryName)
SelectedCountryID.Id = countryItem.CountryID;
countries.Add(Mapper.Map<cDTOCountry, cCountry>(countryItem));
}
countries.Remove(countries[0]);
return countries;
}
Like in the method above I need to return a parameter
SelectedCountryID and also the countries list.Is there any way to
return both?
populate and return an object instead.
public class ReturnObject
{
public List<cCountry> Countries { get; set; }
public guid SelectedCountryID { get; set; } // don't think you defined what type SelectedCountryID is in your question
}
But if you find yourself needing to return more than 1 thing, it's probably an indication that your method is doing too much and should be refactored.
Why can't you reuse the value that you are sending to the method?
return Tuple.Create(new List<Country>(),"astring")
Answer to your question
You can also use the out modifier on a parameter to pass by reference, which would let the method modify the resulting object, effectively returning it. That's a pretty bad design for most cases though, and you probably refactor your methods into smaller pieces or wrap things up into better objects.
In your code:
private List<cCountry> GetCountries(string p, out cDTOSingleValue SelectedCountryID)
{
//your code
}
Potential Refactoring
Looking at your code it, seems your really are trying to do two separate things.
Getting Countries and mapping them
Finding the last country whose name matches the parameter passed in
So as long as your country list isn't ridiculously large, making two separate method calls will make your code more readable and more maintainable. I like using LINQ to manipulate the in-memory collections.
I'd personally use one method to fetch the data.
private List<cDTOCountry> GetCountries()
{
inputCollection = new cDTOCollection<cDTOBase>();
outputCollection = new cDTOCollection<cDTOBase>();
return UpdateProfileBizobj.ProcessRequest(ActionConstants.ActionGetCountriesList, null);
}
And then later I'd process the data as needed:
var preMappedCountries = GetCountries();
var mappedCountries = preMappedCountries
.Select(c => Mapper.Map<cDTOCountry, cCountry>(c)) //map the data
.Skip(1) //skip the first element (the remove index 0)
.ToList(); //cast to List. Read more on LINQ's deferred execution.
var lastMatchingName = preMappedCountries
.LastOrDefault(c => c.Name == p); //gets the last country with matching name
The benefit to separating the logic into pieces is potential reuse of methods. If you ever find yourself needing to get data without mapping, you can do just that and skip all the LINQ logic. This way the logic that gets data is distinct from the logic that matches country names.
In your case an out param seems more appropriate. Otherwise as Kevin suggested you can return a Tuple.

Passing LINQ Results to a function

I have a class called UserInfo that contains details about a given user.
There are several places in code where the data might be queried and I'd like to have a single function to fill the UserInfo object with the corresponding data from the Linq Query.
var userData = dc.Users.Where(λ => (λ.Login == username) && λ.Active)
.Select(λ => new { λ.ID, Salt = λ.Seasonings.Single().Salt, λ.Login, λ.PassHash, λ.Admin, λ.Trusted, λ.E_mail, λ.Name, λ.Phone, λ.Note, λ.RegistrationDate }).SingleOrDefault();
string tmppass = generatePassHash(password, userData.Salt);
if (userData.PassHash.Trim() == tmppass.Trim())
{
ID = userData.ID;
// Here is the stuff i'd like to move to a function
_user._id = userData.ID;
_user._userState = State.NotAuthorized;
_user._username = userData.Login;
_user._name = userData.Name;
_user._email = userData.E_mail;
_user._phone = userData.Phone;
_user._notes = userData.Note;
...
}
How do I properly set up a function to accept this anonymous type as an argument? Do I need to declare a new interface or is there a simpler way?
Thanks for the help!
PS- sorry for the excessive underscores, nested classes make things a bit messy.
For simplicity's sake, couldn't you just have all your routines accept the entity object itself? E.g. if dc.Users is a table of type UserEntity, skip the Select():
UserEntity userData = dc.Users.Where(
λ => (λ.Login == username) && λ.Active).SingleOrDefault();
And if that isn't acceptable, encapsulate a more limited object which takes UserEntity as a ctor parameter:
public class UserInfo
{
public string Name {get;set;}
public string Phone {get;set;}
...
public UserInfo(UserEntity entity)
{
this.Name = entity.Name;
this.Phone = entity.Phone;
...
}
}
var userData = dc.Users.Where(
λ => (λ.Login == username) && λ.Active).Select(
λ => new UserInfo(λ)).SingleOrDefault();
This abstracts the messy conversion away from the rest of your application. However, in general I'd recommend simply using the entity object, since it makes it much easier to go in reverse when you need (passing a modified entity back to the DAL).
I'm afraid It's not possible to pass an anonymous type as an argument to another method.
But I wonder why you are using an anonymous type and not working with user in the first place?
PS : BTW if you are applying the same pattern through out your code why don't you implement a concrete class for UserInfo ?

Db4o StartsWith and ignore case

The following query takes a while to return:
db.Query<Person>(x => x.StartsWith("Chr", StringComparison.CurrentCultureIgnoreCase))
is there a way to get this working correctly? ie faster?
Maybe you ran into a limitation of db4o’s query-optimization. Normally Native Queries and LINQ-Queries are translated into a low level SODA-query. When this optimization fails, db4o instantiates the objects in the database in order to execute the query. As you can imagine this can be quite slow.
The best current solution is to use a SODA directly for this case. For example a class with one property:
public class SimpleObject
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
The native query like this:
var result = db.Query<SimpleObject>(x => x.Name.StartsWith ("Chr",StringComparison.CurrentCultureIgnoreCase));
Can be represented by this SODA-Query:
IQuery query = db.Query();
query.Constrain(typeof (SimpleObject)); // restrict to a certain class
query.Descend("name").Constrain("Chr").StartsWith(false); // the field 'name' starts with 'chr', case-insensitive
foreach (var s in query.Execute())
{
//
}
I hope future versions of the Query-Optimizer support this case directly.
adding and index on the column you're comparing would probably help.
http://developer.db4o.com/Documentation/Reference/db4o-7.4/net35/tutorial/docs/Indexes.html#outline219

Categories