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.
Related
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 am rather new to programming, < 2 years. I am trying to take a flat table that is currently a stored procedure in MS-SQL and turn it into a complex data structure. What I'm trying to accomplish is returning all the changes for the various release versions of a project.
These are the model classes I currently have:
public class ReleaseNote
{
public string ReleaseVersion { get; set; }
public DateTime ReleaseDate { get; set; }
public List<ReleaseNoteItems> ReleaseNoteItems { get; set; }
}
public class ReleaseNoteItems
{
public string ChangeType { get; set; }
public List<string> Changes { get; set; }
}
And this is the business logic in the DAL class I have:
public IEnumerable<ReleaseNote> GetAllReleaseNotes()
{
string cmdText = ConfigurationManager.AppSettings["ReleaseNotesAll"];
Func<DataTable, List<ReleaseNote>> transform = releaseNoteTransform;
return getRecords<ReleaseNote>(cmdText, transform);
}
public List<ReleaseNote> releaseNoteTransform(DataTable data)
{
//DISTINCT LIST OF ALL VERSIONS (PARENT RECORDS)
var versions = data.AsEnumerable().Select(row => new ReleaseNote
{
ReleaseVersion = row["ReleaseVersion"].ToString(),
ReleaseDate = DateTime.Parse(row["ReleaseDate"].ToString())
}).Distinct().ToList();
//ENUMERATE VERSIONS AND BUILD OUT RELEASENOTEITEMS
versions.ForEach(version =>
{
//GET LIST OF ROWS THAT BELONG TO THIS VERSION NUMBER
var rows = data.AsEnumerable().Where(row => row["ReleaseVersion"].ToString() == version.ReleaseVersion).ToList();
//GET DISTINCT LIST OF CHANGE TYPES IN THIS VERSION
var changeTypes = rows.Select(row => row["ChangeType"].ToString()).Distinct().ToList();
//INSTANTIATE LIST FOR RELEASENOTE ITEMS
version.ReleaseNoteItems = new List<ReleaseNoteItems>();
//ENUMERATE CHANGE TYPES AND CREATE THEM
changeTypes.ForEach(changeType =>
{
//FILTER FOR CHANGES FOR THIS SPECIFIC CHANGE TYPE AND PROJECT TO LIST<STRING>
var changes = rows.Where(row => row["ChangeType"].ToString() == changeType)
.Select(row => row["ReleaseNote"].ToString()).ToList();
//CREATE THE ITEM AND POPULATE IT
var releaseNoteDetail = new ReleaseNoteItems();
releaseNoteDetail.ChangeType = changeType;
releaseNoteDetail.Changes = changes;
version.ReleaseNoteItems.Add(releaseNoteDetail);
});
});
return versions;
}
I'm presently using Postman to return a JSON object and the issue I'm presently having is that it is not returning unique objects or release versions, it is still giving me duplicates.
These are some links I've looked at. None I've found provide solutions for the specific implementation I'm using. I've tried different implementations, but it seems they fall outside the framework of what I'm trying to accomplish.
Please let me know if you need more information. I'm trying to follow the question protocol, but I'm sure there is something I've left out.
Thanks in advance!
Nice & universal way to convert List of items to Tree
Is there a way to easily convert a flat DataTable to a nested .NET object?Recursive method turning flat structure to recursive
Sounds like your data has duplicates. A given ReleaseVersion may have more than one record. When you take DISTINCT in your example, you are enforcing uniqueness over {ReleaseVersion, ReleaseDate}, which apparently is not good enough.
If you want to have rows that are unique with respect to ReleaseVersion, you need to figure out how to populate ReleaseDate when there is more than one possible value. I would suggest that it should be populated with the latest release date associated with that version. You can enforce that logic with LINQ GroupBy and Max, like this:
var uniqueRows = dt.AsEnumerable()
.GroupBy(row => row["ReleaseVersion"])
.Select (group => new ReleaseNote
{
ReleaseVersion = group.Key as string,
ReleaseDate = group.Max(row => (DateTime)row["ReleaseDate"])
}
);
This LINQ will create one row per release version. The release date will be populated with the latest (max) release date, given the release version.
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.
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();
I am using Entity Framework and Breeze. For an Entity, there is a bit of associated data I would like to provide with the entity. Getting this data is most efficiently done by querying the Entity table and joining to other tables; this query includes a group by sub-query.
I am attempting to tack this extra data on by adding it as a [NotMapped] field to the entity:
[NotMapped]
public string NotMappedField { get; set; }
So then I basically want to replace this webapi controller method
[HttpGet]
public IQueryable<MyObject> MyObjects()
{
return _contextProvider.Context.MyObjects;
}
With something like this:
public IQueryable<MyObject> MyObjectsWithExtraData()
{
var query = from o in _contextProvider.Context.MyObjects
// big complex query
select new MyObject
{
FieldA = o.FieldA,
FieldB = o.FieldB,
// all fields
NotMappedField = x.ResultFromComplexJoin
}
return query;
}
This gives me an error:
The entity or complex type 'MyObject' cannot be constructed in a LINQ to Entities query.
I've tried this a few ways and it seems to fight me both from the EF side and the Breeze side. I need to keep this as returning something like IQueryable so I can filter from the client through webapi because doing something like a ToList() here causes memory issues due to the dataset size.
So my question is - is there a best practices kind of way to accomplish what I am attempting or can anyone provide a solution?
Update:
I have found you can return extra data alongside of your entity and still have access to the entity as a queryable from Breeze:
public object MyObjectsWithExtraData()
{
var query = from o in _contextProvider.Context.MyObjects
// big complex query....
select new
{
theObject = MyObject,
NotMappedField = x.ResultFromComplexJoin
};
return query;
}
and then from the client breeze side you can do something like this:
var query = breeze.EntityQuery
.from("MyObjectsWithExtraData")
.where("theObject.FieldA", "Equals", 1)
.expand("theObject.SomeNavigationalProperty")
.orderBy("theObject.FieldB");
Still not exactly what I was looking for but it is actually pretty slick.
Take a look at the EntityQuery.withParameters method.
// client side
var q = EntityQuery.from("CustomersStartingWith")
.withParameters({ companyName: "C" });
// server side
[HttpGet]
public IQueryable<Customer> CustomersStartingWith(string companyName) {
var custs = ContextProvider.Context.Customers.Where(c => c.CompanyName.StartsWith(companyName));
return custs;
}
You can also mix and match a combination of regular query predicates with these custom parameters.
LINQ to entity can only construct pur "Data Transfert Object" : class containing only public properties with trivial getter and setter and without constructor.
See my answer to a similar question here : https://stackoverflow.com/a/21174654/3187237
I specify my answer
An Entity class can't be instanciated in a LINQ to Entities query.
If you want to construct similar (or almost similar) in the query you have to define an other class.
In your case you want to return object almost similar to your MyObject. So you have to define a class:
public class MyObjectExtended
{
public string FieldA { get; set; }
public string FieldB { get; set; }
// ... all other MyObjetc fields
public string ExtraFieldA { get; set; }
public string ExtraFieldB { get; set; }
}
Now, your service can return a IQueryable<MyObjectExtended>:
public IQueryable<MyObjectExtended> MyObjectsWithExtraData() {
var myQuery = from o in _contextProvider.Context.MyObjects
// big complex query....
select new MyObjectExtended {
FieldA = o.FieldA,
FieldB = o.FieldB,
//... all fields ...
ExtraFieldA = x.ResultFromComplexJoinA,
ExtraFieldB = x.ResultFromComplexJoinB
};
return myQuery;
}
I hope this is what you are looking for.