Using .First() with IMobileServiceSyncTable? - azure-mobile-services

I can't figure out how to return one item from an IMobileServiceSyncTable. I have queries like this that are really awkward since I'm building a list and then checking the contents whereas I just want to get the First item or FirstOrDefault to return null if it doesn't exist.
var itemId = await _someItemTable.Where (x => x.ItemId == itemId)
.Select (x => x.OtherId)
.Take (1)
.ToListAsync ();

You can use the First or FirstOrDefault on the awaited result:
var itemId = (await _someItemTable.Where (x => x.ItemId == itemId)
.Select (x => x.OtherId)
.Take (1)
.ToEnumerableAsync ()).FirstOrDefault();

Related

EF Core rewrite the query in a form that can be translated after upgrading

This query was working fine before we upgraded:
var appUsers = await _masterDbContext
.Users
.Include(x => x.UserCustomers)
.AsNoTracking()
.Select(AppUserDto.Projection)
.Where(user => !user.IsDeleted && user.UserCustomers.Any(x => x.CustomerId == tenant.Id && x.UserId == user.Id))
.DistinctBy(x => x.Id)
.ToDataSourceResultAsync(request.GridOptions, cancellationToken: cancellationToken);
We're now getting the error:
Either rewrite the query in a form that can be translated,
or switch to client evaluation explicitly by inserting a call to 'AsEnumerable',
'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.
It appears that the DistinctBy is the offender but being fairly new to LINQ I can't figure out how to rewrite it so that it works.
Change DistinctBy to Distinct() and move that and the predicate before the Select. I also shifted the AsNoTracking() up:
var appUsers = await _masterDbContext
.Users
.AsNoTracking()
.Include(x => x.UserCustomers)
.Where(user =>
!user.IsDeleted
&& user.UserCustomers
.Any( x => x.CustomerId == tenant.Id ) )
.Distinct()
.Select(AppUserDto.Projection)
.ToDataSourceResultAsync(request.GridOptions, cancellationToken: cancellationToken);
Looks you have upgraded from EF Core 2.x, which loads full filtered set into the memory. DistinctBy is not translatable by EF Core 6.
Try the following solution:
var filtered = _masterDbContext.Users
.Select(AppUserDto.Projection)
.Where(user => !user.IsDeleted && user.UserCustomers.Any(x => x.CustomerId == tenant.Id && x.UserId == user.Id));
var query =
from d in filtered.Select(d => new { d.Id }).Distinct()
from u in filtered.Where(u => u.Id == d.Id).Take(1)
select u;
var appUsers = await query.ToDataSourceResultAsync(request.GridOptions, cancellationToken: cancellationToken);

How to get results from another query list if first linq query returns empty?

In this EF query, for the contacts list, I'm trying to query to get records for ContactTypeA and populate results in contacts list. If there are no records for ContactTypeA, then I want it to query for records for ContactTypeB and populate results in contacts list. I tried to use DefaultIfEmpty but that method only accepts single value and not a list. 'contacts' is a List object. Any ideas or even an alternative to DefaultIfEmpty? Thanks.
select(i => new transaction{
....
contacts = contactRepository.All.Where(c => c.AccountId == i.Account.Id && contactTypeRepository.All.Any(ct => ct.ContactId == c.Id && ct.Type == ContactType.ContactTypeA)).ToList().DefaultIfEmpty((contactRepository.All.Where(c => c.AccountId == i.Account.Id && contactTypeRepository.All.Any(ct => ct.ContactId == c.Id && ct.Type == ContactType.ContactTypeB)).ToList()
}
)
Firstly, make absolutely sure your contactRepository.All() method returns IQueryable<Contact> and not IEnumerable<Contact>, IList<Contact> or the like, otherwise you are automatically loading all contacts into memory.
From there, don't be afraid to simplify your query across multiple statements to make it a lot easier to understand. You should also leverage navigation properties rather than relying on completely disconnected entities and generic repositories and manually joining these all up in huge expressions.
Ideally an Account could have a collection of Contacts, but if not, at a minimum a Contact should have ContactType references:
var accountContactsQuery = contactRepository.All
.Where(c => c.AccountId == i.AccountId); //Remains IQueryable
var contacts = accountContactsQuery
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeA)
.ToList(); // Gets List of Contacts where contains at least 1 ContactTypeA type.
// If we have none, replace with results for ContactTypeB
if (!contacts.Any())
contacts = accountContactsQuery
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeB)
.ToList();
This looked a bit odd in that your ContactType appears to have a ContactId, (As opposed to Contact containing a ContactTypeId?) But the above reflects the relationship in your example.
With Account containing Contacts collection:
var accountContactsQuery = accountRepoitory.All
.Where(a => true /* replace with relevant criteria */);
var contacts = accountContactsQuery
.SelectMany(a => a.Contacts)
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeA)
.ToList(); // Gets List of Contacts where contains at least 1 ContactTypeA type.
// If we have none, replace with results for ContactTypeB
if (!contacts.Any())
contacts = accountContactsQuery
.SelectMany(a => a.Contacts)
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeB)
.ToList();
When dealing with conditions in expressions, I can suggest returning all relevant details, then building your final "payload" based on the conditions.
For example, if querying accounts to build transactions but wanting to load ContactAs if available and Bs for each account if not available:
var transactionData = accountRepoitory.All
.Where(a => true /* replace with relevant criteria */);
.Select(a => new
{
a.AccountId,
/* populate account and common details.. */
ContactAs = a.Contacts
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeA).ToList(), // Consider a Select to get relevant details...
ContactBs = a.Contacts
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeB).ToList()
}).ToList(); // Executes query against DB to load relevant data...
var transactions = transactionData
.Select( t => new Transaction
{
AccountId = t.AccountId,
/* Other fields */
Contacts = t.ContactAs.Any() ? t.ContactAs : t.ContactBs
}).ToList();
Essentially use the EF Linq expressions to load the possible data, so include results for both ContactA and ContactB, then afterwards build your final projection using that data and conditionally use the ContactA or B as suited. Generally I don't advise passing Entities back (Actual Contact entities) but project into minimally viable view models in the first EF query using Select.

