Exception in Expression Trees - entity-framework

This is my model:
- Business
- BusinesType - FK
- Categories (*) - FK
- Branch (*)
- BranchType - FK
- Address
- Phone (*)
- CustomFields (*)
- OpeningTimes (*)
- WorkingPeriods (*)
- .....
Now I have a controller-action that accepts a form that consists of the whole bunch of data as a single Business entity with all its properties and collections set fine.
Now I have to walk thru all the properties and collections recursively, and compare with the database graph; if they don't exist, add them, if they do walk thru all properties again and perform the same to a deeper level until no navigation properties are left over. Since I have many more properties and descendants than mentioned in the previous example, it's just inside to walk thru them manually.
Thanks to this answer I found GraphDiff which offered a brilliant solution to the situation.
Here's an update query I'm calling:
Context.UpdateGraph(business, bus => bus
.AssociatedEntity(bu => bu.BusinessType)
.AssociatedCollection(bu => bu.Categories)
.OwnedCollection(bu => bu.Branches, branch => branch
.AssociatedEntity(b => b.BranchType)
.OwnedEntity(b => b.Address)
.OwnedCollection(b => b.Phones)
.OwnedCollection(b => b.CustomFields)
.OwnedCollection(b => b.OpeningTimes, openingTimes => openingTimes
.OwnedCollection(b => b.WorkingPeriods)
)
)
);
It throws this exception:
System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'.
I tried debugging the source code, but I'm not an expert with Expression Trees, the problem occurs when the internal Include call (to include object graph to load store object) tries to attach WorkingPeriods, looks like it's not ready to take that deepness level of recursion. I messed around with it a bit, but I'm sure someone with extensive knowledge in expression trees will be able to solve this easily. Any suggestions will be appreciated on that to.
Here's what the include path expression is supposed to be generated like:
.Include(b =>
b.Branches.Select(br =>
br.OpeningTimes.Select(ot =>
ot.WorkingPeriods)));
Here's the stacktrace of the error.
Essentially, the exception is thrown because the recursive call returns the inner include as a method call, without processing it and returning the collection property it's meant to expose.

sorry it took me a while to get back to you.
I'ts 3am and I've had a fair bit of wine but the problem is fixed :) If you get the latest version of code # https://github.com/refactorthis/GraphDiff it should work fine.
I'll update the new nuget package (RefactorThis.GraphDiff) soon.

Related

How do I use Entity Framework to find a record in Cosmos with case insensitive conditions

I know tis question has been asked several times on SO but I haven't found a solution that works.
It's clear that Cosmos is capable of case insensitive searches since June 2020 but I haven't managed to find a solution that works with EF.
For example...
await objDbContext.Tags.SingleOrDefaultAsync(t => t.Value.Equals("value", StringComparison.InvariantCultureIgnoreCase))
... throws the following exception:
System.InvalidOperationException: 'The LINQ expression 'DbSet()
.Where(t => t.Value.Equals(
value: "Value",
comparisonType: InvariantCultureIgnoreCase))' could not be translated. Additional information: Translation of the 'string.Equals' overload with a 'StringComparison' parameter is not supported. See https://go.microsoft.com/fwlink/?linkid=2129535 for more information. 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'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'
I've also tried .ToLower and string.Equals with similar results.
Is there a right way to do it? If not, is there an alternative? I'm happy to store the value in lowercase along side the actual value, to be used for searching only, but it must be updated automatically.
EF Core 5 has now implemented the transactions. Once you have updated you can change this line
await objDbContext.Tags.SingleOrDefaultAsync(t => t.Value.Equals("value", StringComparison.InvariantCultureIgnoreCase));
to
await objDbContext.Tags.SingleOrDefaultAsync(t => t.Value.ToLower() == "value");

TYPO3 DataHandler: Copying record and relate it to itself --> running into loop

