I've got realm implemented in a PCL for Xamarin. This works fine, and as it should (data is being stored and retrieved).
Now that I'm building more and more features I'm running into the situation that I can't find a way to query empty collections.
I need to return a IRealmCollection<Customer> because of model binding, so I can't enumerate and THEN filter items out that have no blogentries.
Any idea how I can make this happen on an IQueryable?
I tried
var realm = Realm.GetInstance();
var customers = realm.All<Customer>();
// errors out - only Realm-managed props can be used
customers = customers.Where(x => x.BlogEntries.Count > 0));
// errors out - Any() is not supported
customers = customers.Where(x => x.BlogEntries.Any());
// errors out - Datatype mismatch in comparison
customers = customers.Where(x => x.BlogEntries != null);
// errors out - Datatype mismatch in comparison
customers = customers.Where(x => x.BlogEntries == default(IList<BlogEntries>));
Unfortunately that is not supported as of Realm Xamarin 1.2.0. What you could do is implement a poor-man's collection notifications to work around the issue:
public class MyViewModel
{
private IRealmCollection<BlogEntry> _blogEntries;
private IEnumerable<Customer> _customers;
public IEnumerable<Customer> Customers
{
get { return _customers; }
set { Set(ref _customers, value); }
}
public MyViewModel
{
Customers = realm.All<Customer>()
.AsEnumerable()
.Where(c => !c.BlogEntries.Any())
.ToArray();
_blogEntries = realm.All<BlogEntry>().AsRealmCollection();
_blogEntries.CollectionChanged += (s, e) =>
{
var updatedCustomers = realm.All<Customer>()
.AsEnumerable()
.Where(c => !c.BlogEntries.Any())
.ToArray();
if (!IsEquivalent(updatedCustomers, Customers))
{
Customers = updatedCustomers;
}
};
}
private bool IsEquivalent(Customer[] a, Customer[] b)
{
if (a.Length != b.Length)
{
return false;
}
for (var i = 0; i < a.Length; i++)
{
if (!a[i].Equals(b[i]))
{
return false;
}
}
return true;
}
}
As when we call ToArray() we only lose collection change notifications, we implement them naively by observing the blog entries collection in Realm and doing a simplistic check if anything has been updated. If you feel like it, you could extend this solution and wrap it in a custom implementation of INotifyCollectionChanged and bind to that. Then, you could even apply some semantics to raise the correct collection change events (or opt for a simple .Reset for each change).
To address any performance concerns, calling ToArray on a Realm collection will not materialize objects' properties, so it's relatively cheap. The only somewhat expensive operation is iterating over all Customer objects and checking their BlogEntries lists. My advise would be to give it a try and see if it performs satisfactory for your use case.
Related
I'm using Entity Framework Core 6 and I want to find a series of entities in a DbSet. The entities I want to obtain are the ones match some properties in a list of input objects.
I've tried something like this:
public IEnumerable<MyEntity> FindEntities(IEnumerable<MyEntityDtos> entries)
{
return dbContext.MyDbSet.Where(r => entries.Any(e => e.Prop1 == r.Prop1 && e.Prop2 == r.Prop2));
}
But I get the classic EF Core exception saying that my LINQ cannot be translated to a database query (the problem in particular is the entries.Any(...) instruction)
I know I can just loop over the list of entries and obtain the entities one by one from the DbSet, but that is very slow, I was wondering if there was a more efficient way to do this in EF Core that I don't know about.
I think this should work:
public IEnumerable<MyEntity> FindEntities(IEnumerable<MyEntityDtos> entries)
{
var props1=entries.Select(x=>x.Prop1).ToArray();
var props2=entries.Select(x=>x.Prop2).ToArray();
return dbContext.MyDbSet.Where(r => props1.Contains(r.Prop1) && props2.Contains(r.Prop2));
}
In the end, I've done this:
public static IEnumerable<MyEntity> GetRangeByKey(this DbSet<MyEntity> dbSet, IEnumerable<MyEntity> toFind)
{
var keys = new HashSet<string>(toFind.Select(e => e.Id));
IEnumerable<MyEntity> result = null;
for (int i = 0; i < keys.Length; i += 1000)
{
var keyChunk = keys[i..(Math.Min(i + 1000, keys.Length))];
var res = dbSet.Where(x => keyChunk.Any(k => x.ResourceArn == k));
if (result == null)
{
result = res;
}
else
{
result = result.Concat(res);
}
}
return result;
}
Basically I get the keys to find in a HashSet and use it to perform a Where query, which will be translated to a SQL IN clause which is quite fast. I do it in chunks because there's a maximum number of values you can put in a IN clause before the DB engine refuses it.
I am attempting to implement a controller method to reorder image indexes that need to be saved in the database using EF Core.
I have the following controller method:
[HttpPost]
public async Task<JsonResult> ReorderImage(int p_iImageID, int p_iNewOrderIndex)
{
if (p_iImageID <= 0)
return Json(new { Status = "Error", Message = $"Unable to retrieve item image with ID of {p_iImageID}" });
ItemImage l_oItemImage = await _Context.ItemImages.FirstOrDefaultAsync(l_oImage => l_oImage.ID == p_iImageID);
if (l_oItemImage.IsNull())
return Json(new { Status = "Error", Message = $"Unable to retrieve item image with ID of {p_iImageID}" });
List<ItemImage> l_oItemImages = await _Context.ItemImages.Where(l_oImage => l_oImage.ItemID == l_oItemImage.ItemID)
.OrderBy(l_oImage => l_oImage.Order)
.ToListAsync();
l_oItemImages.Remove(l_oItemImage);
l_oItemImages.Insert(p_iNewOrderIndex, l_oItemImage);
foreach(ItemImage l_oImage in l_oItemImages)
{
l_oImage.Order = l_oItemImages.IndexOf(l_oImage);
if (l_oItemImages.IndexOf(l_oImage) == 0)
l_oImage.IsPrimary = true;
else
l_oImage.IsPrimary = false;
l_oImage.Uri = _AzureBlobStorage.GetBlobUri(_ItemImageAzureBlobContainerName, l_oImage.GetFileName());
}
_Context.ItemImages.UpdateRange(l_oItemImages);
await _Context.SaveChangesAsync();
return Json(l_oItemImages)
}
The order and data of l_oItemImages when calling UpdateRange() and subsequently SaveChangesAsync() appears correct to me.
I've been looking at this question which mentions not creating new classes and using UpdateRange(). This seems a bit different but I can see how this might be my issue.
Am I having this issue because I'm manipulating the objects of the list using Remove(l_oItemImage) and then Insert(p_iNewOrderIndex, l_oItemImage)? Or is it because I'm using ToListAsync() to begin with when I grab the item images?
EDIT: Tried Update(l_oItemImage) in place of UpdateRange(l_oItemImages) with same results. Added image of QuickWatch showing tacked entities both are correctly showing State = Modified as well as the expected changed values for int Order and bool IsPrimary properties.
EDIT 2: Added image of QuickWatch data with highlighted changed properties on entities.
Yes, you should be able to take advantage of the List methods however I think UpdateRange is unnecessary for this common task, here is an alternative implementation.
You may want to consider something like the following instead where the Sequence is reassigned for a subset of sequenced entities:
public async Task SetSequenceAsync(int forPageComponentId, int newSequence)
{
var infoAboutItemWereChangingSequenceFor = await context.PageComponents
.Where(x => x.Id == forPageComponentId)
.Select(x => new {
OriginalSequence = x.Sequence, // I need to know it's current sequence.
x.PageId // I need to only adjust sequences for items that have the same PageId, so I need to know what the pageId is for the item we're targeting.
}).FirstOrDefaultAsync();
// Get just the data we want to modify, we're going to include the item we're targeting so this list is inclusive of it.
// Including the item we're changing to make logic below a little mor consise instead of managing the list and the item we're targeting
// seperately.
var allItemsWithSequenceThatWillChange = await context.PageComponents
.Where(x =>
x.PageId == infoAboutItemWereChangingSequenceFor.PageId // Only those items sharing the same page Id.
// Only those items we need to change the sequence for.
&& x.Sequence >= Math.Min(infoAboutItemWereChangingSequenceFor.OriginalSequence, newSequence)
&& x.Sequence <= Math.Max(infoAboutItemWereChangingSequenceFor.OriginalSequence, newSequence)
)
.Select(x =>
new PageComponent() // The type of object EF knows about.
{
// The Primary key, so Entity Framework knows what record to change the sequence on.
Id = x.Id,
// The sequence value we need to change.
Sequence = x.Sequence
}
).ToListAsync();
// Set the sequence of the item we're targeting.
allItemsWithSequenceThatWillChange
.Where(x => x.Id == forPageComponentId)
.First()
.Sequence = newSequence;
// Now update the sequence on the other items (excluding the target item)
foreach (var item in allItemsWithSequenceThatWillChange.Where(x => x.Id != forPageComponentId))
{
// Either increment or decrement the sequence depending on how the original item was moved.
item.Sequence += infoAboutItemWereChangingSequenceFor.OriginalSequence > newSequence ? 1 : -1;
// Add any other property changes here.
}
// Save changes.
await context.SaveChangesAsync();
}
Also, as a matter of simplification on your ItemImage object, I notice you have an apparently DB persisted property "IsPrimary" - you may want to change this to be calculated on the entity and even at the db level instead, eg:
public class ItemImage {
// ... Other Properties ...
public int Order { get; set; }
public bool IsPrimary {
get => Order == 0;
set {}
}
}
For a calculated column in your MSSQL Database you can query against, add to your DbContext OnModelCreating:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity(typeof(ImageObject)).Property("IsPrimary").HasComputedColumnSql("CASE WHEN [Order] = 0 THEN 1 ELSE 0 END");
}
I'm not sure I fully understand the n+1 problem. Does this case also relate to the n+1 problem?
Using EF Core: for example there are 10000 members and 1000000 transactions.
public class ReportService
{
...
public IEnumerbale<ReportItem> GetResult()
{
var reportItems = new List<ReportItem>();
var members = _context.Users.Where(x => x.IsMember);
foreach(var member in members)
{
var calculationResult = _calculationService.Calculate(member.Id);
reportItems.Add(calculationResult);
}
return reportItems;
}
}
public class CalculationService
{
...
public CalculationResult Calculate(int memberId)
{
var memberTransactions = _context.Transactions.Where(x => x.UserId == memberId);
var result = new CalculationResult(memberTransactions.Sum(x => x.Amount));
return result;
}
}
Should I move responsibility to get data from the CalculationService (to avoid many queries)? What is the best way to avoid situations like this one?
Finding users and than making a foreach to finding each users Transaction data will make a lot of effort and may kill your perfomance.
This kind of code will make you api quite slow and it may throw time out exceptions. the better way will be joining your both tables and just getting result from joined list. Something like this will be better.
context.Users.Join(
contex.Transactions,
x => x.MemberId,
xm => xm.MemberId
(x,xm) => new {Users = x, Transactions = xm }
).Select(p => p.Transactions.Amount).Sum()
This will make easier for you app and you dont't need each time make a query.
I have a list of transactions and i need to find if there is more then 1 account
i did
var MultipleAccounts = list.GroupBy(t => t.AccountId).Count() > 1;
is there a better way?
If you're willing to lose the single-line I prefer the use of !.All(item => bool) or .Any(item => bool) as I think it's the most semantic and easiest to read, as well as being a good candidate for the fastest.
var accountId = accounts[0].AccountId;
var hasMultipleAccounts = !accounts.All(account => account.AccountId == accountId);
Alternatively, and perhaps even more semantically, you could use .Any(item => bool) instead of .All(item => bool).
var accountId = accounts[0].AccountId;
var hasMultipleAccounts = accounts.Any(account => account.AccountId != accountId);
Things to watch out for are making sure you have at least one item (so that accounts[0] doesn't fail) and not doing a multiple enumeration of your IEnumerable. You say you're working with a List, so multiple enumeration shouldn't cause you any trouble, but when you just have an unknown IEnumerable it's important to be careful.
I prefer:
var MultipleAccounts = list.Select(t => t.AccountId).Distinct().Skip(1).Any();
This should be exceedingly fast as it will stop iterating the source list as soon as it finds a second AccountId.
Anytime you execute a full .Count() it has to iterate the full source list.
You can test this with the following code:
void Main()
{
Console.WriteLine(Data().Select(t => t).Distinct().Skip(1).Any());
}
private Random __random = new Random();
public IEnumerable<int> Data()
{
while (true)
{
var #return = __random.Next(0, 10);
Console.WriteLine(#return);
yield return #return;
}
}
A typical run looks like this:
7
9
True
Ok here is what i found the quickest
public bool HasMultipleAccounts(List<Account> list)
{
foreach (var account in list)
if (account.AccountId != list[0].AccountId)
return true;
return false;
}
usage: var MultipleAccounts = HasMultipleAccounts(list);
Credits: #hvd
i know its more code but if you think what the cpu needs to do its the quickest
I want to create a reusable query object for my project that uses ElasticSearch. I am already using a similar QueryObject from the Generic Unit of Work/Repositories by LongLe for queries against the database using Entity Framework.
I can't really seem to wrap my head around exactly how to do this - I'm not sure how to "chain" the parts of the lambda expression together. I've tried using Expression/Func but this is a new area for me that I do not fully understand. I feel as though I'm just stabbing in the dark.
Since I don't even know exactly how to word my question, here's an example of what I am currently doing, what I am trying to do, and my progress so far:
What I currently have to do:
ISearchResponse<DemoIndexModel> result = client.Search<DemoIndexModel>(s => s.Query(
q => q.Term(t => t.FirstName, firstName)
&& q.Term(t => t.LastName, lastName)));
What I would like to do:
var query = new DemoIndexQuery();
query = query.ByFirstName(firstName);
query = query.ByLastName(lastName);
result = client.Search<DemoIndexModel>(s => s.Query(query.Compile()));
Code so far:
public abstract class ElasticQueryObject<T> where T : class
{
private Func<QueryDescriptor<T>, QueryContainer> _query;
// tried using Expression, still completely lost
private Expression<Func<QueryDescriptor<T>, QueryContainer>> _expression;
public Func<QueryDescriptor<T>, QueryContainer> Compile()
{
return _query;
}
public Func<QueryDescriptor<T>, QueryContainer> And(Func<QueryDescriptor<T>, QueryContainer> query)
{
if (_query == null)
{
_query = query;
}
else
{
// how do I chain the query??? I only can figure out how to set it.
}
return null;
}
}
public class DemoIndexQuery : ElasticQueryObject<DemoIndexModel>
{
public DemoIndexQuery ByFirstName(string firstName)
{
And(p => p.Term(term => term.FirstName, firstName));
return this;
}
public DemoIndexQuery ByLastName(string lastName)
{
And(p => p.Term(term => term.LastName, lastName));
return this;
}
}
You're breaking the LINQ contract. The query should be immutable - all the methods operating on the query should return a new query instead of modifying the old one. Due to the way the query is built, this is intrinsically composable, so instead of
public DemoIndexQuery ByFirstName(string firstName)
{
And(p => p.Term(term => term.FirstName, firstName));
return this;
}
you can just use this:
public DemoIndexQuery ByFirstName(string firstName)
{
return Where(p => p.Term(term => term.FirstName, firstName));
}
If this is not possible for some reason, you'll need to handle building the expression tree yourself, before you pass it forward. The simplest way would be something like this:
Expression<...> oldQuery = ...;
var newCondition = (Expression<...>)(p => p.Term(...));
return Expression.And(oldQuery, newCondition);
If your query provider doesn't support this, you'll need a bit more work - you can build the whole where predicate yourself separately, and then make sure you fix the lambdas and lambda parameters.