What is the correct name of an relationship which is passed to Eloqent `Model::load()`? - eloquent

I have to models Photo and SizeVariant. A photo has many size variants, a size variant has exactly one photo as its owner. The tables of the DB follow Eloquents naming scheme and are called photos and size_variants.
My models look like that (shortened down):
class Photo extends Model {
protected $with = [ 'size_variants' ];
public function sizeVariants(): HasMany {
return $this->hasMany(SizeVariant::class);
}
}
class SizeVariant extends Model {
protected $with = ['photo'];
protected $touches = ['photo'];
public function photo(): BelongsTo {
return $this->belongsTo(Photo::class);
}
}
However, when I try to call $photo->load('size_variants) on an existing $photo object, I receive an Illuminate\Database\Eloquent\RelationNotFoundException exception with message Call to undefined relationship [size_variants] on model [App\Models\Photo].
What is the right name of the relationship? I thought the naming scheme would follow Laravels usual pattern and Laravel magically maps between snake and camel case.
I already tried the all combinations of capitalizing, snake and camel case and plural vs. singular, i.e. $photo->load('size_variants'), $photo->load('size_variant'), $photo->load('sizeVariants'), $photo->load('sizeVariant'), $photo->load('SizeVariants') and $photo->load('SizeVariant').

Your relation name that is passed in with or load contexts should match the name of the relation you defined on your model, which in your case should be sizeVariants.
On another note, I would also be careful with always eager loading both relations by defining the protected $with property on both models. You'll end up getting timeouts because: your Photo model would be eager loading the SizeVariant models, which would eager load the Photo model, which would eager load the SizeVariant models, which would eager load the Photo model, etc.
You should remove the protected $with property and use with on your queries or load on your models whenever you need the related models instead to avoid this.

Related

EF Core load references of unknown entity

DISCLAIMER: Since we are all familiar with it, i will be using contoso university design to explain my question. Also, i am using EF core and .net core 2.0 on a mvc code first design.
I am developing a very generic RESTful API that works on any model. It has one method for each of create, read, update and delete operation in only one controller, the route of this is
[Route("/api/{resource}")]
Resource is the entity that the client wants to work with, for example if someone wants to get all Courses using the api he has to do a GET request on http://www.example.com/api/course/ or http://www.example.com/api/course/2 to get one by id and the following code will do the job.
[HttpGet("{id:int:min(1)?}")]
public IActionResult Read([FromRoute] string resource, [FromRoute] int? id)
{
//find resourse in models
IEntityType entityType = _context.Model
.GetEntityTypes()
.FirstOrDefault(x => x.Name.EndsWith($".{resource}", StringComparison.OrdinalIgnoreCase));
if (entityType == null) return NotFound(resource);
Type type = entityType.ClrType;
if (id == null)//select all from table
{
var entityRows = context.GetType().GetMethod("Set").MakeGenericMethod(type).Invoke(context, null);
if (entityRows == null)
return NoContent();
//TODO: load references (1)
return Ok(entityRows);
}
else //select by id
{
var entityRow = _context.Find(type, id);
if (entityRow == null)
return NoContent();
//TODO: load references (2)
return Ok(entityRows);
}
}
This small piece of code will do the magic with one small exception, intermediate collections will not be loaded. Given our example, the fetched course or courses will have no info for CourseInstructor (the intermediate collection in between Course and Person). I am trying to find a way to Eager load the navigation properties only if it is a collection; or by any other condition that will ensure that only many-to-many relationships are loaded.
For //TODO: load reference (2) i could use
_context.Entry(entityRow).Collection("CourseInsructor").Load();
On runtime if i could find all the navigation properties (filtered by spoken condition) and foreach of them i did Load(), i should get the desired result. My problem is when i get all (when id is null) the entityRows is type 'InternalDbSet' which is an unknown model.
So for the two TODOs i need some help on doing the following steps
1: find navigation properties of many-to-many relationships only
2: load them
Any suggestions?
In general, this seems like a very bad idea to me. While the CRUD stuff is going to be identical for most resources, there will be variances (as you've now run into). There's also something to be said for having a self-documenting API: with individual controllers, you know which resources can be accessed by nature of having a controller associated with that resource. With they way you're doing it, it's a complete black box. This also will of course effect any sort of actual generated API documentation. For example, if you were to include Swagger in your project, it would not be able to determine what you're doing here. Finally, you're now having to use reflection for everything, which will effect your performance.
What I would suggest instead is creating a base abstract controller and then creating a controller for each unique resource that inherits from that, for example:
public abstract class BaseController<TEntity> : Controller
where TEntity : class, new()
{
protected readonly MyContext _context;
public BaseController(MyContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
...
[HttpGet("create")]
public IActionResult Create()
{
var model = new TEntity();
return View(model);
}
[HttpPost("create")]
public async Task<IActionResult> Create(TEntity model)
{
if (ModelState.IsValid)
{
_context.Add(model);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(model);
}
...
}
I just wanted to give a quick example, but you'd build out all the rest of the CRUD methods in the same fashion, generically using TEntity. Then, for each actual resource, you simply do:
public class WidgetController : BaseController<Widget>
{
public WidgetController(MyContext context)
: base(context)
{
}
}
No duplication of code, but you've now got an actual real controller backing the resource, aiding both the innate and possibly explicit documentation of your API. And, no reflection anywhere.
Then, to solve problems like what you have here, you can add hooks to your base controller: essentially just virtual methods that are utilized in your base controller's CRUD actions and do nothing or just default things. However, you can then override these in your derived controllers to stub in additional functionality. For example, you can add something like:
public virtual IQueryable<TEntity> GetQueryable()
=> _context.Set<TEntity>();
Then, in your derived controller, you can do something like:
public class CourseController : BaseController<Course>
{
...
public override IQueryable<Course> GetQueryable()
=> base.GetQueryable().Include(x => x.CourseInstructors).ThenInclude(x => x.Instructor);
So, for example, you'd make your BaseController.Index action, perhaps, utilize GetQueryable() to get the list of entities to display. Simply by overriding this on the derived class, you can alter what happens based on the context of a particular type of resource.

Laravel Eloquent Models __construct method to call relations

I want to have my model automatically call its relations when instantiated. As of now my model looks like this:
class AdminLog extends Model{
public function __construct(){
$this->belongsTo('App\User', 'admin_id');
}
}
but when i try to do dd(AdminLog::get()->first());, it doesnt show any relations.
Edit#1: tried adding parent::__construct(); inside the model's __construct method but it didn't work.
belongsTo() defines a relationship, it doesn't load it.
First you need to define the relationship, then you can load it at any point using the load method.
class AdminLog extends Model {
public function user() {
return $this->belongsTo(\App\User::class, 'admin_id');
}
}
$log = AdminLog::first();
$log->load('user');
It is possible to load inside the constructor, but I would highly recommend against that. If you have 20 AdminLog objects then it will query the database 20 times, once for each object. That's inefficient.
What you should do instead is use eager loading. This will query the users table just once for all 20 admin logs. There are many ways to do this, here is an example:
$logs = AdminLog::take(20)
->with('user')
->get();
dd($logs->toArray());

Serializing List of base object types using XmlMessageFormatter

I have an object like so:
public class Intent
{
public List<Entity> Updates { get; set; }
}
Which I wish to serialize into XML for passing as a message using MSMQ. The list of type Entity can contain any number of instances of classes that inherit from Entity. For example, there may be:
public Person : Entity { /* ... */ }
public Vehicle : Entity { /* ... */ }
I'm using XmlMessageFormatter, which so far I have defined as:
XmlMessageFormatter _formatter =
new XmlMessageFormatter(new[] { typeof(T) });
Where T in this instance is Intent (as above).
Trouble is, when the code actually attempts to serialize the following exception occurs:
The type CoreApi.Domain.Person was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.
I believe this is because I need to tell the serializer somehow of the fact that Person is a child class of entity.
I've seen solutions that basically entail adorning Entity with multiple XmlInclude decorations, which in my scenario is unworkable as the list of inheritors of Entity is large and could grow - I don't want to constantly update this list as new inheritors are added.
I've seen other solutions that use XmlSerializer, passing in a list of known types, the trouble with this is that I somehow need to replace XmlMessageSerialiser with the XmlSerialiser instance which isn't compatible.

Why is my Entity Framework Code First proxy collection null and why can't I set it?

I am using DBContext and have two classes whose properties are all virtual. I can see in the debugger that I am getting a proxy object when I query the context. However, a collection property is still null when I try to add to it. I thought that the proxy would ensure that collection is initialized.
Because my Poco object can be used outside of its data context, I added a check for the collection being null in the constructor and create it if necessary:
public class DanceStyle
{
public DanceStyle()
{
if (DanceEvents == null)
{
DanceEvents = new Collection<DanceEvent>();
}
}
...
public virtual ICollection<DanceEvent> DanceEvents { get; set; }
}
That works outside the data context but if I retrieve an object using a query, although the test is true, when I try to set it, I get following exception: 'The property 'DanceEvents' on type 'DanceStyle_B6089AE40D178593955F1328A70EAA3D8F0F01DDE9F9FBD615F60A34F9178B94' cannot be set because the collection is already set to an EntityCollection.'
I can see it is null and I cannot add to it, but neither can I set it to a collection because the proxy says it is already set. Therefore I cannot use it. I'm confused.
Here is the DanceEvent class:
public class DanceEvent
{
public DanceEvent()
{
if (DanceStyles == null)
{
DanceStyles = new Collection<DanceStyle>();
}
}
...
public virtual ICollection<DanceStyle> DanceStyles { get; set; }
}
I have omitted the other value-type properties from the code above. I have no other mappings for those classes in the context class.
As you correctly observed in the answer to your own question, removing the "virtual" keyword from the collection properties works around the problem, by preventing the Entity Framework from creating a change tracking proxy. However, this is not a solution for many people, because change tracking proxies can be really convenient and can help prevent issues when you forget to detect changes at the right places in your code.
A better approach would be to modify your POCO classes, so that they instantiate the collection properties in their get accessor, rather than in the constructor. Here's your POCO class, modified to allow change tracking proxy creation:
public class DanceEvent
{
private ICollection<DanceStyle> _danceStyles;
public virtual ICollection<DanceStyle> DanceStyles
{
get { return _danceStyles ?? (_danceStyles = new Collection<DanceStyle>()); }
protected set { _danceStyles = value; }
}
}
In the above code the collection property is no longer automatic, but rather has a backing field. It's better if you leave the setter protected, preventing any code (other than the proxy) from subsequently modifying these properties. You will notice that the constructor was no longer necessary and was removed.
I found the solution to this problem here: Code First adding to collections? How to use Code First with repositories?
I removed 'virtual' from all properties except collections and lazy loaded objects, that is, all native types.
But I still don't understand how you can end up with the situation where you have a null collection that you cannot use and have no way to set it to a valid collection.
I also found this answer from Rowan Miller on an MSDN forum
Hi,
If you make all your properties virtual then EF will generate proxy classes at runtime that derives from your POCO classed, these proxies allow EF to find out about changes in real time rather than having to capture the original values of your object and then scan for changes when you save (this is obviously has performance and memory usage benefits but the difference will be negligible unless you have a large number of entities loaded into memory). These are known as 'change tracking proxies', if you make your navigation properties virtual then a proxy is still generated but it is much simpler and just includes some logic to perform lazy loading when you access a navigation property.
Because your original code was generating change tracking proxies, EF was replacing your collection property with a special collection type to help it find out about changes. Because you try and set the collection back to a simple list in the constructor you are getting the exception.
Unless you are seeing performance issues I would follow Terrence's suggestion and just remove 'virtual' from your non-navigation properties.
~Rowan
So it appears that I only have the problem with a full 'change tracking proxy' if all my properties are virtual. But given that, why can I still not use the virtual property on the change tracking proxy? This code blows up on line three because ds2.DanceEvents is null and cannot be set in the constructor:
DanceStyle ds2 = ctx.DanceStyles.Where(ds => ds.DanceStyleId == 1).Single();
DanceEvent evt = CreateDanceEvent();
ds2.DanceEvents.Add(evt);
I'm still confused, even though my code is now working because of the fix above.
Old question...
Poco class:
public partial class MyPOCO
{
public MyPOCO()
{
this.MyPocoSub = new HashSet<MyPocoSub>();
}
//VIRTUAL
public virtual ICollection<MyPocoSub> MyPocoSub { get; set; }
}
and proxy code:
public override ICollection<MyPocoSubSet> MyPocoSubSets
{
get
{
ICollection<MyPocoSubSet> myPocoSubSets = base.MyPocoSubSets;
if (!this.ef_proxy_interceptorForMyPocoSubSets(this, myPocoSubSets))
{
return base.MyPocoSubSets;
}
return myPocoSubSets;
}
set
{
if (value != this.RelationshipManager.GetRelatedEnd("WindowsFormsApplication.Models.MyPocoSubSet_MyPOCO", "MyPocoSubSet_MyPOCO_Source"))
{
// EXCEPTION
throw new InvalidOperationException("The property 'MyPocoSubSets' on type 'MyPOCO_A78FCE6C6A890855C68B368B750864E3136B589F9023C7B1D90BF7C83FD291AC' cannot be set because the collection is already set to an EntityCollection.");
}
base.MyPocoSubSets = value;
}
}
As you can see that exception raised in proxy class in ExtityFramework 5. This means that behavior still exist.

Entity Framework 4 POCO entities in separate assembly, Dynamic Data Website?

Basically I want to use a dynamic data website to maintain data in an EF4 model where the entities are in their own assembly. Model and context are in another assembly.
I tried this Entity Framework 4 + Self-Tracking Entities + ASP.NET Dynamic Data = Error
but get an "ambiguous match" error from reflection:
System.Reflection.AmbiguousMatchException was unhandled by user code
Message=Ambiguous match found.
Source=mscorlib
StackTrace:
at System.RuntimeType.GetPropertyImpl(String name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
at System.Type.GetProperty(String name)
at System.Web.DynamicData.ModelProviders.EFTableProvider..ctor(EFDataModelProvider dataModel, EntitySet entitySet, EntityType entityType, Type entityClrType, Type parentEntityClrType, Type rootEntityClrType, String name)
at System.Web.DynamicData.ModelProviders.EFDataModelProvider.CreateTableProvider(EntitySet entitySet, EntityType entityType)
at System.Web.DynamicData.ModelProviders.EFDataModelProvider..ctor(Object contextInstance, Func1 contextFactory)
at System.Web.DynamicData.ModelProviders.SchemaCreator.CreateDataModel(Object contextInstance, Func1 contextFactory)
at System.Web.DynamicData.MetaModel.RegisterContext(Func`1 contextFactory, ContextConfiguration configuration)
at WebApplication1.Global.RegisterRoutes(RouteCollection routes) in C:\dev\Puffin\Puffin.Prototype.Web\Global.asax.cs:line 42
at WebApplication1.Global.Application_Start(Object sender, EventArgs e) in C:\dev\Puffin\Puffin.Prototype.Web\Global.asax.cs:line 78
InnerException:
I came across a similar problem to this recently. It had to do with inheritance in my model. I had a Resource entity that had derived types of Person, Equipment, etc. and in those I had overridden a couple properties, but by mistake gave them different signatures. I'll describe my scenario and hopefully it will help.
To be able to debug deep enough into the framework, and see all the variable values, you will have to disable optimizations:
Link
I was seeing the Ambiguous Match error when registering the Context in Global.asax as you were:
public static void RegisterRoutes(RouteCollection routes)
{
// IMPORTANT: DATA MODEL REGISTRATION
// Uncomment this line to register an ADO.NET Entity Framework model for ASP.NET Dynamic Data.
// Set ScaffoldAllTables = true only if you are sure that you want all tables in the
// data model to support a scaffold (i.e. templates) view. To control scaffolding for
// individual tables, create a partial class for the table and apply the
// [ScaffoldTable(true)] attribute to the partial class.
// Note: Make sure that you change "YourDataContextType" to the name of the data context
// class in your application.
DefaultModel.RegisterContext(typeof(EntityModelContainer), new ContextConfiguration() { ScaffoldAllTables = true });
Stepping into the RegisterContext method, I got to System.Web.DynamicData.ModelProviders.EFDataModelProvider there is section of code that loads all the Entities in the model by traversing the inheritance hierarchy in the constuctor for EFDataModelProvider.
while (objectStack.Any()) {
EntityType entityType = objectStack.Pop();
if (entityType != null) {
// Update the entity set when we are at another root type (a type without a base type).
if (entityType.BaseType == null) {
currentEntitySet = entitySetLookup[entityType];
}
var table = CreateTableProvider(currentEntitySet, entityType);
tables.Add(table);
}
foreach (EntityType derivedEntityType in derivedTypesLookup[entityType]) {
// Push the derived entity types on the stack
objectStack.Push(derivedEntityType);
}
}
I put a breakpoint in here and was able to see that Ambiguous Match was occurring for me when calling CreateTableProvider on my Equipment entity (which was derived from Resource).
Looking back at the Stack Trace from the original exception (which I should have done in the first place!) I put a breakpoint in the constructor for System.Web.DynamicData.ModelProviders.EFTableProvider.IsPublicProperty and watched to see which property/method/whatever was causing the ambiguous match -- for me this ended up being a navigation property called Resources (Resources are themselves a hierarchy) that I had overridden in Equipment.
private static bool IsPublicProperty(Type entityClrType, string propertyName) {
var property = entityClrType.GetProperty(propertyName);
return property != null && property.GetGetMethod() != null;
}
In the partial class for Equipment, I had:
public partial class Equipment
{
public new IEnumerable<Resource> Resources
{
but in the parent class, Resource, Resources was defined as:
public virtual ICollection<Resource> Resources
{
When these Properties are being loaded by the .GetProperty(propertyName) in IsPublicProperty, they have the same name but different signatures (because I had given them different return type by mistake) so it isn't clear which shoudl be loaded based on name alone. I corrected my mistake and made Resources in my Equipment class return an ICollection, and boom -- no more ambiguous match.
Not sure if this will help or not, but if you step through in a similar way you should be able to find exactly what is causing the ambiguous match.