I'm developing an event extension with recurring dates. Therefor, I have a recurring date pattern and copy the record for each date. So the record has a relation to itself:
- Main Event
-- N Child Events
Currently, I use the DataHandler method copyRecord, which works perfect. But this just copies the record, without mapping the relation.
- Main Event --> should have the count of children in the database
-- N Child Events --> should habe the relation to its parent main event
The DB should look like:
Event 1 (Main Event) | uid: 1 | event: 0 | recurring_children: 3 (count)
Event copy 1 | uid: 2 |  event: 1 | recurring_children: 0
Event copy 2 | uid: 3 |  event: 1 | recurring_children: 0
Event copy 3 | uid: 4 |  event: 1 | recurring_children: 0
I tried several ways, but none without problems.
The following try sets the relation in the database, but creates more events (I guess, this loops and I have to build a condition to avoid filling the datamap with duplications):
public function processDatamap_afterDatabaseOperations(
$status,
$table,
$recordUid,
array $fields,
\TYPO3\CMS\Core\DataHandling\DataHandler $parentObject){
$event = BackendUtility::getRecord($table, $recordUid);
if ($status === 'update') {
/** #var \TYPO3\CMS\Core\DataHandling\DataHandler $tce */
$tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
$tce->start(array(), array());
$overrides = [
'is_recurring_parent' => 0,
'is_recurring_child' => 1,
'recurring_weekdays' => '',
'recurring_period_end' => '0000-00-00',
'event' => $recordUid,
'title' => 'FOOBAR'
];
if ($event['is_recurring_parent']) {
$foobar = $tce->copyRecord('tx_technoseumevents_domain_model_event', $event['uid'], $event['pid'], false, $overrides);
}
T3 version 8.7
Sorry, my first time I'm using the datahandler for complex tasks. Maybe someone has a hint for me...
The recursiveness happening in the first place, indicates a problem with your architecture that might be worth addressing before this problem as it would solve it at the root cause instead of treating symptoms. Normally, a record should not relate to itself - but that said, recursiveness can happen in other ways and it might not be possible to avoid in your use case.
Advise for addressing the problem:
Consider adding a condition that prevents your code from being called on tables other than the one table you need to operate on. This alone could actually be why your copies recurse so that's number one to handle.
If possible, and if you are not concerned with short-circuiting things like changing the title TCA field for a record to prepend "Copy of", or causing the copy to be hidden, you can switch to copyRecord_raw. It takes slightly different arguments (you may have to refactor to pass some arguments as part of the overrides array, compared to calling copyRecord). Doing that will prevent hooks from being called on the children you copy.
You are using a nested DataHandler instance - it might be preferable to call that method on the $dataHandler instance in your example. The DataHandler maintains a copy stack, but (parts of) it will only work if you do things in the same instance and only for some particular hooks (i.e. the pre-type and not-global hooks).
Lastly, there exists a runtime cache entry which contains an array of records which have been copied. Although the utility methods that access and store entries in this cache entry are not publicly accessible - so you cannot call them from your hook class - you can read the array of entries, manipulate it and put it back to prevent recursive processing of the same record. See the method isNestedElementCallRegistered in DataHandler (https://github.com/TYPO3/TYPO3.CMS/blob/v8.7.17/typo3/sysext/core/Classes/DataHandling/DataHandler.php#L9034).
Final note: the after-database-operations hook may be called at times when you do not expect it. For example, if you copy a record and also move it (as in: the copy comes from the UI, not programmatically) the array you receive in $fieldArray may not be the final one (for example, pid may be an unexpected value). Not being aware of this and the peculiarities above might also increase the danger of unintentionally causing recursive operations.
EDIT: If you are using bi-directional relations in TCA then removing one side may also improve the situation. For example, each "date" doesn't necessarily have to be aware of which event it is associated with. Something to consider.

Performance issue with fluent query in EF vs SharpRepository

I was having some performance issues using SharpRepository, and after playing around the SQL Query Profiler I found the reason.
With EF I can do stuff like this:
var books = db.Books.Where(item => item.Year == '2016');
if (!string.IsNullorEmpty(search_author))
books = books.Where(item => item.Author.Contains(search_author);
return (books.ToList());
EF will not really do anything until books is used (last line) and then it will compile a query that will select only the small set of data matching year and author from the db.
But SharpRepository evaluates books at once, so this:
var books = book_repo.Books.FindAll(item => item.Year == '2016');
if (!string.IsNullorEmpty(search_author))
books = books.Where(item => item.Author.Contains(search_author);
return (books.ToList());
will compile a query like "select * from Books where Year == '2016'" at the first line, and get ALL those records from the database! Then at the second line it will make a search for the author within the C# code... That behaviour can be a major difference in performance when using large databases, and it explains why my queries timed out...
I tried using repo.GetAll().Where() instead of repo.FindAll().... but it worked the same way.
Am I misunderstanding something here, and is there a way around this issue?
You can use repo.AsQueryable() but by doing that you lose some of the functionality that SharpRepository can provide, like caching or and aspects/hooks you are using. It basically takes you out of the generic repo layer and lets you use the underlying LINQ provider. It has it's benefits for sure but in your case you can just build the Predicate conditionally and pass that in to the FindAll method.
You can do this by building an Expression predicate or using Specifications. Working with the Linq expressions does not always feel clean, but you can do it. Or you can use the Specification pattern built into SharpRepository.
ISpecification<Book> spec = new Specification<Book>(x => x.Year == 2016);
if (!string.IsNullorEmpty(search_author))
{
spec = spec.And(x => x.Author.Contains(search_author));
}
return repo.FindAll(spec);
For more info on Specifications you can look here: https://github.com/SharpRepository/SharpRepository/blob/develop/SharpRepository.Samples/HowToUseSpecifications.cs
Ivan Stoev provided this answer:
"The problem is that most of the repository methods return IEnumerable. Try repo.AsQueryable(). "

Entity Framework Code First: "The specified table 'foo' was not found in the model."

Haven't seen this error before, and a cursory web search turns up very little. Here's (I think) the offending code:
this.HasMany(a => a.ListItems).WithRequired()
.Map(m =>
{
m.MapKey("AttributeId");
m.ToTable("ProductAttributeListItem");
}
)
;
And here's the full error:
The specified table 'ProductAttributeListItem' was not found in the
model. Ensure that the table name has been correctly specified.
The table is there and spelled correctly.
The lack of search results makes me think I'm missing something obvious. What might that be?
If you want to define the table name of the entity ListItems is refering to you need to do that on the entity, not in the relationship mapping:
modelBuilder.Entity<ListItem>() // or whatever the entity is called
.ToTable("ProductAttributeListItem");
And remove m.ToTable from the Map action.

Asp.Net MVC 2: How exactly does a view model bind back to the model upon post back?

Sorry for the length, but a picture is worth 1000 words:
In ASP.NET MVC 2, the input form field "name" attribute must contain exactly the syntax below that you would use to reference the object in C# in order to bind it back to the object upon post back. That said, if you have an object like the following where it contains multiple Orders having multiple OrderLines, the names would look and work well like this (case sensitive):
This works:
Order[0].id
Order[0].orderDate
Order[0].Customer.name
Order[0].Customer.Address
Order[0].OrderLine[0].itemID // first order line
Order[0].OrderLine[0].description
Order[0].OrderLine[0].qty
Order[0].OrderLine[0].price
Order[0].OrderLine[1].itemID // second order line, same names
Order[0].OrderLine[1].description
Order[0].OrderLine[1].qty
Order[0].OrderLine[1].price
However we want to add order lines and remove order lines at the client browser. Apparently, the indexes must start at zero and contain every consecutive index number to N.
The black belt ninja Phil Haack's blog entry here explains how to remove the [0] index, have duplicate names, and let MVC auto-enumerate duplicate names with the [0] notation. However, I have failed to get this to bind back using a nested object:
This fails:
Order.id // Duplicate names should enumerate at 0 .. N
Order.orderDate
Order.Customer.name
Order.Customer.Address
Order.OrderLine.itemID // And likewise for nested properties?
Order.OrderLine.description
Order.OrderLine.qty
Order.OrderLine.price
Order.OrderLine.itemID
Order.OrderLine.description
Order.OrderLine.qty
Order.OrderLine.price
I haven't found any advice out there yet that describes how this works for binding back nested ViewModels on post. Any links to existing code examples or strict examples on the exact names necessary to do nested binding with ILists?
Steve Sanderson has code that does this sort of thing here, but we cannot seem to get this to bind back to nested objects. Anything not having the [0]..[n] AND being consecutive in numbering simply drops off of the return object.
Any ideas?
We found a work around, by using the following:
Html.EditorFor(m => m, "ViewNameToUse", "FieldPrefix")
Where FieldPrefix is the "object[0]". This is hardly ideal, but it certainly works pretty well. It's simple and elegant.