How can I use IEnumerable.Where().Count() in LINQ query? - asp.net-mvc

In Enitity Framework I'd like to use a linq query in the where clause of another linq query.
public class A {
}
public class B {
public A A { get; set; }
}
public class AB {
public A A { get; set; }
public B B { get; set; }
}
IEnumerable<MassiveObject> filteredData = ... // MassiveObject contains A and some more attributes
var query = from b in db.Bs
.Include("A")
where filteredData.Where(x => x.A.equals(b.A)).Count() > 0 // filteredAs contains an object that contains the same A as the b's A
select new AB {
A = filteredData.Where(a => a.equals(b.A)),
B = b
};
It looks like LINQ doesn't support that. "Unable to create a constant value of type 'MassiveObject'. Only primitive types or enumeration types are supported in this context." Neither the first nor the second use of the nested linq.
Is there another way to this without looping through the query's results after the query was executed?

filteredData is a collection that exists inside the memory of your process. On the other hand, the EF query is translated into SQL that runs on the SQL server.
The SQL server cannot know what data exists in the memory of your process, and even if it could see that memory SQL still has no concept of object instances or iteration. So what you are trying to do is fundamentally impossible.

Linq does not support using an enumeration of an object in a SQL construct because there is no way to make that a SQL stement to start with.
Pull the b's and then filter in that enumeration.

You can't mix in-memory and SQL LINQ queries like this. Instead you need to pull the SQL data into memory before doing the join.
Try something like this instead:
var query =
from b in db.Bs.Include("A").ToArray()
join a in filteredData.Select(x => x.A) on b.A equals a into gas
where gas.Any() // or use gas.Count() > 0
select new AB
{
A = gas.First(),
B = b,
};
Note the .ToArray() call that brings the SQL data into memory.

It isn't very clear what you are trying to achieve, however, previous queries can be used in the current query in various ways e.g.
query2 = source.Where(s => query1.Contains(s));
or using LINQ joins.
In your case itlooks like youare trying to reuse a filter,not a query.
You can achieve this using an extension method from IQueryable<T> to IQueryable<T> or by using expressions.
e.g.
static class MyExtensions
{
public static void IQueryable<T> FilterResults(this IQueryable<T> query)
{
return query.Where(i => i....);
}
}
and then use like so:
query2 = query.FilterResults().Where(...);
For details on the alternative of using Expression Trees see this MSDN article.

Related

How pass a LINQ expression to a function that returns a IQueryable

I have a few methods that act on a query one that is for a simple where filter an the other with an expression to be used in the where filter and the third also has an expression as a parameter.
public static IQueryable<Employee> FilterExpression(this IQueryable<Employee> employees,
Expression<Func<Employee, Boolean>> expression)
{
return employees.Where(expression);
}
public static IQueryable<Employee> Active(this IQueryable<Employee> employees)
{
return employees.Where(e => e.IsActive);
}
The first two execute fine without any problems.
var active = db.Employees.Active().Take(5).ToList();
var activeExpression = db.Employees.Take(5).FilterExpression((e) => e.IsActive).ToList();
The next one is being called in another extension method that returns a delegate for a select on a tasks collection.
public static Expression<Func<SpwTask, TaskView>> Tasks(this TaskService service)
{
return (x) => new TaskView()
{
NotifyList = db.TaskContacts.JoinedEmployees
(t => t.TaskId == x.Id).Select(Projections.SimpleEmployees)
};
}
public static IQueryable<Employee> JoinedEmployees(this IQueryable<TaskContact> contacts,
Expression<Func<TaskContact, Boolean>> expression)
{
var id = ServicesRoot.Company.Id;
return from c in contacts.Where(expression)
join e in db.Employees on c.EmployeeId equals e.Id
where e.CompanyId == id
select e;
}
The calling code looks like this
// db is the DbContext and the Tasks is the extension method
...
return db.Tasks.Select(this.Tasks());
...
The error I get is this:
System.NotSupportedException: 'LINQ to Entities does not recognize the method 'System.Linq.IQueryable1[SafetyPlus.Data.Models.Employee] JoinedEmployees(System.Linq.IQueryable1[SafetyPlus.Data.TaskContact], System.Linq.Expressions.Expression1[System.Func2[SafetyPlus.Data.TaskContact,System.Boolean]])' method, and this method cannot be translated into a store expression.'
Are there any ways to work around this problem? It would be really nice to reused this an other similar queries. It seems like this should work.
Inline version:
public static Expression<Func<SpwTask, TaskView>> Tasks(this TaskService service)
{
return (x) => new TaskView()
{
NotifyList =
(from c in service.db.TaskContacts.
where c.TaskId == x.Id
join e in db.Employees on c.EmployeeId equals e.Id
where e.CompanyId == id
select e).Select(Projections.SimpleEmployees)
};
}
When I run the code inline version of the query I get a single SQL produced which is optimized and what I want but is not reusable.
I had thought the Compile() solution given below was the answer that I needed, at first, but upon looking at the SQL Profiler I realize that each row of the outer task query is running a single query for each task instead of the single query for the whole dataset. This pretty much defeats the purpose of reusing the query.
You should pass it as a delegate without invoking it in the Select and compile before use.
Try this:
db.Tasks.Select(Tasks.Compile());

How to use COLLATE Latin1_General_bin in Entity framework?

I have to use COLLATE in entity framework query. How to write SQL query equivalent in Entity Framework as show below code?
SQL query:
select * from AspNetUsers order by Email COLLATE Latin1_General_bin
Entity Framework:
using (var db = new testEntities())
{
var appUsers = await db.Users.OrderBy(x => x.Email).ToListAsync();
}
It's possible to use Entity Framework's interception hooks.
The first step it to define an interface:
interface ISortInterceptable
{
IEnumerable<string> AdaptableSortFieldNames { get; set; }
}
Then make your context implement it:
class TestEntities : DbContext, ISortInterceptable
{
...
public IEnumerable<string> AdaptableSortFieldNames { get; set; }
...
}
Next, create a command interceptor:
class SortCommandInterceptor : DbCommandInterceptor
{
public override void ReaderExecuting(DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
if (interceptionContext.DbContexts.First() is ISortInterceptable interceptable
&& interceptable.AdaptableSortFieldNames != null)
{
var query = command.CommandText;
foreach (var fieldName in interceptable.AdaptableSortFieldNames)
{
var pattern = $#"(.*\s*ORDER BY\s*.*\.)(\[{fieldName}\])(.*)";
query = Regex.Replace(query, pattern, "$1$2 COLLATE Latin1_General_bin $3");
}
command.CommandText = query;
}
base.ReaderExecuting(command, interceptionContext);
}
}
This is where all the magic happens.
The interceptor first checks if it has to do with a ISortInterceptable (maybe this check can be refined by getting all ISortInterceptables from interceptionContext.DbContexts).
The command text in the command to be executed is analyzed on any occurence of strings like ORDER BY [Alias].[fieldName] where fieldName is a variable. This search pattern is in keeping with the pattern EF always follows to generates queries.
The field name part of the ORDER BY clause, which is in the third group ($2) of the regex match, is extended by the collation phrase.
The replacement is repeated for all field names.
Finally, an example of how to use this interceptor:
DbInterception.Add(new SortCommandInterceptor());
using (var db = new TestEntities())
{
db.AdaptableSortFieldNames = new[] { "LastName", "Email" };
var users = db.AspNetUsers
.OrderBy(u => u.LastName)
.ThenBy(u => U.Email)
.ToList();
}
As always with string manipulation, one caveat: this works in a couple of my own tests, but I can't guarantee it to be rock solid. For one, the sorting fields should be text fields, of course.
One last note. EF core 3 also offers interception hooks that can be used in a similar way.

How to improve my Entity Framework , to join several database queries into single query

I have the following ActionFilter class, to implement my custom authorization system:-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : ActionFilterAttribute
{
Repository repository = new Repository();
public string Model { get; set; }
public string Action { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string ADusername = filterContext.HttpContext.User.Identity.Name.Substring(filterContext.HttpContext.User.Identity.Name.IndexOf("\\") + 1);
if (!repository.can(ADusername,Model,Action))
{
filterContext.Result = new HttpUnauthorizedResult("You cannot access this page");
}
base.OnActionExecuting(filterContext);
}
}
The above class will call the following repository method:-
public bool can(string user, string Model, string Action)
{
bool result;
bool result2;
int size =tms.PermisionLevels.Where(a5 => a5.Name == Action).SingleOrDefault().PermisionSize;
var securityrole = tms.SecurityroleTypePermisions.Where(a => a.PermisionLevel.PermisionSize >= size && a.TechnologyType.Name == Model).Select(a => a.SecurityRole).Include(w=>w.Groups).Include(w2=>w2.SecurityRoleUsers).ToList();
foreach (var item in securityrole)
{
result = item.SecurityRoleUsers.Any(a => a.UserName.ToLower() == user.ToLower());
var no = item.Groups.Select(a=>a.TMSUserGroups.Where(a2=>a2.UserName.ToLower() == user.ToLower()));
result2 = no.Count() == 1;
if (result || result2) {
return true;
}}
return false;
}
But inside my repository method , I am doing the following:-
Query the database and include all the Groups & SecurityRoleUsers when executing the .tolist()
Then filter the returned records insdie the server, based on the foreach loop.
But this will cause the following drawbacks:-
If I have many Groups and SecurityRoleUsers, then I will be getting them all from the DB, and then filter the result on the server.
And since this code will be executed whenever an action method is called, as it Is a security attribute at the begging of the controller class. So this might not be very efficient.
So my question is whether I can join all the queries inside the repository method to be single query , and do all the work on the Database and just return true or false to the server ?
The associated tables looks as follow:-
Ideally remove this foreach.
Try riding with Linq to Sql.
You should be more comfortable because it resembles SQL.
This link has several examples.
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
Att
Julio Spader
wessolucoes.com.br
Use linq.
Ideally you should only have one line of code after you got the size value. e.g.
int size =tms.PermisionLevels.Where(a5 => a5.Name == Action).SingleOrDefault().PermisionSize;
var result = //One line of code to determine user authenticity
return result;
I think you should design you database in the way that join queries are easy to do. So you don't have to perform more than one select.
Try code-first EF, which links tables very easily.
You need to take care with Lazy Loading. If not used correctly, it will make a query to the database each object segmentation, especially in your foreach. With that already has a good improvement.
Take a look at this article. I think it will help you too.
http://www.sql-server-performance.com/2012/entity-framework-performance-optimization/
Att
Julio Spader
wessolucoes.com.br

How to solve "Specified cast is not valid" error when using linq query with multiple joins?

I'm having an issue where I'm trying to construct a Linq query in my repository that contains multiple joins from different tables in a database and then return this as an object for my controller to display. The error that I'm getting is: "Specified cast is not valid."
The problem part seems to be when I try to pass the object from my query and I'm not really sure how to solve this. (I'm still relatively new to web development and trying to learn the basics).
Below is the code in my repository:
public ListingModel GetListing(int listingId)
{
var query = from listing in listingsTable
where listing.ListingID == listingId
join feature
in featuresTable on listing.ListingID equals feature.ListingID into features
from f in features.DefaultIfEmpty()
join avail
in availabilityTable on listing.ListingID equals avail.ListingID into availability
from a in availability.DefaultIfEmpty()
join image
in imageTable on listing.ListingID equals image.ListingID into images
from i in images.DefaultIfEmpty()
select new ListingModel
{
Listing = listing,
Features = features,
Availability = availability,
Images = images
};
return query.FirstOrDefault();
}
If it's needed, here's the ListingModel class:
public class ListingModel
{
public Listing Listing { get; set; }
public IEnumerable<Feature> Features { get; set; }
public IEnumerable<Availability> Availability { get; set; }
public IEnumerable<Image> Images { get; set; }
}
When I try the query in LinqPad it works fine and returns the data exactly how I'd want it, so it seems to be erroring when I try to pass the object back. I've tried a few things with this and every time I seem to be getting the same error; I'm having real difficulty pinpointing what's causing it. Thanks in advance for any help given.
Try selecting an anonymous object in your query and then using it to instantiate a ListingModel in memory. The end of your method will look like this:
select new
{
Listing = listing,
Features = features,
Availability = availability,
Images = images
};
return query.AsEnumerable()
.Select(x => new ListingModel
{
Listing = x.Listing,
Features = x.Features,
Availability = x.Availability,
Images = x.Images
})
.FirstOrDefault();
But can't you just take advantage of Foreign Key relationships and have LINQ to SQL join the associated tables for you? Also, to avoid creating duplicate ListingModels you should remove the lines containing DefaultIfEmpty().

Json and Circular Reference Exception

I have an object which has a circular reference to another object. Given the relationship between these objects this is the right design.
To Illustrate
Machine => Customer => Machine
As is expected I run into an issue when I try to use Json to serialize a machine or customer object. What I am unsure of is how to resolve this issue as I don't want to break the relationship between the Machine and Customer objects. What are the options for resolving this issue?
Edit
Presently I am using Json method provided by the Controller base class. So the serialization I am doing is as basic as:
Json(machineForm);
Update:
Do not try to use NonSerializedAttribute, as the JavaScriptSerializer apparently ignores it.
Instead, use the ScriptIgnoreAttribute in System.Web.Script.Serialization.
public class Machine
{
public string Customer { get; set; }
// Other members
// ...
}
public class Customer
{
[ScriptIgnore]
public Machine Machine { get; set; } // Parent reference?
// Other members
// ...
}
This way, when you toss a Machine into the Json method, it will traverse the relationship from Machine to Customer but will not try to go back from Customer to Machine.
The relationship is still there for your code to do as it pleases with, but the JavaScriptSerializer (used by the Json method) will ignore it.
I'm answering this despite its age because it is the 3rd result (currently) from Google for "json.encode circular reference" and although I don't agree with the answers (completely) above, in that using the ScriptIgnoreAttribute assumes that you won't anywhere in your code want to traverse the relationship in the other direction for some JSON. I don't believe in locking down your model because of one use case.
It did inspire me to use this simple solution.
Since you're working in a View in MVC, you have the Model and you want to simply assign the Model to the ViewData.Model within your controller, go ahead and use a LINQ query within your View to flatten the data nicely removing the offending circular reference for the particular JSON you want like this:
var jsonMachines = from m in machineForm
select new { m.X, m.Y, // other Machine properties you desire
Customer = new { m.Customer.Id, m.Customer.Name, // other Customer properties you desire
}};
return Json(jsonMachines);
Or if the Machine -> Customer relationship is 1..* -> * then try:
var jsonMachines = from m in machineForm
select new { m.X, m.Y, // other machine properties you desire
Customers = new List<Customer>(
(from c in m.Customers
select new Customer()
{
Id = c.Id,
Name = c.Name,
// Other Customer properties you desire
}).Cast<Customer>())
};
return Json(jsonMachines);
Based on txl's answer you have to
disable lazy loading and proxy creation and you can use the normal methods to get your data.
Example:
//Retrieve Items with Json:
public JsonResult Search(string id = "")
{
db.Configuration.LazyLoadingEnabled = false;
db.Configuration.ProxyCreationEnabled = false;
var res = db.Table.Where(a => a.Name.Contains(id)).Take(8);
return Json(res, JsonRequestBehavior.AllowGet);
}
Use to have the same problem. I have created a simple extension method, that "flattens" L2E objects into an IDictionary. An IDictionary is serialized correctly by the JavaScriptSerializer. The resulting Json is the same as directly serializing the object.
Since I limit the level of serialization, circular references are avoided. It also will not include 1->n linked tables (Entitysets).
private static IDictionary<string, object> JsonFlatten(object data, int maxLevel, int currLevel) {
var result = new Dictionary<string, object>();
var myType = data.GetType();
var myAssembly = myType.Assembly;
var props = myType.GetProperties();
foreach (var prop in props) {
// Remove EntityKey etc.
if (prop.Name.StartsWith("Entity")) {
continue;
}
if (prop.Name.EndsWith("Reference")) {
continue;
}
// Do not include lookups to linked tables
Type typeOfProp = prop.PropertyType;
if (typeOfProp.Name.StartsWith("EntityCollection")) {
continue;
}
// If the type is from my assembly == custom type
// include it, but flattened
if (typeOfProp.Assembly == myAssembly) {
if (currLevel < maxLevel) {
result.Add(prop.Name, JsonFlatten(prop.GetValue(data, null), maxLevel, currLevel + 1));
}
} else {
result.Add(prop.Name, prop.GetValue(data, null));
}
}
return result;
}
public static IDictionary<string, object> JsonFlatten(this Controller controller, object data, int maxLevel = 2) {
return JsonFlatten(data, maxLevel, 1);
}
My Action method looks like this:
public JsonResult AsJson(int id) {
var data = Find(id);
var result = this.JsonFlatten(data);
return Json(result, JsonRequestBehavior.AllowGet);
}
In the Entity Framework version 4, there is an option available: ObjectContextOptions.LazyLoadingEnabled
Setting it to false should avoid the 'circular reference' issue. However, you will have to explicitly load the navigation properties that you want to include.
see: http://msdn.microsoft.com/en-us/library/bb896272.aspx
Since, to my knowledge, you cannot serialize object references, but only copies you could try employing a bit of a dirty hack that goes something like this:
Customer should serialize its Machine reference as the machine's id
When you deserialize the json code you can then run a simple function on top of it that transforms those id's into proper references.
You need to decide which is the "root" object. Say the machine is the root, then the customer is a sub-object of machine. When you serialise machine, it will serialise the customer as a sub-object in the JSON, and when the customer is serialised, it will NOT serialise it's back-reference to the machine. When your code deserialises the machine, it will deserialise the machine's customer sub-object and reinstate the back-reference from the customer to the machine.
Most serialisation libraries provide some kind of hook to modify how deserialisation is performed for each class. You'd need to use that hook to modify deserialisation for the machine class to reinstate the backreference in the machine's customer. Exactly what that hook is depends on the JSON library you are using.
I've had the same problem this week as well, and could not use anonymous types because I needed to implement an interface asking for a List<MyType>. After making a diagram showing all relationships with navigability, I found out that MyType had a bidirectional relationship with MyObject which caused this circular reference, since they both saved each other.
After deciding that MyObject did not really need to know MyType, and thereby making it a unidirectional relationship this problem was solved.
What I have done is a bit radical, but I don't need the property, which makes the nasty circular-reference-causing error, so I have set it to null before serializing.
SessionTickets result = GetTicketsSession();
foreach(var r in result.Tickets)
{
r.TicketTypes = null; //those two were creating the problem
r.SelectedTicketType = null;
}
return Json(result);
If you really need your properties, you can create a viewmodel which does not hold circular references, but maybe keeps some Id of the important element, that you could use later for restoring the original value.

Resources