Conditional WHERE clause on an Entity Framework context

objRecord = await _context.Persons
.Where(tbl => tbl.DeletedFlag == false)
.ToListAsync();
This is the EF code I've got which successfully gets all the records from the Person table where DeletedFlag is false.
I want to add another where criteria that if a surname has been passed in, then add the extra where clause
.Where(tbl => tbl.Surname.Contains(theSurname))
I've tried IQueryable and some other options but can't figure out how to do the equivalent of
string theSurname = "";
objRecord = await _context.Persons
.Where(tbl => tbl.DeletedFlag == false)
if ( theSurname != "") {
.Where(tbl => tbl.Surname.Contains(theSurname))
}
.ToListAsync();
which obviously doesn't work as you can't put an if statement in an EF call.
I can add a criteria afterwards that limits objRecord, but I don't want to retrieve all the records, then cut it down, I'd rather only get the records I need.
You can combine conditions in the Where method by just adding tbl.Surname.Contains(theSurname) so your final query will look like below:
objRecord = await _context.Persons
.Where(tbl => tbl.DeletedFlag == false &&
tbl.Surname.Contains(theSurname))
.ToListAsync();
You have to apply logical AND (&&) with the existing condition in Where clause i.e. tbl.Surname.Contains(theSurname);
So your query would be
.Where(tbl => tbl.DeletedFlag == false && tbl.Surname.Contains(theSurname));

Linq query using result of another query

I have a query that gets data in the form of an IQueryable
var assys = assetrelationshipRepository.GetAll()
.Where(x => x.AssetId == siteAssetId)
.Where(x => x.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent)
.Where(x => x.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.Assembly)
.Select(x => x.RelatedAsset.CustomAssetAttributes2);
For every 'assy' that is returned, I'd like to get it's AssetId and use this to get a list of 'subassys', see below. For each 'assy' record, the assyId variable should be substituted for its AssetId.
var subassys = assetrelationshipRepository.GetAll()
.Where(x => x.AssetId == assyId)
.Where(x => x.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent)
.Where(x => x.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.SubAssy)
.Select(x => x.RelatedAsset.CustomAssetAttributes2);
I assume I'll need to use ForEach, does anyone know if what I'm trying to do is possible?
Thanks
Applying ForEach would be extremely bad approach to retrieve your desired result. You could apply join and group for that queries instead.
var assysQuery = assetrelationshipRepository.GetAll()
.Where(x => x.AssetId == siteAssetId)
.Where(x => x.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent)
.Where(x => x.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.Assembly);
Then apply join and group;
var subAssysQuery =
from assy in assysQuery
join subAssy in assetrelationshipRepository.GetAll() on assy.Id equals subAssy.AssetId
where
subAssy.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent &&
subAssy.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.Assembly
group assy by assy.Id into g
select new
{
AssyId = g.Key,
SubAssets = g.Select(x => x.RelatedAsset.CustomAssetAttributes2),
};
This should give you CustomAssetAttributes2 value of subassys for per assy record.
Note : Also, I suggest you to use known type for select clause instead
of anonymous type.

