Entity Framework, Generic loading of related entities with a where condition - c#

I'm trying to create a function that generically loads the related child entities with a filter.
All my entities are derived from my own Base Class "BusinessObject"
public abstract class BusinessObject : BaseObject, IBaseObject, ILocalObject
{
[Browsable(false)]
[Key]
public int ID { get; set; }
[Browsable(false)]
public int? HqID { get; set; }
private bool _deleted;
[Browsable(false)]
public bool Deleted
{
get { return _deleted; }
set { CheckPropertyChanged(ref _deleted, value); }
}
}
I have created the following function that when supplied an entity will load all the related child objects.
When defining my entities, all child collections are flagged by my own attribute "EntityChildCollectionAttribute" so I can easily find the collections I want to load.
public virtual void OnLoadEntityChildren(object entity)
{
var propNames = entity.GetPropertyNames();
foreach (var propName in propNames.Where(propName => entity.PropertyHasCustomAttribute(propName, typeof(EntityChildCollectionAttribute))))
{
MyData.Entry(entity).Collection(propName).Load();
}
}
This works lovely!
My Problem comes when I want to filter the child collection.
In this case I want to only load child entities where Deleted == false.
I cannot work out how to do this!
I have had many attempts and replacing MyData.Entry(entity).Collection(propName).Load(); with
MyData.Entry(entity).Collection(propName).Query().Cast<BusinessObject>().Where(x=>x.Deleted.Equals(false)).Load();
compiles but then I get the error;
"Unable to cast the type 'FmOrderProcessing.Entities.OpDocumentDetail' to type 'FwBaseEntityFramework.BusinessObject'. LINQ to Entities only supports casting EDM primitive or enumeration types."
Any Help/Pointers/Answers will be gratefully received
Thanks in advance
Lance

I was implementing a "Soft Delete" pattern which means the records in the database are flagged as deleted rather than removed (for audit and replication purposes).
All entities are derived from a base definition with a bool Deleted property.
I found the answer here:
https://www.nuget.org/packages/EntityFramework.DynamicFilters
This package allows the definition of global filters in the data context.
I fixed my issue with one line of code in the OnModelCreating override.
modelBuilder.Filter("Deleted", (IBaseObject d) =>d.Deleted, false);
The filter function is applied globally to any entity presenting (in my case) the IBaseObject interface.
I hope this helps any body else with a similar issue

Related

Entity Framework performance issues adding child to list

I am working on a project where we are using Entity Framework 6.1.3. Right now we are experiencing pretty big performance issues when adding a child object to a list of the parent entity (see code sample below).
We are using lazy-loading, so what I noticed is that everything works fine until we call _parent.Children.Add(child); since it seems to load all children from the database just to be able to add a new one. Since some of our parent objects have about 50,000 children, this is delaying this simple insert call by 7-8 seconds and sometimes even causing timeouts.
To me it doesn't really make sense for Entity Framework to load all children just in order to add one, so is there a way I can avoid this or is this an Entity Framework design-flaw and should we find a workaround?
I'd obviously like to find a solution for this and would prefer not having to implement pure ADO queries for this one problem.
Thanks!
public class Parent
{
public Guid Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public Guid Id { get; set; }
}
public class ParentAggregate
{
private readonly Parent _state;
public ParentAggregate(Parent state)
{
_state = state;
}
public void AddChild(Guid id)
{
var child = new Child { Id = id };
_state.Children.Add(child);
}
}
To me it doesn't really make sense for Entity Framework to load all children just in order to add one
The lazy loading occurs the first time you access a navigation property through its getter. And the sample code
_parent.Children.Add(child);
consists of two operations:
(1) retrieve the Children property (through the property getter!):
var children = _parent.Children;
(2) perform some operation on it (call Add method in this case):
children.Add(child);
The lazy loading occurs because of the operation (1). As you can see, EF has nothing to do with that because it has no control over it. And there is no way to know what you are going to do with that property value - enumerate it, take a count or use Add, Remove etc. methods.
Here are some solutions.
First, why using lazy loading at all? It has so many side effects and inefficiencies, and all they can easily be solved by EF provided out of the box eager loading via Include methods. That's why by default EF Core ("the future of EF") does not use lazy loading by default and requires a special package and procedure for enabling it.
Second, if you insist using lazy loading, then you have the following two options:
(A) Disable lazy loading during the data modifications (requires access to/control of the DbContext instance):
dbContext.Configuration.LazyLoadingEnabled = false;
_parent.Children.Add(child);
dbContext.Configuration.LazyLoadingEnabled = true;
This also requires the collection property to be initialized in order to avoid NRE.
(B) Use explicit backing field and provide some direct access to it (to avoid triggering the lazy load by property accessor). For instance:
public class Parent
{
public Guid Id { get; set; }
private ICollection<Child> children;
public virtual ICollection<Child> Children { get => children; set => children = value; }
public void Add(Child child)
{
// use the backing field directly
if (children == null) children = new HashSet<Child>();
children.Add(child);
}
}

