I have a Table-Valued function which I would like to use as an IQueryable in a LINQ statement. I have created the following in my DbContext class:
[DbFunction("dbo","MyTableValuedFunction")]
public virtual IQueryable<MyClass> MyFunction(string keyword)
{
var keywordsParam = new System.Data.Entity.Core.Objects.ObjectParameter("keywords", typeof(string))
{
Value = keyword
};
return (this as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext
.CreateQuery<MyClass>("dbo.MyTableValuedFunction(#keywords)", keywordsParam);
}
The result of dbo.MyTableValuedFunction(#keywords) match an existing class "MyClass". An example of how I'd like to use this function:
MyClass example = (from a in dbContext.MyClass
join b in dbContext.MyFunction("exampleKeyword")
on a.Id equals b.Id
join c in dbContext.MyOtherClass
on a.SomeId equals c.Id
select a);
...but enumerating this IEnumerable throws an exception:
'dbo.MyTableValuedFunction' cannot be resolved into a valid type or function.
I have tried reducing the function to just one column, and changing the type to <int>, but this doesn't work, so I am not sure what is happening here; it's like the TVF is not being correctly found/recognised/used?
I have also tried following https://weblogs.asp.net/Dixin/EntityFramework.Functions#Table-valued_function and on the off-chance there's some subtle difference, I create the function with:
[Function(FunctionType.TableValuedFunction, "MyTableValuedFunction", Schema = "dbo")]
...but I run into exactly the same problem.
First if you used this extension, you should write following code according to your code:
[DbFunction("dbo","MyTableValuedFunction")]
public virtual IQueryable<MyClass> MyFunction(string keyword)
{
var keywordsParam = new System.Data.Entity.Core.Objects.ObjectParameter("keywords", typeof(string))
{
Value = keyword
};
return (this as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext
.CreateQuery<MyClass>("[Your DbContext Name].MyFunction(#keywords)", keywordsParam);
}
According to the extension codes, the EF uses conventions to translate the C# syntax into sql syntax. for this reason, you should register your functions in dbContext first as the link mentioned.
Related
I have the following enum:
public enum WorkType
{
Type1,
Type2,
Type3,
Type4,
Type5,
Type6
}
and a class
public class Work {
public WorkType Type {get; set;}
....
}
and an extension method:
public static partial class WorkTypeExtensions
{
public static bool IsHighValueWork(this WorkType value)
{
switch (value)
{
case WorkType.Type1:
case WorkType.Type2:
return true;
default:
return false;
}
}
}
and SQL Linq query
public List<Work> GetHighValueWork()
{
var query = Context.Work.Where( w => w.IsHighValueWork());
return query.ToList();
}
This is a simplified version of my problem. This query used to work, but it is not working any more after the code was converted from net core 2.1 to 3.1. The error msg is
The query could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(). I don't want to change it to
public List<Work> GetHighValueWork()
{
var query = Context.Work.Where( w => w.Type == WorkType.Type1 || w.Type == WorkType.Type2);
return query.ToList();
}
Because actual function is very complex. I searched it seems LINQ Expression Func can be used, but I haven't figured that yet. What is the best way to do this?
IsHighValueWork is just a simple C# method. There is no way to convert that function to SQL by EF.
It is really explained well in that link, why it was working in .net core 2.1. It seems that, in previous versions when EF Core couldn't convert an expression that was part of a query to either SQL or a parameter, it automatically evaluated the expression on the client.
And it is really bad. Because, as noted:
For example, a condition in a Where() call which can't be translated can cause all rows from the table to be transferred from
the database server, and the filter to be applied on the client.
So, it seems previously you were just loading all data to the client and then applying filter on the client side.
So, the problem with your code is, that Func cant be translated into Sql.
Either fetch all data into app explicitly and filter then or use second version of you code.
Context.Work.ToList()
.Where( w => w.Type.IsHighValueWork());
But, I don't recommend to use that version. It is better to use second version like so:
Func<Work, bool> IsHighValueWork = (work) =>
work.Type == WorkType.Type1 || work.Type == WorkType.Type2;
And then:
var query = Context.Work.Where(IsHighValueWork);
I've been looking for a good solution to speed up my queries in my application. I came across this link:
https://github.com/MikaelEliasson/EntityFramework.Utilities
Where in example they show how their code works:
var result = db.Contacts
.IncludeEFU(db, c => c.PhoneNumbers)
.ToList();
So basically I pass two parameters into includefu, first is the object of the context I'm using, and then using lambda expression select the child record in the same class...
My structure looks like this:
Class Items
{
//some item data properties
List<Transactions> _transactions {get;set;}
}
And my query looks like this:
var list = ctx.Items.IncludeEfu(ctx,c=>c._transactions).ToList();
But when I use my lambda expression on 2nd parameter I get only these methods:
Equals
GetHashCode
ToString
GetType
What am I doing wrong here, has anyone else worked with EF Utilities before?
I'm not sure, if I understand the question correctly, but I think you are just missing some public in your structure.
If so, this might help:
public class Items
{
//some item data properties
public List<Transactions> _transactions {get;set;}
}
I want to execute this query :
public List<ViewSheet> ShowSheet(List<Sheet> lst)
{
var res = (from sheet in _sheetRepository.Get()
join line in _lineRepository.Get() on sheet.LineId equals line.Id
join basemat in _baseMaterialRepository.Get() on sheet.BaseMaterialId equals basemat.Id
join lineend in _lineEndRepository.Get() on sheet.LineEndId equals lineend.Id
join Paint in _paintCodeRepository.Get() on sheet.PaintCodeId equals Paint.Id
select new ViewSheet()
{
BaseMaterialId = basemat.Name,
Catagory = sheet.Catagory,
LineEndId = lineend.Name,
LineId = line.LineNumber,
MtPercent = sheet.MtPercent,
PAndId = sheet.PAndId,
PaintCodeId = Paint.Name,
ParentId = sheet.ParentId,
}).ToList();
return res;
}
as you can see i create a join between 4 tables and the get function has this structure :
public interface ISheetRepository
{
IQueryable<Sheet> Get();
bool Save();
}
But i get this error :
The specified LINQ expression contains references to queries that are
associated with different contexts.
You can't combine in one query data retrieved from multiple contexts, these multiple contexts may point to two difference databases or have different options, then the query might not execute.
It seems that each repository creates its own DataContext object and retrieves the data using that object.
To solve that without have to pass the context and share it between all repositories, you might want to return a List from the Get() method and not an IQueryable. I don't know if you think that Get() is executing the query but it is not. It is just storing information about the query in IQueryable. Only when you call .ToList(); inside your ShowSheet method, the queries of Get() gets executed.
Else, you need to create one context and use it in all the repositories in this query. Your option here is to have the constructor of the repository (or the Get() Method itself) accept a YourContext parameter. Then you pass the context : _sheetRepository.Get(context) so that all of them share the same object.
I saw this code work with LINQ to SQL but when I use Entity Framework, it throws this error:
LINQ to Entities does not recognize the method 'System.Linq.IQueryable'1[MyProject.Models.CommunityFeatures] GetCommunityFeatures()' method, and this method cannot be translated into a store expression.`
The repository code is this:
public IQueryable<Models.Estate> GetEstates()
{
return from e in entity.Estates
let AllCommFeat = GetCommunityFeatures()
let AllHomeFeat = GetHomeFeatures()
select new Models.Estate
{
EstateId = e.EstateId,
AllHomeFeatures = new LazyList<HomeFeatures>(AllHomeFeat),
AllCommunityFeatures = new LazyList<CommunityFeatures>(AllCommFeat)
};
}
public IQueryable<Models.CommunityFeatures> GetCommunityFeatures()
{
return from f in entity.CommunityFeatures
select new CommunityFeatures
{
Name = f.CommunityFeature1,
CommunityFeatureId = f.CommunityFeatureId
};
}
public IQueryable<Models.HomeFeatures> GetHomeFeatures()
{
return from f in entity.HomeFeatures
select new HomeFeatures()
{
Name = f.HomeFeature1,
HomeFeatureId = f.HomeFeatureId
};
}
LazyList is a List that extends the power of IQueryable.
Could someone explain why this error occurs?
Reason:
By design, LINQ to Entities requires the whole LINQ query expression to be translated to a server query. Only a few uncorrelated subexpressions (expressions in the query that do not depend on the results from the server) are evaluated on the client before the query is translated. Arbitrary method invocations that do not have a known translation, like GetHomeFeatures() in this case, are not supported.
To be more specific, LINQ to Entities only support Parameterless constructors and Initializers.
Solution:
Therefore, to get over this exception you need to merge your sub query into the main one for GetCommunityFeatures() and GetHomeFeatures() instead of directly invoking methods from within the LINQ query. Also, there is an issue on the lines that you were trying to instantiate a new instance of LazyList using its parameterized constructors, just as you might have been doing in LINQ to SQL. For that the solution would be to switch to client evaluation of LINQ queries (LINQ to Objects). This will require you to invoke the AsEnumerable method for your LINQ to Entities queries prior to calling the LazyList constructor.
Something like this should work:
public IQueryable<Models.Estate> GetEstates()
{
return from e in entity.Estates.AsEnumerable()
let AllCommFeat = from f in entity.CommunityFeatures
select new CommunityFeatures {
Name = f.CommunityFeature1,
CommunityFeatureId = f.CommunityFeatureId
},
let AllHomeFeat = from f in entity.HomeFeatures
select new HomeFeatures() {
Name = f.HomeFeature1,
HomeFeatureId = f.HomeFeatureId
},
select new Models.Estate {
EstateId = e.EstateId,
AllHomeFeatures = new LazyList<HomeFeatures>(AllHomeFeat),
AllCommunityFeatures = new LazyList<CommunityFeatures>(AllCommFeat)
};
}
More Info: Please take a look at LINQ to Entities, what is not supported? for more info.
Also check out LINQ to Entities, Workarounds on what is not supported for a detailed discussion on the possible solutions.
(Both links are the cached versions because the original website is down)
i asked this a few weeks ago, but couldnt get any of the suggested answers working, so i would be grateful for any help on this:
i have a list of event Ids returned from an xml document as shown below
public IEnumerable<EventFeed> GetEventIdsByEventDate(DateTime eventDate)
{
return (from feed in xmlDoc.Descendants("Show")
from ev in feed.Elements("Event")
where Convert.ToDateTime(ev.Attribute("Date").Value).ToShortDateString() == eventDate.ToShortDateString()
select new EventFeed()
{
EventShowCode = feed.Attribute("Code").Value
}).ToList();
}
i now need to query my database to match events that equal the eventIds returned from the above method. so i would have something like:
select * from eventsdb where eventId in GetEventIdsByEventDate()
how can i do this using LINQ
thanks
kb
Hi Prutswonder, ive created the method below based on your suggestion
public IEnumerable<EventFeed> foo(DateTime str)
{
var foo = from f in GetAllEventsFromDatabase().ToList()
where GetAllEventsByDate(str).Contains(f.EventShowCode)
select e;
return (IEnumerable<EventFeed>) foo;
}
but on compile i get the following error
Error 7 The type arguments for method 'System.Linq.Enumerable.Contains<TSource>(System.Collections.Generic.IEnumerable<TSource>, TSource)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
GetAllEventsFromDatabase:
public IEnumerable<EventFeed> GetAllEventsFromDatabase()
{
var allEvents = from eventsList in GetEventsList()
select new EventFeed()
{
EventName = eventsList.Title,
EventSummary = eventsList.Introduction,
EventShowCode = eventsList.EventId,
EventImageSmall = eventsList.EventImageThumbUrl,
EventUrl = eventsList.Url,
EventSortBy = eventsList.SortOrder
};
return allEvents.OrderBy(x => x.EventSortBy);
}
The GetEventIdsByEventDate() method should return an IEnumerable of strings, containing the Event Ids (like the method name implies):
public IEnumerable<string> GetEventIdsByEventDate(DateTime eventDate)
{
return (from feed in xmlDoc.Descendants("Show")
from ev in feed.Elements("Event")
where Convert.ToDateTime(ev.Attribute("Date").Value).ToShortDateString() == eventDate.ToShortDateString()
select feed.Attribute("Code").Value
).ToList();
}
Also, don't forget to rename the foo() method to a more suitable name (for example GetEventsByEventDate())
About your error:
GetAllEventsByDate returns an IEnumerable containing EventFeed objects, so when you use the "Contains" method, it expects an "EventFeed" object to compare to the objects in the list. Instead, you are passing it an f.EventShowCode, which I assume is an integer or something:
EventShowCode = eventsList.EventId
I believe what you're looking for is this:
public IEnumerable<EventFeed> foo(DateTime str)
{
var foo = from f in GetAllEventsFromDatabase()
where GetAllEventsByDate(str).Contains(f)
select f;
return foo;
}
Download LINQPad. It's free but the upgraded version provides Intellisense support. This app has helped me figure out some pretty complicated LINQ queries.