I have a model class with a custom method within:
public class Category {
public int Id { get; set; }
public string Name { get; set; }
public CategoryListViewModel ToListViewModel() {
return new CategoryListViewModel {
Id = this.Id,
Name = this.Name
};
}
Nice. In my repository layer, I'm trying to do an async query by using System.Data.Entity available methods, such as ToListAsync(). However, when I try to call the following piece of code...
Context.Categories.Select(c => c.ToListViewModel()).ToListAsync();
The compiler complains with the message: LINQ to Entities does not recognize "ToListViewModel()" method [...].
I could do the following to make the query support the method...
Context.Categories.AsEnumerable().Select(c => c.ToListViewModel())
... although I cannot call ToListAsync(), since IEnumerable does not implement this method.
How to keep my query async and calling that method above mentioned? Any thoughts?
Thank you all!
If all else fails then do the projection inline
Context.Categories.Select(c => new CategoryListViewModel {
Id = c.Id,
Name = c.Name
}).ToListAsync();
So that it can be generated properly as linq to entities has to be able to convert the query to an actual SQL statement.
Related
I have several Stored Procedures which I use to search the database. Each returns several fields to do with the amount of data found/returned. The models look like this:
// simplified
public abstract class SearchResult {
public int RowCount { get; set; }
public int FilteredRowCount { get; set; }
// other properties
}
public class FooSearch {
public string Id { get; set; }
public string Foo { get; set; }
public SearchResult Result { get; set; }
}
Then in the DBContext I'm using OwnsOne() to link the classes, as per this MS guide
public DbSet<FooSearch> FooSearch { get; set; }
// OnModelCreating
builder.Entity<FooSearch>().OwnsOne(t => t.Result);
Finally I make the SP call:
var searchResult = db.FooSearch.FromSqlRaw("EXECUTE [Search].[Foo] {0}, {1}", foo, bar).ToList();
However this last step is giving me the following error:
System.InvalidOperationException: 'FromSqlRaw or FromSqlInterpolated was called with non-composable SQL and with a query composing over it. Consider calling AsEnumerable after the FromSqlRaw or FromSqlInterpolated method to perform the composition on the client side.'
Changing ToList() to AsEnumerable() makes no difference to the result.
If I remove the OwnsOne() and the Result property from the FooSearch class then the SP works and I get results. What is the cause of this issue, and how can I fix it?
I faced the same issue and fixed it after reading the issue #18232.
You need to add .IgnoreQueryFilters() after FromSqlRaw. Like that:
var searchResult = db.FooSearch
.FromSqlRaw("EXECUTE [Search].[Foo] {0}, {1}", foo, bar)
.IgnoreQueryFilters()
.ToList();
First Of all, you need to inform the model builder that your entity is a Keyless
entity
modelBuilder.Entity<FooSearch>().HasNoKey().ToView(null)
when using a keyless entity or executing RawSQL, EF will map all of the
properties mentioned in the query to your "FooSearch", how would ef map to
SearchResult object?
remove the SearchResult and replace it with fields you need to select.
I have a model with a linked list of foreign keys i.e.
[Table("a"]
public class A {
[Key]
[Column("a_id")]
public int Id { get; set; }
public List<B> Bs { get; set; } = new List<B>();
}
[Table("b"]
public class B {
[Key]
[Column("b_id")]
public int Id { get; set; }
[NotMapped]
public string MyFunctionValue { get; set; }
[ForeignKey("a_id")]
public A A { get; set; }
}
I've then defined a function which links to a scalar sql function like so...
public static class MySqlFunctions {
[DbFunction("MyFunction", "dbo")]
public static string MyFunction(int bId) {
throw new NotImplementedException();
}
}
and registered in my context like so...
modelBuilder.HasDbFunction(() => MySqlFunctions.MyFunction(default));
What I want to be able to do in my repository class is to grab the A records with the linked B records in a List with their MyFunctionValue value set to the return value of the function when ran against the id of B. Something like...
myContext.A
.Include(a => a.Bs.Select(b => new B {
Id = b.Id,
MyFunctionValue = MySqlFunctions.MyFunction(b.Id)
});
However with all the options I've tried so far I'm getting either a InvalidOperationException or NotImplementedException I guess because it can't properly convert it to SQL?
Is there any way I can write a query like this or is it too complex for EF to generate SQL for? I know there's a possibility I could use .FromSql but I'd rather avoid it if possible as it's a bit messy.
EDIT:
So I've managed to get it working with the following code but it's obviously a bit messy, if anyone has a better solution I'd be grateful.
myContext.A
.Include(a => a.Bs)
.Select(a => new {
A = a,
MyFunctionValues = a.Bs.Select(b => MySqlFunctions.MyFunction(b.Id))
})
.AsEnumerable()
.Select(aWithMfvs => {
for (int i = 0; i < aWithMfvs.MyFunctionValues.Count(); i++) {
aWithMfvs.A.Bs[i].MyFunctionValue = aWithMfvs.MyFunctionValues[i];
}
return aWithMfvs.A;
})
.AsQueryable();
There are several things you should consider with db functions:
When you declare a DbFunction as static method, you don't have to register it with the modelBuilder
Registering is only needed, when you would use Fluent API (which IMHO I recommend anyway in order to have you entities free of any dependencies)
The return value, the method name and the count, type and order of the method parameters must match your code in the user defined function (UDF)
You named the method parameter as bId. Is it exactly the same in your UDF or rather as in the table like b_id?
I'm attempting to build an adapter to make using JQuery Datables with Entity Framework considerably simpler. The adapter is very similar to the one built by Telerik in their Kendo UI Extensions. I've managed to get most of the logic working, but the final piece that's giving me some trouble is getting the dynamically generated Linq to work.
I've looked into both LinqKit and Dynamic Expressions, and I'm a bit torn on how to approach this. I'm currently using the Dynamic Linq extension with limited success. It appears to work fine with Varchar and Int fields, but it stumbles with dates.
I'm using it like so:
public class Search
{
[DataMember(Name = "value")]
public string Value { get; set; }
[DataMember(Name = "regex")]
public string Regex { get; set; }
public string ToExpression(IList<Column> columns)
{
var list = columns.Select(column => $"{column.Data}.Contains.(#{columns.IndexOf(column)})").ToList();
return string.Join("or", list);
}
}
public class FilterExpression
{
public string Filter { get; }
public IEnumerable<object> Values { get; }
public FilterExpression(IEnumerable<Column> columns, Search search)
{
if (search.Value == null) return;
var list = columns.Where(n => n.Searchable).ToList();
Filter = ToExpression(list);
Values = list.Select(n => search.Value);
}
private static string ToExpression(IEnumerable<Column> columns)
{
var colList = columns.Where(n => n.Searchable).ToList();
var list = colList.Select(column => $"{column.Data}.ToString().Contains(#{colList.IndexOf(column)})").ToList();
return string.Join(" or ", list);
}
}
public static IQueryable Where(this IQueryable source, FilterExpression filterExpression)
{
return filterExpression?.Values == null ? source : source.Where(filterExpression.Filter, filterExpression.Values.ToArray());
}
The above works for most cases, but again Dates are a bit of a problem. The goal is to prevent developers from having to manually write their Linq Where statements and instead allow the adapter to simply generate it.
Again, the above works for both INT fields and VARCHAR, but if I include a DATE field in the model, the error I get is: System.NotSupportedException: 'LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.'
https://www.c-sharpcorner.com/blogs/exception-linq-to-entities-does-not-recognize-the-method-tostring
Check this one out.
Bascially when you work with linq to entites, it creats a query which is fired on sql server. ToString() works with in memory objects.
If the object was in memory ,it would have worked just fine. Please go through the link.
I have a code-first EF database where objects have "statuses" to track history. They're implemented something like this:
public class Example
{
public Example()
{
this.Statuses = new HashSet<Status>();
}
public Guid Id { get; set; }
public virtual ICollection<Status> Statuses { get; set; }
}
public class Status
{
public Guid Id { get; set; }
public DateTimeOffset SetOn { get; set; }
public string SetBy { get; set; }
}
We have a few instances in code where we need to get either the oldest or newest status. Currently we've been using chained linq expressions like the following:
var setBy = example.Statuses.OrderByDescending(s => s.SetOn).FirstOrDefault().SetBy;
I think it would be more readable if we could do some of that with extensions, since getting the newest or oldest status is just a difference of whether it's sorted by ascending or descending.
A simple extension method like this works with linq-to-objects, if we've already gotten results from the database:
public static Status Newest(this IQueryable<Status> items)
{
return items.OrderByDescending(s => s.SetOn).FirstOrDefault();
}
However, this doesn't work if I'm running it on an IQueryable representing our database, since EF is unable to translate it to a store expression. For instance, if "repository" below is an IQueryable<Example> representing Examples in a SQL backend, the following will fail:
var date = DateTimeOffset.Parse("4/1/2018");
var query = repository.Where(ex => ex.Statuses.Newest().SetOn > date).FirstOrDefault();
Is there a way I can refactor this into an extension method or expression that can be translated to a store expression?
This can be done with LINQKit by defining an expression that returns a Status from an Example, and wrapping the IQueryable with LINQKit's Expandable. Using the above classes, I could do something like
private Expression<Func<Example, Status>> Newest =
e => e.Statuses.OrderByDescending(s => s.SetOn).FirstOrDefault();
And invoke it like
var results = from example in repository.AsExpandable()
select new
{
Example = example,
LatestStatus = Newest.Invoke(example)
};
I'm trying to use dynamic linq to obtain a subset of people from a database using Entity
Framework (EF). I'm running into a problem when using the contains operation. Here is the entity
for the People table:
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
Here is a query that works successfully.
var people = personContext
.People
.OrderBy("id asc")
.Skip(0)
.Take(5)
.ToList();
Notice that I'm using dynamic linq in the OrderBy method. However, when I try to apply
filtering, I get an exception.
var people = personContext
.People
.Where("id.Contains(15)")
.OrderBy("id asc")
.Skip(0)
.Take(5)
.ToList();
What I'd like to get back is a subset of people with ids that contain the substring "15", such as:
"015", "115", "151", "152", etc.
When I execute the code, I get the following error.
System.Linq.Dynamic.ParseException was unhandled by user code
Message=No applicable method 'Contains' exists in type 'String'
What is the syntax for determining if the Id field contains the string "15"?
What is the syntax for determining if the Id field contains the string "15"?
Well, definitely not .Where("id.Contains(15)") which is trying to invoke the method Contains with numeric value 15.
According to the documentation, you can use either a string literal:
.Where("id.Contains(\"15\")")
or substitution values:
.Where("id.Contains(#0)", "15")
I feel misconception here... You are not supposed to use LINQ like this.
As a start you need to invoke the overloads that accept lambdas; then you specify the property in the lambda and if its a string you invoke Contains on it. Like so:
var people = personContext
.People
.Where(p => p.Id.Contains("15"))
.OrderByDescending(p => p.Id)
.Skip(0) // You don't need this line.
.Take(5)
.ToList();
The EF itself will do the heavy lifting and translate these pure C# codes into the correct SQL statements.
You can't use Contains in the LINQ query. Instead you can try this
var people = (from p in personContext.Set<People>()
where p.Id.Contains("15")
orderby p.Id
select p).Skip(0).Take(5).ToList();