Convert scalar type to complex type in OData

I have following EF entity.
public class Order
{
public List<OrderItem> OrderItems { get; set; }
public bool Delivered { get { /* some logic involving OrderItems */ } }
}
I register it with OData like this:
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Order>("Orders");
My problem is that this "Delivered" isn't a field in database, but it is computed on the fly based on the statuses of the individual OrderItems. As such, it is an expensive field to compute so I'd like to include it only when client explicitly asks for it with "$expand=Delivered". If this expand isn't specified, then the property isn't included so it's not computed and the operation is cheap.
Unfortunately, this doesn't pass validation in the ODataController, it fails with:
Microsoft.OData.ODataException: 'Property 'Delivered' on type 'Gemalto.Sas.Authentication.Entities.UserDto' is not a navigation property or complex property. Only navigation properties can be expanded.'
Which kind of makes sense. Since I can't make it into navigation property, I tried to make it into complex type. If I understand this, complex type is "non scalar object without primary key". So I thought that just converting it into this will help:
public class Order
{
...
public ComplexValue Delivered { get; set; }
}
public class ComplexValue
{
public boolean Value { get { /* complex logic here ... */ } }
}
But ODataConventionModelBuilder still doesn't create it as a complextype but as normal property (confirmed via CSDL export). I also tried to explicitly call .ComplexType() on the builder, but that doesn't do anything (call succeeds though).
I feel like I'm missing something. Does anybody know how to achieve this?

Domain model vs Entity FW: Is this a case when splitting in a persistence model is usefull?

In a DDD approach, I have a Domain Model (DM), with a rich behaviour. Suppose I have a root entity, called Order and relative LineOrder. The exposed collection of LineOrder need to be a IReadOnlyCollection since none can alter the collection arbitrarily. In code:
public class Order : AggregateRoot {
// fields
private List<LineOrder> lineOrder;
// ctors
private Order() {
this.lineOrder = new List<LineOrder>();
// other initializations
}
// properties
public IReadOnlyCollection<LineOrder> LineOrder {
get
{
return lineOrder.AsReadOnly();
}
}
// behaviours
}
So far, so good. But when I want to persist this domain I have some technology restrictions imposed by Entity Framework (a key is needed even if I have a value object, a parameterless constructor and so on) that is not a perfect match with a DDD approach.
Another limitation that I have is:
public class OrderConfiguration : EntityTypeConfiguration<Order>
{
public OrderConfiguration()
{
ToTable("Order");
HasMany<LineOrder>(m => m.LineOrder); // Exception: Cannot convert from IReadOnlyCollection to ICollection
}
}
I cannot cast IReadOnlyCollection to ICollection (incidentally, if LineOrder was an ICollection everything was OK!).
For the reasons I have expressed above: could be usefull in this case create a Persistence Model (with belonging cons: mapping DM/PM and viceversa)?
Are there an alternative? And, above all: are there an alternative that well fit a DDD approach?
Have you tried declaring the LineOrder collection as protected? This way EF has access but consumers do not.
// properties
protected ICollection<LineOrder> LineOrder { get; set; }
You can then expose this collection in a read-only manner to the end user with:
public IReadOnlyCollection<LineOrder> ReadOnlyLineOrder
{
get
{
return LineOrder.ToList().AsReadOnly();
}
}

