I am working against a particular SDK in the .NET Framework (CRM SDK, however not critical to this question; just background) that allows me to write an expression against a data repository and return me results. The key here is that the query actually executed relies on which fields I ask for when the query is executed.
So if I run an expression that has 6000 items it might take 5 minutes to return all 120 columns and all 6000 records. However, if I only ask for 5-6 fields/columns, it might only take 5-10 seconds to return all 6000 items. The critical key here is which fields I ask for all in the object initializer affects the query run by the SDK.
To this end, I'm having trouble accessing a property of type EntityReference in my object initializer and immediately access a property on it in 1 call. Some code to explain - the problematic code below. It's also Very fast because query executed only asks for 3 properties, so data returned is small:
var results = allAccountsQuery.Select(account => new Account
{
AccountNo = account.GetAttributeValue<string>("AccountNumber"),
//AccountID NOT working!
AccountID = new EntityReference() { Id = account.GetAttributeValue<EntityReference>("AccountID").Id }.Id,
Name = account.GetAttributeValue<string>("AccountName")
}).ToList();
The offending line is as follows:
AccountID = new EntityReference() { Id = account.GetAttributeValue<EntityReference>("AccountID").Id }.Id
As you can see, I'm trying to instantiate an instance of EntityReference, initialize it, and then immediately access it's property on the back end. It fails with Object reference not set to an instance of an object
The following code works, but is super inefficient, because it brings back everything. The SDK is preforming some under the covers magic to only evaluate the query based on the fields asked for in an object initiazlizer. In this case the query is being evaluating and in the 1st iteration of the loop it brings back everything, but it works:
List<Account> allAccounts = new List<Account>();
//Super slow - framework actually gets back ALL accounts in 1st iteration
foreach (var account in allAccountsQuery)
{
Account newAccount = new Account
{
AccountNo = account.GetAttributeValue<string>("AccountNumber"),
Name = account.GetAttributeValue<string>("AccountName")
};
//WORKS: This separate instantiation and then accessing of the property works!!
//Note it is done in 2 steps outside the object initializer
//Need to see if I can do this in an object initializer instead of separately
EntityReference ef = account.GetAttributeValue<EntityReference>("AccountID");
newAccount.AccountID = ef.Id;
allAccounts.Add(newAccount);
}
So the question is how can I dictate an EntityReference instance and extract it's property Id in an object initializer so that my resulting evaluating query is small?
If I've understood you correctly you want to achieve a request comparable to the following SQL statement
SELECT AccountNumber, Id, Name FROM Accounts
A comparable request with the CRM SDK would be
AccountSet.Select(account => new Account
{
AccountNumber = account.AccountNumber,
Id = account.Id,
Name = account.Name
})
Hint: The Id or AccountId is only a Guid. It is not an EntityReference.
Therefore your call to account.GetAttributeValue<EntityReference>("AccountID") will return null. Therefore you get a NullReference when accesing the Id Property.
This results in the following QueryExpression
<?xml version="1.0" encoding="utf-8"?>
<QueryExpression xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/xrm/2011/Contracts">
<ColumnSet>
<AllColumns>false</AllColumns>
<Columns xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d3p1:string>accountnumber</d3p1:string>
<d3p1:string>name</d3p1:string>
</Columns>
</ColumnSet>
<Criteria>
<Conditions />
<FilterOperator>And</FilterOperator>
<Filters />
</Criteria>
<Distinct>false</Distinct>
<EntityName>account</EntityName>
<LinkEntities />
<Orders />
<PageInfo>
<Count>0</Count>
<PageNumber>1</PageNumber>
<PagingCookie i:nil="true" />
<ReturnTotalRecordCount>false</ReturnTotalRecordCount>
</PageInfo>
<NoLock>false</NoLock>
</QueryExpression>
So only these three attributes are requested and included in the result
I'm not sure what is wrong with your solution but I'm pretty sure that you really want to be limiting your columns on the query expression and then using the .ToEntity<> method to cast your collection to Accounts.
QueryExpression query = new QueryExpression("account");
query.ColumnSet = new ColumnSet(new string[] { "AccountId", "AccountNumber", "AccountName" });
List<Account> allAccounts = service.RetrieveMultiple(query).Entities.Select(account => account.ToEntity<Account>()).ToList<Account>();
Thus far the best working solution I have found is to load the initial data into an anonymous type collection, and then load the actual concrete type collection based on it:
//Anonymous type collection
var accounts = allAccountsQuery.Select(account => new
{
AccountNo = account.GetAttributeValue<string>("AccountNumber"),
AccountID = account.GetAttributeValue<EntityReference>("AccountID"),
Name = account.GetAttributeValue<string>("AccountName")
}).ToList();
Now that I did the dirty work to get the EntityReference instance defined, I can access it's .Id property when populating the concrete type:
var results = accounts.Select(account => new Account
{
AccountNo = account.AccountNo,
AccountID = account.AccountID.Id,
Name = account.Name
}).ToList();
This is tested and working. the results collection has the realized .Id property defined. However as you see I still had to do it in 2 steps. It does however solve my problem because the query runs quickly. I'm still open to being able to do this in 1 object initializer because this solution can get lengthy if I have to create a sort of temporary holding tank just to then access and map it's property later.
I'd still prefer an answer that does this loading in a single object initializer.
Related
I've tried to reduce this example to remove the OrganizationServiceProxy/XrmServiceContext, but now I believe the issue is originating there. I'm pulling data from a Dynamics 365 instance using the code generated by CrmSvcUtil.exe from Microsoft. Here is the relevant snippet:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
CrmServiceClient client = new CrmServiceClient(userName, CrmServiceClient.MakeSecureString(password), string.Empty, orgName, isOffice365: true);
using (OrganizationServiceProxy service = client.OrganizationServiceProxy)
{
XrmServiceContext crm = new XrmServiceContext(service);
var q = crm.CreateQuery<Account>();
List<Account> accounts = q.Where(x => x.AccountNumber != x.dynamics_integrationkey).ToList();
}
I get the following exception:
variable 'x' of type '{company-specific}.Account' referenced from scope '', but it is not defined
This is happening on the last line when the Linq is actually executed. It's only when it's created through the Microsoft.Xrm.Sdk.Client.OrganizationServiceContext that I get the error.
Things I've checked:
I get the error when comparing any two fields on the same object (not just Account), even when both are the same exact data type.
If I replace crm.CreateQuery... with a hand-populated List...AsQueryable() the Linq works fine
Most frustratingly, if I work through an intermediary list with List<Account> a = q.ToList(); to rasterize the result set first then this all works fine. It takes much longer to run because I've forfeited any lazy loading, but it works without error.
I've looked at other answers related to the referenced from scope '' but not defined Linq error, but they are all about malformed Linq. I know this Linq is well formed because it works fine if I change the underlying Queryable.
Clearly something is different between a List...ToQueryable() and the Microsoft.Xrm.Sdk.Linq.Query<> created by the XrmServiceContext (which inherits CreateQuery from Microsoft.Xrm.Sdk.Client.OrganizationServiceContext), but I don't know what it is.
The query you are trying to build (compare a field value with another field value on the same entity) is not possible in Dynamics.
The LINQ Provider still uses QueryExpression under the hood, meaning that your LINQ condition will be converted in a ConditionExpression, and that comparison is not possible.
Similar questions:
https://social.microsoft.com/Forums/en-US/d1026ed7-56fd-4f54-b382-bac2fc5e46a7/linq-and-queryexpression-compare-entity-fields-in-query?forum=crmdevelopment
Dynamics QueryExpression - Find entity records where fieldA equals fieldB
This morning I realized that my answer contained the flaw of comparing two fields against each other in a LINQ query. My bad. Thank you Guido for calling that out as impossible.
I generate my proxy classes with a 3rd party tool, so I'm not familiar with the XrmServiceContext class. But, I have confirmed that XrmServiceContext inherits from Microsoft.Xrm.Client.CrmOrganizationServiceContext, which inherits from Microsoft.Xrm.Sdk.Client.OrganizationServiceContext
I use OrganizationServiceContext for LINQ queries.
If you retrieve all the accounts first by calling ToList() on the query, you can then do the comparison of the two fields. If you have too many Accounts to load all at once, you can page through them and store the mismatched ones as you go.
And, I would probably retrieve a subset of the fields rather than the whole record (as shown).
var client = new CrmServiceClient(userName, CrmServiceClient.MakeSecureString(password), string.Empty, orgName, isOffice365: true);
using (var ctx = new OrganizationServiceContext(client))
{
var q = from a in ctx.CreateQuery<Account>()
where a.AccountNumber != null
&& a.dynamics_integrationkey != null
select new Account
{
Id = a.AccountId,
AccountId = a.AccountId,
Name = a.Name,
AccountNumber = a.AccountNumber,
dynamics_integrationkey = a.dynamics_integrationkey
};
var accounts = q.ToList();
var mismatched = accounts.Where(a => a.AccountNumber != a.dynamics_integrationkey).ToList()
}
After a quite complicated rework of code I stumbled over a small part of code which is not giving me the expected result. The following Linq query should query the data contained in the main object by selecting only those containing a particular string.
The main list contains about 5500 entries. after execution of the Linq query, the secondary object contains still these 5500 entries. Am I blind or already mentally deranged? The CompanyName parameter contains a company which effectively exists.
UPDATE: If the data is taken from the cache the list contains all relevant entries, just the query seems to have no effect.
public List<Account> GetAccountsByValue(string CompanyName)
{
string CacheKey = "Accounts";
ObjectCache dataCache = MemoryCache.Default;
if (dataCache.Contains(CacheKey))
{
var ResultCached = (IEnumerable<Account>)dataCache.Get(CacheKey);
var ResultCached2 = from c in ResultCached
where c.Name.Contains(CompanyName)
select new
{
c.Name,
c.Street,
c.City,
c.ID
};
var temp = ResultCached2.ToList();
return temp;
}
else
{
IList<CrmAccount> Accounts = CrmServiceAgent.GetAccounts();
var ResultNoCache = from CrmAccount f in Accounts
orderby f.DisplayName
select new Account(f);
// put the data in the cache
CacheItemPolicy cacheItemPolicy = new CacheItemPolicy();
cacheItemPolicy.AbsoluteExpiration = DateTime.Now.AddHours(4.0);
dataCache.Add(CacheKey, ResultNoCache, cacheItemPolicy);
return ResultNoCache.ToList();
}
}
The List of data which is queried looks like this (in WCF Test Client)
Everything works fine, just only the query part is not having effect which means that again all entries are returned instead of only thos containing the query parameter.
UPDATE:
Actually I get an Null Reference Exception, probably because the Name-Attribute of the first few entries is null....
This is throwing now the error:
var ResultCached3 = ResultCached.Where(c => c.Name.Contains(CompanyName)).ToList();
I have now fixed the whole thing. The data queried from the database now does not contain any null values anymore. The problem was, that mandatory fields where checked only in the frontend and not on database level. so data which usually is mandatory was imported as null values
so now in the List I have only "valid" data and now the linq query works as it should.
Thank you all for your hints ans suggestions!
kind regards
Sandro
var ResultCached2 = (from c in ResultCached
where c.Name.Contains(CompanyName)
select new
{
c.Name,
c.Street,
c.City,
c.ID
}).ToList();
So what I was trying was to fetch only those columns from table which has to be updated and I tried it as below:
var user = (from u in context.tbl_user where u.e_Id == model.Id select
new { u.first_name, u.last_name, u.role, u.email, u.account_locked })
.FirstOrDefault();
But when I tried to assign new value to the fetched data as below
user.first_name = model.FirstName;
I saw below error getting displayed
Property or indexer 'anonymous type: string first_name, string
last_name, string role, string email, bool account_locked.first_name'
cannot be assigned to -- it is read only
But when I retrieved all the values from table without filtering as below it worked fine.
var user = (from u in context.tbl_user where u.e_Id == model.Id select u).FirstOrDefault();
Why it doesn't work for first query. I've read in many sites that it is good to retrieve only required properties from database in terms of performance and security. But I am really not able to understand what's wrong with the first approach I opted. Any explanations are much appreciated.
Update
Are there any other ways to fetch only required column and update them and store them back?
Anonymous Types properties are read-only so you can not change them.
Stop doing micro-optimizing or premature-optimization on your code. Try to write code that performs correctly, then if you face a performance problem later then profile your application and see where is the problem. If you have a piece of code which have performance problem due to finding the shortest and longest string then start to optimize this part.
We should forget about small efficiencies, say about 97% of the time:
premature optimization is the root of all evil. Yet we should not pass
up our opportunities in that critical 3% - Donald Knuth
If you want to just fetch specific columns you can create a custom class and fill the properties in your query like others have mentioned.
As others said anonymous type is read only, for achieving what you want, you will have to create a type for it with properties that are required:
public class User
{
public string FirstName {get;set;}
public string LastName {get;set;}
.....................
.....................
}
and then, you have to use it in your linq query:
var user = (from u in context.tbl_user
where u.e_Id == model.Id
select new User
{
FirstName = u.first_name,
LastName = u.last_name,
......................,
.....................
}).FirstOrDefault();
Anonymous types are read-only by design.
In order to retrieve something that you can edit, you have to create a class and select your entities into that class, as done here: How to use LINQ to select into an object?
Or here:
var user = (from u in context.tbl_user where u.e_Id == model.Id select
new User_Mini { u.first_name, u.last_name, u.role, u.email, u.account_locked })
.FirstOrDefault();
Note: you won't be able to call context.SubmitChnages() when editing this new object. You could do something like this though: LINQ to SQL: how to update the only field without retrieving whole entity
This will allow you to update only certain parts of the object.
I have written test code as bellow:
Entities db = new Entities();
var place = new Place
{
Id = Guid.NewGuid(),
Name = "test",
Address = "address"
};
db.Places.Add(place);
var cachedPlace = db.Places.Where(x => x.Id == place.Id).FirstOrDefault(); \\ null
I expected dbset will return the added entity. But it gives me object only after changes were saved to the real DB.
If you want to access the unsaved query, then you use the Local property of the DbSet.
The reason it doesn't work the way you want is that it must also support autonumbered identities, and that will mean the ID is 0. If you insert multiple records, you would have multiple objects with the same 0 ID. EF won't know what the real ID is until after it's been saved.
I ran this code, but does it returns zero items but it should return one item because I know it exists in Rally.
var request = new Request("hierarchicalrequirement")
{
Fetch = new List<string>() { "ObjectID" },
Query = new Query("Name", Query.Operator.Equals, myUserStory.Name)
.And(new Query("Iteration.Name", Query.Operator.Equals, myUserStory.Iteration))
};
QueryResult queryResult = _restApi.Query(request);
by the way...
myUserStory.Name = a valid User Story name
myUserStory.Iteration = a valid Iteration name that the User Story belongs to
If you change your second query clause value to be myUserStory.Iteration.Name I bet it will work. My guess is that if you examine your Query (and call ToString() on it) you will see it is trying to call ToString() on myUserStory.Iteration, which is another DynamicJsonObject.
We have also found it helpful to have a proxy installed to examine the actual requests and responses. We have had good luck with Charles: http://www.charlesproxy.com/