Criteria for where predicate using columns from two tables and functions in NHibernate - tsql

My goal is to fetch two values from two tables joined together and perform a comparison which if true, outputs the rows from table 1. The TSQL code below illustrates the query, question is whether there is a way to do the third predicate in the where clause using NHibernate criteria using session.CreateCriteria:
declare #currentSystemTime datetimeoffset = '2015-07-22 18:42:16.1172838 +00:00'
select
inst.ExpiryDate,
ent.DaysToExpiryNotificationStart,
convert(date, dateadd(day, -ent.DaysToExpiryNotificationStart, inst.ExpiryDate)) as NotificationStart,
convert(date, #currentSystemTime) as CurrentSystemTime,
*
from
Instances inst
inner join Entries ent on inst.Entry_id = ent.Id
where
inst.ExpiryDate is not null
and ent.DaysToExpiryNotificationStart is not null
and convert(date, #currentSystemTime) >= convert(date, dateadd(day, -ent.DaysToExpiryNotificationStart, inst.ExpiryDate))
The properties are defined as follows in the entity classes:
public virtual DateTimeOffset? ExpiryDate { get; set; }
public virtual int? DaysToExpiryNotificationStart { get; set; }
I am using Fluent NHibernate to map these. Are manual queries via CreateQuery or CreateSQLQuery the only way to go? If there is an easier way to accomplish this task, I am open. Any help will be appreciated.
Thanks,
Shawn

session.Query<Instances>()
.Where(i => Datetime.Today >= i.ExpiryDate.AddDays(i.DaysToExpiryNotificationStart))
adddays is not yet natively supported but can be added quite easyly. see here to make AddDays work
the same slightly different can be done with QueryOver
session.QueryOver<Instances>()
.Where(i => Datetime.Today >= i.ExpiryDate.AddDays(i.DaysToExpiryNotificationStart))
public static class QueryOverExtensions
{
public static void Register()
{
ExpressionProcessor.RegisterCustomProjection(() => default(DatTime).AddDays(1), QueryOverExtensions.ProcessAddDays);
}
private static IProjection ProcessAddDays(MethodCallExpression methodCallExpression)
{
IProjection property = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[0]).AsProjection();
return (Projections.SqlFunction("addDays", NHibernateUtil.DateTime, NHibernateUtil.Int32, property));
}
}
Note: I'm not sure if adddays is already defined as sql function. you might need to register one in the driver

Related

Linq expression with Join, and conditional OrderBy / OrderByDescending

I am building a Linq expression, I don't have the ability to use Dynamic Linq in this project. Here is my initial statement.
var orderListQuery = context.Orders.Where(oExpr)
.Join(context.Members,
o => o.MemberId,
m => m.Id,
(o, m) => new {Order = o, Member = m})
.Where(oM => oM.Member.CustomerId == custId);
The next part is where I would like to conditionally add a descending expression:
orderListQuery = descending
? orderListQuery.OrderByDescending(sortProperty)
: orderListQuery.OrderBy(sortProperty);
the error which I already saw a post on is that I need to explicitly list the <TSource, TKey>, but due to the complexity of the syntax from the join, I have no idea how to explicitly list these. I tried <Order, string>, which does not work.
Anything helps, thanks.
Because you forget to write what goal you are trying to reach (no specification), it is a bit unclear what your problem is, and how this can be solved. I assume that your problem is not the join (although it can be simplified), but the problem is how users of your method can provide a sort property.
How to specify and use the Sort Property
The problem is, that the caller of your function decides about the sort property. Of course you only want to make useful and very reusable functions, so you don't want to limit the user of your function, he can specify any sort property he wants, as long as it is something the result of your join can be sorted on.
The result of your join contains data from a sequence of Orders and a Sequence of members. It is obvious that your user can only request to sort the result of your join on (a combination of) values from Orders and or Members.
The key selector in Queryable.OrderBy has the format Expression<Func<TSource, TKey>> keySelector. In this format the TSource is the result of your join.
As the user can only sort on values from Orders and/or Members, his key selector should be a function that selects a key from Orders and/or Members
public MyResult MyQuery<TKey>(Expression<Func<Order, Member, TKey> keySelector,
SortOrder sortOrder)
{
... // TODO: your query?
}
Examples of usage would be:
// order by Order.Id:
var result = MyQuery( (order, member) => order.Id, SortOrder.Ascending);
// order by Member.Name:
var result = MyQuery( (order, member) => member.Name, SortOrder.Descending);
// order by something complicated
var result = MyQuery( (order, member) => new{Id = order.Id, Name = member.Name},
SortOrder.Ascending);
Now that the interface is specified, let's define some helper classes and fill your function
class MyResult
{
public Order Order {get; set;}
public Member Member {get; set;}
}
class SortableMyResult<TKey>
{
public TKey SortKey {get; set;}
public MyResult MyResult {get; set;}
}
MyResult MyQuery<TKey>(
Expression<Func<Order, Member, TKey> keySelector, SortOrder sortOrder)
{
var query = context.Orders.Where(oExpr)
.Join(context.Members,
order => order.MemberId,
member => member.Id,
(order, member) => new SortableMyResult<TKey>()
{
SortKey = KeySelector(order, member),
MyResult = new MyResult()
{
Order = order,
Member = member,
}
))
.Where(....);
What I've done, is that your original result is put in a MyResult object. I have also calculated the value of SortKey using the Expression that the caller provided, and put the SortKey and the MyResult in a SortableMyResult.
Did you notice that the return value of your Expression is a TKey, which is also the type of property SortKey?
Because I've defined the helper classes, my compiler can check that I did not make any errors.
Do the sorting:
IQueryable<SortableMyResult<TKey>> sortedMyResult;
switch (sortOrder)
{
case sortOrder.Ascending:
sortedMyResult = query.OrderBy(item => item.SortKey);
break;
case sortOrder.Descending:
sortedMyResult = query.OrderByDescending(item => item.SortKey);
break;
default: // do not order
sortedMyResult = query;
break;
}
Finally, extract and return MyResult:
return sortedMyResult.Select(sortedMyResult => sortedMyResult.MyResult);
If you want to make your function even more generic, you can let the caller give the opportunity to provide an IComparer (and if he doesn't, use the default comparer for TKey):
MyResult MyQuery<TKey>(
Expression<Func<Order, Member, TKey> keySelector,
SortOrder sortOrder,
IComparer<TKey> comparer = EqualityComparer<TKey>.Default)
{
...
}
Thanks for that incredible detailed answer. It has been a few months now, and more about MVC and the existing application code is becoming clear. What had happened in my case was that we had an extension method defined that allowed OrderBy and OrderByDescending to accept strings passed in by our sorting module from the front end. e.g:
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName)
{
return GetExpression(source, propertyName);
}
This would then funnel it into a GetExpression function that returned an IOrderedQueryable<T>
I had not included the reference to this, and due to my inexperience with both the framework and the application I was cranking my head for essentially no reason.

Passing a comparison function and value as parameters into Entity Framework

This is a follow-up on question on Passing a comparison function as a parameter into Entity Framework.
I would like to pass a comparison function and a value into a search function that queries Entity Framework. The comparison function has to operate on different properties depending on the value. E.g., my data is
class DataItem
{
[Key]
public int ID { get; set; }
public bool Active { get; set; }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
and I need a context method
public List<DataItem> SearchValue(Func<string, string, bool> op, string value)
{
if (value.Length < 10)
return DataItem.Where(di => di.Active && op(di.Prop1, value)).ToList();
else
return DataItem.Where(di => di.Active && op(di.Prop2, value)).ToList();
}
that I can then call like
List<DataItem> list = context.SearchValue((s1, s2) => s1 == s2, "A");
where I also need different comparison functions (all canonical).
Program compiles alright, but on running I get a "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." error.
Loading the DataItems into memory is not an option since I'll have around a billion of them. That's why I'm looking for a server-side solution. All the operations I'm using are canonical, so they can be translated into SQL queries. My question is just: how can I pass them as parameters?
This issue means that your LINQ query cannot be converted to a proper SQL query for backend execution.All LINQ to SQL or Entity framework queries get converted to SQL which is run on the backend and if they have some operation which could not be translated to SQL this error can come.
I think you should first bring your data in memory using .ToList() or .AsEnumerable() and then use 'Op' operation on that.That should solve the problem
Unfortunately you can't do it with LinqToEntities. You have to create sql query manually or call Database function...

Support for Table Valued Functions in EF6 Code First?

Is it possible to call a TVF in EF6 Code First?
I started a new project using EF6 Database first and EF was able to import a TVF into the model and call it just fine.
But updating the model became very time consuming and problematic with the large read-only db with no RI that I'm stuck dealing with.
So I tried to convert to EF6 code first using the Power Tools Reverse Engineering tool to generate a context and model classes.
Unfortunately the Reverse Engineering tool didn't import the TVFs.
Next I tried to copy the DBFunctions from my old Database First DbContext to the new Code First DbContext, but that gave me an error that my TVF:
"cannot be resolved into a valid type or function".
Is it possible to create a code first Fluent mapping for TVFs?
If not, is there a work-around?
I guess I could use SPs instead of TVFs, but was hoping I could use mostly TVFs to deal with the problematic DB I'm stuck with.
Thanks for any work-around ideas
This is now possible. I created a custom model convention which allows using store functions in CodeFirst in EF6.1. The convention is available on NuGet http://www.nuget.org/packages/EntityFramework.CodeFirstStoreFunctions. Here is the link to the blogpost containing all the details: http://blog.3d-logic.com/2014/04/09/support-for-store-functions-tvfs-and-stored-procs-in-entity-framework-6-1/
[Tested]
using:
Install-Package EntityFramework.CodeFirstStoreFunctions
Declare a class for output result:
public class MyCustomObject
{
[Key]
public int Id { get; set; }
public int Rank { get; set; }
}
Create a method in your DbContext class
[DbFunction("MyContextType", "SearchSomething")]
public virtual IQueryable<MyCustomObject> SearchSomething(string keywords)
{
var keywordsParam = new ObjectParameter("keywords", typeof(string))
{
Value = keywords
};
return (this as IObjectContextAdapter).ObjectContext
.CreateQuery<MyCustomObject>(
"MyContextType.SearchSomething(#keywords)", keywordsParam);
}
Add
public DbSet<MyCustomObject> SearchResults { get; set; }
to your DbContext class
Add in the overriden OnModelCreating method:
modelBuilder.Conventions.Add(new FunctionsConvention<MyContextType>("dbo"));
And now you can call/join with
a table values function like this:
CREATE FUNCTION SearchSomething
(
#keywords nvarchar(4000)
)
RETURNS TABLE
AS
RETURN
(SELECT KEY_TBL.RANK AS Rank, Id
FROM MyTable
LEFT JOIN freetexttable(MyTable , ([MyColumn1],[MyColumn2]), #keywords) AS KEY_TBL
ON MyTable.Id = KEY_TBL.[KEY]
WHERE KEY_TBL.RANK > 0
)
GO
I was able to access TVF with the code below. This works in EF6. The model property names have to match the database column names.
List<MyModel> data =
db.Database.SqlQuery<MyModel>(
"select * from dbo.my_function(#p1, #p2, #p3)",
new SqlParameter("#p1", new System.DateTime(2015,1,1)),
new SqlParameter("#p2", new System.DateTime(2015, 8, 1)),
new SqlParameter("#p3", 12))
.ToList();
I actually started looking into it in EF6.1 and have something that is working on nightly builds. Check this and this out.
I have developed a library for this functionality. You can review my article on
UserTableFunctionCodeFirst.
You can use your function without writing SQL query.
Update
First of all you have to add reference to the above mentioned library and then you have to create parameter class for your function. This class can contain any number and type of parameter
public class TestFunctionParams
{
[CodeFunctionAttributes.FunctionOrder(1)]
[CodeFunctionAttributes.Name("id")]
[CodeFunctionAttributes.ParameterType(System.Data.SqlDbType.Int)]
public int Id { get; set; }
}
Now you have to add following property in your DbContext to call function and map to the property.
[CodeFunctionAttributes.Schema("dbo")] // This is optional as it is set as dbo as default if not provided.
[CodeFunctionAttributes.Name("ufn_MyFunction")] // Name of function in database.
[CodeFunctionAttributes.ReturnTypes(typeof(Customer))]
public TableValueFunction<TestFunctionParams> CustomerFunction { get; set; }
Then you can call your function as below.
using (var db = new DataContext())
{
var funcParams = new TestFunctionParams() { Id = 1 };
var entity = db.CustomerFunction.ExecuteFunction(funcParams).ToList<Customer>();
}
This will call your user defined function and map to the entity.

Custom extension function in select statment of an EF query

In our database we have a table that looks like this which we have mapped to an entity in our Database-First EF model:
CREATE TABLE Texts(
Id integer,
Eng nvarchar(max),
Nob nvarchar(max),
...
)
A row in this table may be quite large, so we only want to get the value of the column that is currently need by a language selection the user has done.
My idea was to have an extension function to do it for me, but I dont have any idea nor can't find any way to write it (if it is even possible). I have tried a few variants, but (obviously) it failed with an exception that states that it cannot be translated into a store expression. So I am a bit stuck.
The idea of usage for this function is:
context.Codes.Where(row => row.Id == 234).Select(row => new {
row.Id,
Text = Text.GetLocalizedText("Eng") // This should generate an SQL that only retrieves the Eng
// column of the Text navigation property (which is
// connected to the Texts table.
});
That should generate a select similar to this (which are similar to the example above except using Text.Eng directly):
SELECT
[Extent1].[Id] AS [Id],
[Extent2].[Eng] AS [Eng]
FROM [dbo].[Codes] AS [Extent1]
INNER JOIN [dbo].[Texts] AS [Extent2] ON [Extent1].[TextId] = [Extent2].[Id]
WHERE 234 = [Extent1].[Id]
Does anyone know if this is possible, and if it is; how to write it? If it isn't possible, does anyone have any other idea on how to solve this without retrieving the whole Text entity with all of it's columns?
An extension method of IQueryable<Code> would work but it is not as flexible as you probably want to have it because you would need to have an extension per type of projection you want to perform and you cannot work with an anonymous result object.
The idea is basically like so:
You need a named class (instead of anonymous) which you can project into:
public class CodeData
{
public int Id { get; set; }
public string LocalizedText { get; set; }
}
And then an extension method with the language parameter:
public static class CustomExtensions
{
public static IQueryable<CodeData> SelectCodeData(
this IQueryable<Code> query, string language)
{
switch (language)
{
case "Eng":
return query.Select(code => new CodeData
{
Id = code.Id,
LocalizedText = code.Text.Eng
});
case "Nob":
return query.Select(code => new CodeData
{
Id = code.Id,
LocalizedText = code.Text.Nob
});
//... more languages
}
throw new ArgumentException("Invalid language code.", "language");
}
}
Then it can be called like this:
using CustomExtensions;
// ...
IQueryable<CodeData> codeDataQuery = context.Codes
.Where(row => row.Id == 234)
.SelectCodeData("Eng");

LINQ to Entities for subtracting 2 dates

I am trying to determine the number of days between 2 dates using LINQ with Entity Framework. It is telling me that it does not recognize Subtract on the System.TimeSpan class
Here is my where portion of the LINQ query.
where ((DateTime.Now.Subtract(vid.CreatedDate).TotalDays < maxAgeInDays))
Here is the error I receive in the VS.NET debugger
{"LINQ to Entities does not recognize the method 'System.TimeSpan Subtract(System.DateTime)' method, and this method cannot be translated into a store expression."}
Am I doing something wrong or is there a better way to get the number of days between 2 DateTimes in the entity framework?
thanks
Michael
The accepted answer is better in this case, but for reference you can use the EntityFunctions class to perform operations on dates, among other things.
where (vid.CreatedDate >= EntityFunctions.AddDays(DateTime.Now, -maxAgeInDay))
Here is how I got it to work
I defined a datetime variable that represents the oldest date
DateTime oldestDate = DateTime.Now.Subtract(new TimeSpan(maxAgeInDays, 0, 0, 0, 0));
...
then I modified the where portion of the LINQ query
where (vid.CreatedDate >= oldestDate )
worked like a charm - thanks Micah for getting me to think about the expression tree
You can also use System.Data.Objects.EntityFucntions:
currentDate = DateTime.Now;
...
where EntityFunctions.DiffDays(currentDate, vid.CreatedDate) < maxAgeIdDays
All functions from EntityFunctions are only for Linq-to-entities and are mapped to SQL functions.
You run into these kind of isses because the predicate needs to be translated to an expression tree. And translation process doesn't recognize the DateTime.Now.Subtract method.
The fact is that by design, LINQ to Entities needs to translate the whole query to SQL statements. That's where it cannot recognize Subtract method. It will occur whenever you try to use a C#/VB method inside a query. In these cases you have to figure out a way to bring out that part from the query.
This post explains a bit more:
http://mosesofegypt.net/post/LINQ-to-Entities-what-is-not-supported.aspx
You may define new property in your model:
public DateTime StartDate{ get; set; }
public DateTime EndDate{ get; set; }
public TimeSpan CalculateTime{
get
{
return EndDate.Subtract(StartDate);
}
}
Now, you may use something like that:
var query = from temp in db.Table
select new MyModel {
Id = temp.Id,
Variable1 = temp.Variable1,
...
EndDate = temp.EndDate,
StartDate = temp.StartDate
}
When you have look at result, you may use return such as:
return query
Now, in query, we have CalculateTime (subtract between EndDate and Startdate).