Call a custom method in linq query - c#

I need to call a custom method in linq query, e.g.:
IQueryable<Person> query = _db.Persons.OrderBy(p => Decrypt(p.Name));
However, in this case Decrypt gives me an exception.
I also tried with this approach:
IQueryable<Person> query = _db.Persons.AsEnumerable()
.OrderBy(p => Decrypt(p.Name))
.AsQueryable<Person>();
However in this case it works but as I'm using this query as a source (SelectMethod) for my ListView, paging is not working when using DataPager control.

You need to actually figure out why IS the paging not working.
It's possible to create custom SQL function and let Decrypt work. For that, you need to create SQL function Decrypt that does the work.
This is simple example:
(SQL part)
CREATE FUNCTION ReverseCustName(#string varchar(100))
RETURNS varchar(100)
AS
BEGIN
DECLARE #custName varchar(100)
-- Implementation left as exercise for users.
RETURN #custName
END
and LINQ part
[Function(Name = "dbo.ReverseCustName", IsComposable = true)]
[return: Parameter(DbType = "VarChar(100)")]
public string ReverseCustName([Parameter(Name = "string",
DbType = "VarChar(100)")] string #string)
{
return ((string)(this.ExecuteMethodCall(this,
((MethodInfo)(MethodInfo.GetCurrentMethod())),
#string).ReturnValue));
}
See this for more information: http://msdn.microsoft.com/en-us/library/bb386973(v=vs.110).aspx

if its LINQ to SQL you cant since it tries to render that to real SQL.
try to cast the Persons to another object class of your own and then it will be Linq to Object and there it will work. i had this issue a few times, i played with it a little bit until it worked.
BTW i think one of the linq extensions is looking for an IComparable, so worse case you can just implement one

I will give you a one-liner which "could" work, but please provide the exception as well.
var query = _db.Persons.AsEnumerable().OrderBy(p => Decrypt(p.Name));
Your problem is probably, but it is hard to know since we are not given the exception, that you are trying to get your database to execute Decrypt which is not possible. The added ToList() just enumerates, ie fetches the data from database in this case, and then we can order in memory.
You won't get paging to work the way you want since the database cannot execute the query.

Related

Linq select where has at least one entry in another table

I have the following structure that I wan't to query using Linq, specifically Linq to Entities (Enitity Framework).
Table1: RouteMeta
Table2: SitePage
Multiple SitePages can link to the same RouteMeta.
I'm querying the Route Meta to select a number of rows. I'm using a generic repository, currently like this:
return r.Find().ToList();
There's nothing special about it - the Find method accepts an optional linq expression, so I could do something like this:
return r.Find(x => x.Status=1).ToList();
However, what I actually want to do is to select rows from RouteMeta where at least one linked row exists in SitePages with a property IsPublished = true.
return r.Find(x => x.SitePages("where y => y.IsPublished = true");
Obviously, the above isn't correct, I'm just trying to explain the scenario better.
Any advice appreciated.
try something like
return r.Find(x=>x.Sitepages.Any(y=>y.Published))?
I'd also suggesting using a profiler if possible to check that this translates properly into SQL. It probably should do but it depends on how your repository works.

How to generate nHibernate raw sql for Insert/Update without execution?

Is it possible to generate a raw SQL statement for insert/update operations using nHibernate without actually executing them? Assuming of course that everything (mappings, connectionStrings, etc.) is properly configured?
The closest thing I've found is to call:
Session.SessionFactory.GetClassMetadata(typeof(Client))
Which returns an object of type SingleTableEntityPersister containing SQLIdentityInsertString, that looks like this:
INSERT INTO Client (FirstName, LastName) values (?, ?)
But it would still require me to bind all of the properties manually, and on top of that SQLIdentityInsertString is a protected property. Are there any proper ways of doing that?
Okay, the closest thing I've found is to construct your own sql query with a string builder. First you need to extract your class metadata:
var metaData = Session.SessionFactory.GetClassMetadata(typeof(Client)) as SingleTableEntityPersister;
Then you can retrieve other information, such as:
var propertyNames = metaData.PropertyNames;
var tableName = metaData.TableName;
var firstPropertyValue = metaData.GetPropertyValue(client, propertyNames[0], EntityMode.Poco);
Once you have that information you can construct your own query manually. Not exactly the solution I wanted, but it's as close as it gets, I think.
However, one thing to note is that Session.CreateSQLQuery(string) method is currently bugged, and as a result SetParameter method doesn't work with more than 10 named parameters. There already seems to be a bug report created for this on NHbiernate's Jira.

LINQ to entities : cannot call a method

I'm aware there are alreay a lot of posts concerning this issue, but i can't seem to find a solution for this.
Here's my Ling to Entities query :
IEnumerable<Tblstamp> changes = (from c in userGSN.edb.Tblstamp
where (c.Ts_Date >= userGSN.DateLastCheck &&
TimeSpan.Parse(c.Ts_Time) >= userGSN.TimeLastCheck)
orderby c.Ts_Id ascending
select c);
I want to compare c.Ts_time to userGSN.TimeLastCheck, but for that I have to convert the c.Ts_Time to a timespan (it is a string, and comes from a database I can't modify, tried everything). I also can't do the converting before the query in an other variable since I can't access it outside of the query.
Obviously, I get an error for trying to use the TimeSpan.Parse method in my query, but I can't find any workaround to this. I have tried using LINQ to Object but since I am really not used to it I couldn't make the equivalent query that i have here.
I am aware of the problem, I'm just trying to find a workaround and need some help please !
EDIT :
So I tried the DateDiff function as suggested :
IEnumerable<Tblstamp> changes = (from c in userGSN.edb.Tblstamp
where (c.Ts_Date >= userGSN.DateLastCheck && SqlFunctions.DateDiff("second",userGSN.TimeLastCheck,c.Ts_Time).Value > 0 )
orderby c.Ts_Id ascending
select c
);
but it gives me the same error : "LINQ to Entities does not recognize the method 'System.Nullable1[System.Int32] DateDiff(System.String, System.Nullable1[System.TimeSpan], System.String)' method, and this method cannot be translated into a store expression."
Even though it clearly says here "You cannot call this function directly. This function can only appear within a LINQ to Entities query.", which is exactly what I'm doing ?!
There's no clean way to do this, so you'll have to think outside the box. Try using SqlFunctions.DateDiff to perform the check. You'll need to adapt it for your usage, but:
SqlFunctions.DateDiff("second", c.Ts_Time, userGSN.TimeLastCheck.ToString()) > 0
There are other methods that you can use, if DateDiff is not suitable. See EntityFunctions as well.
Depending on what you're using to query the data, you might want System.Data.Entity.SqlServer.SqlFunctions instead.
Every code you write in LinQ to get data from database must convert to a valid T-SQL query with valid statements. Not all C# methods have a correspondance in T-SQL so whenever you use them in your code while fatching data you will get errors.
You should either do modifications on the SQL side or use C# spesific functions after fething data from database, while working with entities in memory, or use fıunstions which comply with SQL statements.
If you can't get EF to translate your query to SQL, maybe you could do it yourself? See Writing SQL queries for entities
Linq to Entities does not support all functions since the query has to be translated to SQL:
from c in userGSN.edb.Tblstamp.AsEnumerable()
//now you are allready getting the data from the database
//so be carefull with that because you will have bad performance

LINQ to Entities does not recognize the method 'Int32 IndexOf(System.String, System.StringComparison)' method

I have executed a linq query by using Entityframework like below
GroupMaster getGroup = null;
getGroup = DataContext.Groups.FirstOrDefault(item => keyword.IndexOf(item.Keywords,StringComparison.OrdinalIgnoreCase)>=0 && item.IsEnabled)
when executing this method I got exception like below
LINQ to Entities does not recognize the method 'Int32 IndexOf(System.String, System.StringComparison)' method, and this
method cannot be translated into a store expression.
Contains() method by default case sensitive so again I need to convert to lower.Is there any method for checking a string match other than the contains method and is there any method to solve the indexOf method issue?
The IndexOf method Of string class will not recognized by Entity Framework, Please replace this function with SQLfunction or Canonical functions
You can also take help from here or maybe here
You can use below code sample:
DataContext.Groups.FirstOrDefault(item =>
System.Data.Objects.SqlClient.SqlFunctions.CharIndex(item.Keywords, keyword).Value >=0 && item.IsEnabled)
You really only have four options here.
Change the collation of the database globally. This can be done in several ways, a simple google search should reveal them.
Change the collation of individual tables or columns.
Use a stored procedure and specify the COLATE statement on your query
perform a query and return a large set of results, then filter in memory using Linq to Objects.
number 4 is not a good option unless your result set is pretty small. #3 is good if you can't change the database (but you can't use Linq with it).
numbers 1 and 2 are choices you need to make about your data model as a whole, or if you only want to do it on specific fields.
Changing the Servers collation:
http://technet.microsoft.com/en-us/library/ms179254.aspx
Changing the Database Collation:
http://technet.microsoft.com/en-us/library/ms179254.aspx
Changing the Columns Collation:
http://technet.microsoft.com/en-us/library/ms190920(v=sql.105).aspx
Using the Collate statement in a stored proc:
http://technet.microsoft.com/en-us/library/ms184391.aspx
Instead you can use this method below for lowering the cases:
var lowerCaseItem = item.ToLower();
If your item is of type string. Then this might get you through that exception.
Erik Funkenbush' answer is perfectly valid when looking at it like a database problem. But I get the feeling that you need a better structure for keeping data regarding keywords if you want to traverse them efficiently.
Note that this answer isn't intended to be better, it is intended to fix the problem in your data model rather than making the environment adapt to the current (apparently flawed, since there is an issue) data model you have.
My main suggestion, regardless of time constraint (I realize this isn't the easiest fix) would be to add a separate table for the keywords (with a many-to-many relationship with its related classes).
[GROUPS] * ------- * [KEYWORD]
This should allow for you to search for the keyword, and only then retrieve the items that have that keyword related to it (based on ID rather than a compound string).
int? keywordID = DataContext.Keywords.Where(x => x.Name == keywordFilter).Select(x => x.Id).FirstOrDefault();
if(keywordID != null)
{
getGroup = DataContext.Groups.FirstOrDefault(group => group.Keywords.Any(kw => kw.Id == keywordID));
}
But I can understand completely if this type of fix is not possible anymore in the current project. I wanted to mention it though, in case anyone in the future stumbles on this question and still has the option for improving the data structure.

Linq To SQL: Sort Query by Arbitrary Property(Column) Name

I have a larger/more complex problem, but for simplicity sake, let us consider the following:
Let us say that I have table in the SQL DataBase called Product, having two columns, ID (int, primary key) and Name (varchar/string). I also have a simple LINQ DataContext.
I have a query constructed and handed to “my” function. Let us say it is something like: (though it may be a bit more complex)
IQueryable<Product> query = from p in db.Products select p;
Once my method gets this query, passed in as a parameter, it has to change the sort order e.g.
IQueryable<Product> sortedQuery = query.OrderBy(x => x.Name);
I would like to make this more generic i.e. to specify the field to sort on. Normally, I can do a switch statement that takes a string. However I would like to know if there is a way to pass the parameter directly. I intend to extend this to other Database tables, so these switch statements would get tedious.
I was trying something like:
IQueryable<Product> sortedQuery = query.OrderBy(x => (typeof(Product)).GetProperty(“Name”));
But this does not work. I also want to ensure that the LINQ to SQL is maintained i.e. the sort to be done on the SQL Server. Hence if I debug, I should get a SQL query from this LINQ query.
Thank you in advance for your help.
You could use Dynamic Linq for this purpose.
See here Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)
Then you can make calls like this:
var query = DBContext.Users.Where( "Age > 3" ).OrderBy( "Name asc" );
Try this out instead:
query.OrderBy(x => x.GetType().GetProperty(“Name”).GetValue(x, null));
You can't just grab the property. You need to grab the value off of that property, hence the call to GetValue.
It is not as easy as it seems. The LINQ to SQL engine parses the expression you pass to the OrderBy method in order to obtain the name of the property you are referencing, then uses this information to compose a plain SQL order by clause.
I guess that maybe it can be done by using reflection, anyway. Maybe you can get something useful from the accepted answer of this SO question.

Categories