Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a matches utility or document EntityComparator #2392

Open
parisholley opened this issue Nov 10, 2021 · 4 comments
Open

Provide a matches utility or document EntityComparator #2392

parisholley opened this issue Nov 10, 2021 · 4 comments
Labels
enhancement New feature or request

Comments

@parisholley
Copy link
Contributor

Is your feature request related to a problem? Please describe.

I often have service methods which accept partials (eg: EntityData<Entity> or Partial<Entity>) where it may then filter a collection of those entities and run some logic. At the moment, there is no way to use the logic similar to assign (or match?), where it can auto-correct between references, ids, and whole objects. To manually build a collection.filter() command which could adequately support reading EntityData would be very verbose and may not align with how Mikro matches internally.

Describe the solution you'd like

function (em:EntityManager, criteria:EntityData<Entity>, entities:Collection<Entity>){
  // in many cases we already have a full list of entities, so don't want to use the entities.match() API 
  const filtered = entities.filter(criteria);
}

if not using a collection (say an array or other JS collection type), EM could contain a filter method

function filter(criteria:EntityData<Entity>, entities: Iterable <Entity>);
@parisholley parisholley added the enhancement New feature or request label Nov 10, 2021
@B4nan
Copy link
Member

B4nan commented Nov 10, 2021

I don't really understand what you are asking for here, could you provide more verbose example? Like runtime filtering that would work the same way as SQL where query? I am not interested in doing that, it's the job of database, not ORM.

I also don't understand the reference to EntityComparator, but to use it:

// given two User entities, u1 and u2
const comparator = em.getComparator();
const e1 = comparator.prepareEntity(u1);
const e2 = comparator.prepareEntity(u2);
const diff = comparator.diffEntities('User', e1, e2);

@parisholley
Copy link
Contributor Author

parisholley commented Nov 10, 2021

Use Case: I have a invoice modification service, where I pass in a partial invoice line and I want the service to either:

  • If the line amount = 0 and criteria matches an existing line, remove line, otherwise no-op
  • If criteria matches an existing line, update it
  • If criteria does not match an existing line, create a new one

The above is normally named something in the service layer like, createOrUpdateFoobar or ensureFoobar, a common pattern throughout.

Right now, I have to write this kind of function to support this:

      for (const [key, value] of Object.entries(criteria)) {
        if (key === 'description') {
          continue;
        }

        const references = ['action', 'event', 'jobField', 'field', 'provider', 'job', 'invoice'];

        if (references.includes(key)) {
          if (value === null) {
            if (line[key]) {
              return false;
            }
          } else {
            if (line[key] === null || line[key] === undefined) {
              return false;
            }

            if (typeof value === 'string') {
              if (line[key].id !== value) {
                return false;
              }
            } else if (value instanceof Reference) {
              if (value['id'] !== line[key].id) {
                return false;
              }
            } else if (value !== line[key].get()) {
              return false;
            }
          }
        } else {
          if (value === null && !!line[key]) {
            return false;
          }

          if (value != null && line[key] !== value) {
            return false;
          }
        }
      }

That logic above is effectively similar to how manager.assign() works (i believe), and in the event that EntityData capabilities expand in the future, I don't want to have to rewrite this method everywhere. Granted, I can probably generalize it and allow the developer to pass in a list of properties that are known to be references, but that is also subject to error and can be safer through the EM since it has all the metadata.

@B4nan
Copy link
Member

B4nan commented Nov 10, 2021

assign does not work this way, it does not compare things, just sets new values based on metadata (it's of course a bit more complicated, but the point is I don't think similar code exists in EntityAssigner). EntityComparator does, and you can use it as I described above.

@B4nan
Copy link
Member

B4nan commented Oct 25, 2023

FYI the comparator has nowadays a matching method.

https://github.com/mikro-orm/mikro-orm/blob/master/packages/core/src/utils/EntityComparator.ts#L43

The downside of using this is that it is built for comparing the database values, so the entity snapshots - the main difference being the custom types, which are handled on hydration. I guess we could introduce a comparator for runtime values (so for entity instances instead of snapshots) too without too much effort.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants