Does it matter where AsNoTracking in Entity Framework is called - c#

Does it matter where the AsNoTracking method is called when writing an Entity Framework query? e.g.
var matchingCustomers = context.Customers.AsNoTracking().Where(n => n.city == "Milan").Skip(50).Take(100).OrderBy(n => n.Name).ToList();
var matchingCustomers = context.Customers.Where(n => n.city == "Milan").AsNoTracking().Skip(50).Take(100).OrderBy(n => n.Name).ToList();
var matchingCustomers = context.Customers.Where(n => n.city == "Milan").Skip(50).AsNoTracking().Take(100).OrderBy(n => n.Name).ToList();
var matchingCustomers = context.Customers.Where(n => n.city == "Milan").Skip(50).Take(100).AsNoTracking().OrderBy(n => n.Name).ToList();
var matchingCustomers = context.Customers.Where(n => n.city == "Milan").Skip(50).Take(100).OrderBy(n => n.Name).AsNoTracking().ToList();
var matchingCustomers = context.Customers.Where(n => n.city == "Milan").Skip(50).Take(100).OrderBy(n => n.Name).ToList().AsNoTracking();
I like adding it to the end of statements but before the ToList is called like this:
var matchingCustomers = context.Customers.Where(n => n.city == "Milan").Skip(50).Take(100).OrderBy(n => n.Name).AsNoTracking().ToList();

No it doesn't matter: (source)
A new query with NoTracking applied, or the source query if NoTracking is not supported.
So you either do it in the beginning and you expand the "new" query with the method chain, or you do it in the end and then get the "new" query. As long as you call it before the query is executed you're fine.

As Cdaragorn said in the comments.
You cannot call AsNoTracking after ToList because you no longer have
an IQueryable to call it on. It will give a compile time error.
In the case you could do what OP is asking I am going to explain how the query would work because could help others to understand these matters:
With
var matchingCustomers = context.Customers.Where(n => n.city == "Milan").Skip(50).Take(100).OrderBy(n => n.Name).ToList().AsNoTracking();
you are trying to apply NoTracking to a data structure already in memory once EF has executed, and traked, the query.
When you use this fluent API; you are defining a query without executing it until you, of course, execute the query. ToList() will execute the query an bring the data to memory to transform it into a List<T> data structure.
Let's split the command to understand this:
context.Customers --> Select [*] from Customers
Where(n => n.city == "Milan") --> Select [*] from Customers where city
== 'Milan'
Skip(50).Take(100) --> Select [*] from Customers where city == 'Milan'
OFFSET 50 ROWS FETCH NEXT 100 ROWS ONLY
OrderBy name --> Select [*] from Customers where city == 'Milan'
OFFSET 50 ROWS FETCH NEXT 100 ROWS ONLY ORDER BY name
ToList() --> Execute the query and bring the data into memory with Tracking by default!
AsNoTraking() --> Does nothing because EF already executed the query
and tracked the data.

Related

Using .Where() clause inside an .Include() on a linq query with multiples includes

I would like to get a collection of Customers including several properties among which is the address but only when it has not been deleted yet (SuppressionDate == null)
IQueryable<Customer> customers =
context.Persons.OfType<Customer>()
.Include(customer => customer.Addresses)
.Include(customer => customer.Bills)
.Include(customer => customer.Code)
.Include(customer => customer.Tutors);
I have tried several ways to use the where clause in order to filter address:
...
.Include(customer => customer.Addresses.Where(a => a.SuppressionDate == null))
.Include(customer => customer.Bills)
...
That was my first try but it raises the following exception:
System.ArgumentException : The Include path expression must refer to a
navigation property defined on the type. Use dotted paths for
reference navigation properties and the Select operator for collection
navigation properties. Parameter Name : path
I've also tried with the same where clause at the end of the Include() and at the end of the query but neither seems to work.
I'm currently using a workaround which is iterate through the collection of customer and remove the addresses that are deleted as such:
foreach(Customer c in customers){
customer.Addresses = customer.Addresses.Where(a => a.SuppressionDate == null).ToList();
}
Being fairly new to linq to object/entities, I was wondering if there was a built-in way to achieve this.
If you were getting a single customer you could use explicit loading like this:
var customer = context.Persons
.OfType<Customer>()
.Include(customer => customer.Bills)
.Include(customer => customer.Code)
.Include(customer => customer.Tutors)
.FirstOrDefault(); //or whatever
context.Entry(customer).Collections(x => x.Addresses).Query().Where(x => x.SuppressionDate == null).Load();
That makes a nice query and two simple calls to the database. But in this case you are getting a list (or collection or whatever) of customer and there's no shortcuts. Your "workaround" may cause a lot of chatter with the database.
So you probably just have to take it one step at time:
//1. query db to get customers
var customers = context.Persons
.OfType<Customer>()
.Include(customer => customer.Bills)
.Include(customer => customer.Code)
.Include(customer => customer.Tutors)
.ToList();
//2. make an array of all customerIds (no db interation here)
var customerIds = customers.Select(x => x.CustomerId).ToArray();
//3. query db to get addresses for customers above
var addresses = context.Addresses.Where(x => customerIds.Contains(x.CustomerId).ToList();
//4. assign addresses to customers (again, no db chatter)
foreach (var customer in customers)
{
customer.Addresses = addresses
.Where(x => x.CustomerId == customer.CustomerId && x.SuppressionDate == null)
.ToList();
}
Not too bad- still just two queries to database.

LINQ Query find columns where string contains any letter

I'm trying to find all customer codes where the customer has a status of "A" and whose code does not contain any letter using LINQ query.
var activeCustomers = Customers.Where(x => x.Status == "A" && x.Code.Any(n => !char.IsLetter(n))).Select(x => x.Code);
When I run this query in LinqPad I get the following error:
You'll need to do this as a two part query. First, you could get all the users who's status is "A":
var activeCustomers = Customers.Where(x => x.Status == "A").ToList();
After you've got those in-memory, you can create an additional filter for char.IsDigit:
var codes = activeCustomers.Where(x => x.Code.Any(n => !char.IsLetter(n)))
.Select(x => x.Code)
.ToArray();
As commenters have stated, IsLetter() cannot be translated to SQL. However, you could do the following, which will first retrieve all items with Status "A" from the database, then will apply your criteria after retrieval:
var activeCustomers = Customers.Where(x => x.Status == "A").AsEnumerable().Where(x => x.Code.Any(n => !char.IsLetter(n))).Select(x => x.Code);
You'll have to determine if it's acceptable (from a performance perspective) to retrieve all customers with "A" and then process.
The AsEnumerable() transitions your LINQ query to working not with IQueryable (which works with SQL) but with IEnumerable, which is used for plain LINQ to objects.
Since it is LINQ 2 SQL, there is no natural way to translate char.IsLetter to something SQL can understand. You can hydrate a query that retrieves your potential candidates and then apply an addition in-memory filter. This also solves the issue where LINQ 2 SQL has a preference for a string and you are dealing with chars
var activeCustomers = Customers.Where(x => x.Status == "A").ToList();
var filteredCustomers = activeCustomers.Where(x =>
x.Code.Any(n => !char.IsLetter(n))).Select(x => x.Code).ToList();
There are two performance hits here. First, you're retrieving all potential records, which isn't too desirable. Second, in your above code you were only interested in an enumerable collection of codes, which means our query is including far more data than we originally wanted.
You could tighten up the query by only returning back to columns necessary to apply your filtering:
var activeCustomers = Customers.Where(x => x.Status == "A")
Select(x => new Customer{ Status = x.Status, Code = x.Code }).ToList();
You still return more sets than you need, but your query includes fewer columns.

using where clause after get last record?

using (EPOSEntities1 db = new EPOSEntities1())
{
List<ActionPerformed> PLUlist = db.ActionPerformeds.ToList();
ActionPerformed Latest_PLU = PLUlist.OrderByDescending(x => x.Date).FirstOrDefault();
}
This returns the last record stored. However I have now added another column in the table File_Name, how can I add a where clause to this to say orderByDescending to get the latest file, then from there get the first record with the file_Name as 'Sales'.??
so e.g.
File_Name Date
12) Products 11/02/2014
13) Sales 11/02/2014
14) Products 11/02/2014
this would return record 13??
The Where method can filter your collection to only those items where the File_Name is "Sales".
Consider placing your LINQ query before the executing call so that your LINQ-to-DB provider can perform the query server-side and only return you one item. What you were doing is bringing the entire ActionPeformeds table down from the server to the client, then performing the query client-side.
ActionPerformed Latest_PLU = db.ActionPerformeds
.Where(x => x.File_Name == "Sales")
.OrderByDescending(x => x.Date)
.FirstOrDefault();
By "executing call" I mean ToList(), First(), FirstOrDefault(), etc.
Use Where clause before OrderByDescending, if you call ToList it will cause immediate evaluation of the query and records will populated. It will be better if you call Where before evaluation.
ActionPerformed Latest_PLU = db.ActionPerformeds.Where(c=>File_Name == "Sales")
.OrderByDescending(x => x.Date)
.FirstOrDefault();
ActionPerformed Latest_PLU = PLUlist.Where(p => p.File_Name == 'Sales').OrderByDescending(x => x.Date).FirstOrDefault();

Linq Contains in one query

I have a List and i want to write a query about List's ids Contains specific table id.
i Write this and running true but i want to write all in same query..
List<int> tempList=yetkiUygulamaList.Select(y => y.Id).ToList();
query = query.Where(x => tempList.Contains(x.Uygulama.Id));
Wrong Query
query = query.Where(x => yetkiUygulamaList.Select(y =>y.Id).ToList().Contains(x.Uygulama.Id));
this must works
query = query.Where(x => yetkiUygulamaList.Any(y=>y.Id == x.Uygulama.Id));
you can perform a join, it would be more simple and suitable in your case.
If I understand, query is a "collection" of a class (let's call it AObj) containing a property Uygulama and the class Uygulama contains a property Id and yetkiUygulamaList is a "collection" of Uygulama
//will return a IEnumerable<AObj>
IEnumerable<AObj> query = query.Join(yetkiUygulamaList, a => a.Uygulama.Id, u => u.Id, (a,u)=>a);
ToList() materilizes by executing the query, and after that there is no way for NHibernate to understand that the first query should be included as a subquery.
Just remove the useless ToList():
IQueryable<int> tempList = yetkiUygulamaList.Select(y => y.Id); // removed here
query = query.Where(x => tempList.Contains(x.Uygulama.Id));
The above code will generate a single SQL query. If you want to stick it all in one C# code line, just get rid of the intermediary variable:
query = query.Where(x => yetkiUygulamaList.Select(y => y.Id).Contains(x.Uygulama.Id));

How to query (using LINQ) FormattedValues in Dynamics CRM 2011

I am using LINQ to retrieve Account type entities from Microsoft Dynamics CRM Online. I am unable to filter the list for a particular formatted value. I have the correct value, but am receiving zero records.
I'm creating my connection like this:
var connection = new CrmConnection("CRMOnline");
connection.ProxyTypesEnabled = true;
CrmOrganizationServiceContext _context = new CrmOrganizationServiceContext(connection);
I've tried:
List<Account> items = _context.CreateQuery<Account>()
.Where( c => ((OptionSetValue)c["new_accreditationstatus"]).Equals(7))
.ToList();
and
List<Account> items = _context.CreateQuery<Account>()
.Where( c => c.GetFormattedAttributeValue("new_accreditationstatus") == "7"
.ToList();
and
List<Account> items = _context.CreateQuery<Account>()
.Where( c => c["new_accreditationstatus"] == "7"
.ToList();
The last on throws a System.Format exception.
Filters on normal properties, i.e. .Where(c => c.AccountNumber.StartsWith("2010")) work perfectly fine.
You can only get access to the _____Set entities when generating the early-bound CRM file (look into crmsvcutil.exe/Xrm.cs online) and creating an early-bound derivative of CrmOrganizationServiceContext (commonly called XrmServiceContext). You can see the available constructors in the early-bound file.
So if you know the (int) value of the OptionSetValue in advance (7, in this case), you can just use this value as one of the arguments in the Where clause, as you've stated elsewhere:
.Where( c => c.new_AccreditationStatus.Value == 7)
EDIT (try this):
var list = _context.AccountSet.Where(c =>
c.FormattedValues["new_accreditationstatus"] == "7").ToList();
Another great question, but unfortunately, I think this will represent another failure/"limitation" of the Linq provider, which doesn't mention anything about FormattedValues as one of the permitted uses of the Where clause, though it is permitted as an item in the Select clause.
The actual values for OptionSetValues are stored in the StringMap entity, and incidentally enough, you can access the StringMap entity via Linq. An example is as follows.
// This query gets one permissible value for this entity and field.
var actualValue = _context.CreateQuery("stringmap")
.Where(x => x.GetAttributeValue<string>("attributename") == "new_accreditationstatus")
.Where(x => x.GetAttributeValue<int>("value") == "7")
.Where(x => x.GetAttributeValue<int>("objecttypecode") == Account.EntityTypeCode)
.Select(x => x.GetAttributeValue<string>("value"))
.Single();
However, trying to build on this with a subquery and a version of your original query, as in the below, results in an exception, also below.
var actualValues = _context.CreateQuery("stringmap")
.Where(x => x.GetAttributeValue<string>("attributename") == "new_accreditationstatus")
.Where(x => x.GetAttributeValue<int>("objecttypecode") == Xrm.Account.EntityTypeCode);
// This (modified) query uses the StringMap values from the previous query in
// a subquery, linking to the int (attributevalue) value that
// new_accreditationstatus represents.
List<Account> items = _context.CreateQuery<Account>()
.Where(c => actualValues
.Where(x => x.GetAttributeValue<int>("attributevalue") == c.new_accreditationstatus.Value)
.Select(x => x.GetAttributeValue<string>("attributevalue"))
.Single() == "7")
.ToList();
...throws an exception.
Privilege Type Read not defined on entity 'StringMap'.
Which is of course frustrating, because somehow, Linq allows you to query the string map in the first query.
So you'll have to first query the StringMap entity for the AttributeValue that corresponds to "7", then use that value in a new query that references that value as follows:
var actualValue = _context.CreateQuery("stringmap")
.Where(x => x.GetAttributeValue<string>("attributename") == "new_accreditationstatus")
.Where(x => x.GetAttributeValue<int>("value") == "7")
.Where(x => x.GetAttributeValue<int>("objecttypecode") == Account.EntityTypeCode)
.Select(x => x.GetAttributeValue<string>("attributevalue"))
.Single();
List<Account> items = _context.CreateQuery<Account>()
.Where(c => c.new_accreditationstatus = new OptionSetValue(actualValue)
.ToList();
If I can ever find a way to do all of this in one query, I will definitely edit and repost.
Have you seen the crmsvcutil extension that will generate enumerations for optionsets?

Categories