Is it possible to query on an interface property?

I have the following class:
public class BicycleSellerListing : IUserName
{
public UserProfile UserProfile { get; set; }
/// <summary>
/// IUserName interface property
/// </summary>
public string UserName
{
get
{
return UserProfile.UserName;
}
}
}
The interface:
public interface IUserName
{
string UserName { get; }
}
And this query:
public static List<T> GetList<T>(string userName) where T : class, IUserName
{
using (SqlUnitOfWork work = new SqlUnitOfWork())
{
return work.GetList<T>(row => row.UserName == userName)
.ToList();
}
}
When I execute this query, I get the following exception:
The specified type member 'UserName' is not supported in LINQ to
Entities. Only initializers, entity members, and entity navigation
properties are supported.
I understand why I'm getting the exception, but am wondering if there is a way I can perform this query using the interface?
To answer you quistion:
Is it possible to query on an interface property?
Yes. it's no problem. The fault you are getting is not becuase of the interface.
The problem is that you can't query on properties that isn't mappe with Linq 2 Entities
Other people have had this propblem as well.
The underlying expressionbuilder can not distinct between properties that is mapped to the database, and properties that is not.
It is a problem becuase the compiler can't help you.
In Linq to object, it is no problem, so the compiler doesnt throw any errors/warnings
You should try to make it clear the this property is not mapped - perhaps by a prefix, or a nested class that contains all the "custom" properties.
In addition to the existing answer(s), you can perform the where in memory, but that means retrieving the whole table.
Normally I wouldn't recommend this though.
public static List<T> GetList<T>(string userName) where T : class, IUserName
{
using (SqlUnitOfWork work = new SqlUnitOfWork())
{
return work.GetList<T>()
.AsEnumerable()
.Where(row => row.UserName == userName)
.ToList();
}
}
Some workarounds:
You could try to determine underlying entity type in runtime and
then dynamically compile and invoke a generic method executing query
with this type.
It is possible to assemble this query manually in runtime using
Expression class
You could try Entity SQL query syntax instead of Linq

Can I create "relay" or "genericized" properties for Entity Framework models?

I hope my wording makes sense... I wasn't quite sure exactly how to explain what I'm looking to do.
I have a method in a generic class that returns a list of entities as follows:
public abstract class ChildCRUDController<TModel> : CRUDController<TModel, ... >
where TModel : IChildEntity
public ViewResult List(int id)
{
return View(repository.GetMany(x => x.ParentID == id));
}
This controller is implemented by quite a few other controllers. The issue I have is that not all entities that implement IChildEntity have the same parent type. To get around this issue I created ParentID properties for all the models that implement IChildEntity so they could use the same controller.
public partial class PhoneNumber : IChildEntity
{
public int ParentID
{
get { return CustomerID; }
set { CustomerID = ParentID; }
}
}
and...
public partial class Transaction : IChildEntity
{
public int ParentID
{
get { return LeaseID; }
set { LeaseID= ParentID; }
}
}
But when I call the List method above I get the following error:
The specified type member 'ParentID' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Is there any way I can achieve the result I am looking for without pulling the object set into memory or renaming all the properties on the entities themselves?
Thanks!
If you are willing to pass the field name into the List method and to construct your own query you can do it using the techniques described in this StackOverflow article:
Querying Entity with LINQ using Dyanmic Field Name
Or you could supply the ChildCRUDController with another generic type parameter constrained to an interface that supplies the field name and again use it dynamically.

Categories