Linq join query

For example
DB with 2 Tables
Book [BookId (int), Title (nvarchar), ShowInWebshop (bit)] and
InventoryDetail [InventoryDetailId (int), BookId (int), Quantity (int)]
Execute: SELECT * FROM Books LEFT JOIN InventoryDetails ON books.BookId = InventoryDetails.BookId
The output shows all Book columns and related InventoryDetails columns (including the InventoryDetails.BookId column)
..so far so good ...
Trying to transform this query into a Linq one (using LinqPad, comparing several examples, common sense, etc.) I compilated the following generic List (because I wanted a present a list)
private List<Book> Books(int count){
var books = webshopDB.Books
.Join<Book, InventoryDetail, int, Book>( webshopDB.InventoryDetails,
b => b.BookId,
i => i.BookId,
(b, i) => b )
.Where(b => b.ShowInWebshop == true)
.Take(count)
.ToList();
return books
}
This module returns a list of books! Not the one I expected, though! It returns only book details such as Title and ShowOnSite NOT the details from the InventoryDetail table: Quantity
What do I forget?
The result how it works so far ...
public ActionResult Index()
{
// This return a list of tuples {(WebshopDB.Models.Book, WebshopDB.Models.InventoryDetail)}
// Each tuple containing two items:
// > Item1 {WebshopDB.Models.Book}
// > Item2 {WebshopDB.Models.InventoryDetail}
var tuple_books = ListOfTuples_BookInventoryDetail(5);
...
// next step(s)
// add a ViewModel viewmodel
// ...
return (viewmodel);
}
private List<Tuple<Book, InventoryDetail>> ListOfTuples_BookInventoryDetail(int count)
{
var list_of_tuples = new List<Tuple<Book, InventoryDetail>>();
var showbooks = webshopDB.Books
.Join(webshopDB.InventoryDetails, b => b.BookId, i => i.BookId, (b, i) => new { b = b, i = i })
.Where(o => (o.b.ShowInWebshop == true))
.Where(o => o.b.BookThumbUrl.Contains(".jpg"))
.OrderByDescending(o => o.b.OrderDetails.Count())
.Take(count);
foreach (var item in showbooks)
{
list_of_tuples.Add( Tuple.Create<Book, InventoryDetail>( (item.b), (item.i) ) );
}
return list_of_tuples;
}
You need to select from both tables, e.g.
from b in webshop.Books
from i in webshopDB.InventoryDetails
where i.BookId = b.BookId
select b.BookId, b.Title, b.ShowInWebshop, i.InventoryDetailId, i.Quantity
You're getting books because you specified that in your Join statement with the final selector of => b. You want to select both, so use this:
var query = webshopDB.Books.Join(webshopDB.InventoryDetails,
b => b.BookId, i => i.BookId,
(b, i) => new { Book = b, InventoryDetail = i });
Then when you iterate over the results you can use:
foreach (var item in query)
{
Console.WriteLine(item.Book.SomeProperty);
Console.WriteLine(item.InventoryDetail.SomeProperty);
}
The other problem with your method is that the return type is a List<Book>. So the above won't work since a Book class is separate from an InventoryDetail class. You need to setup a new class to encompass both, or use a Tuple<Book, InventoryDetail> if using .NET 4.0.
To return a particular property you could modify the statement to:
var query = webshopDB.Books.Join(webshopDB.InventoryDetails,
b => b.BookId, i => i.BookId,
(b, i) => new { b.BookId, i.Quantity });
Again, you need an appropriate return type if you're returning a List<T>.
EDIT: to get a Dictionary<Book, InventoryDetail> you could use the earlier query as follows:
var query = webshopDB.Books.Join(webshopDB.InventoryDetails,
b => b.BookId, i => i.BookId,
(b, i) => new { Book = b, InventoryDetail = i })
.ToDictionary(o => o.Book, o => o.InventoryDetail);
Of course you can use the Where and Take as necessary before the ToDictionary call. You can also project just the properties you need as the query just before this one. You need to project it into an anonymous type via the new keyword (it was missing earlier so take another look at it).
Quering the answer of SoftwareRockstar in LinqPad4 generates errors! So, that's not the way!
//This does works (in LinqPad4)
from b in webshopDB.Books
from i in webshopDB.InventoryDetails
where b.BookId == i.BookId
where b.ShowInWebshop == true
select new { b.Title, i.Quantity, b.ShowInWebshop }