1

Let's say I have a User class that's partially populated via an ORM, but there are some pieces of data that are logically related to this entity that are provided from elsewhere.

public class User {
  public int Id { get; set; }

  public int ServiceId { get; set; }

  public string FirstName => null; //this comes from somewhere else

  public ICollection<Role> Roles { get; set; }
  //etc...
}

Previously, in cases similar to this, I've been allowing a single interface to be injected into the domain model by the IOC container to handle this.

public class User {
  public readonly IUserBehavior _userBehavior;

  public User() {}
  public User(IUserBehavior userBehavior) {
    _userBehavior = userBehavior;
  }

  public int Id { get; set; }

  public int ServiceId { get; set; }

  public string FirstName => _behavior?.getFirstName(this);

  public ICollection<Role> Roles { get; set; }
  //etc...
}

I recently, however, tried switching from nHibernate to Entity Framework, which looks to make this impossible (at least, in so far as using constructor injection).

My options right now seem to be to move the service call (implementation details) to obtain the missing data directly into the entity or use a double dispatch pattern that seems rather off... Certain data properties of the type would have to be called in a special way. Were this performing an action to change state, I could see double dispatch making more sense. Alternatively, I suppose I could go back to nHibernate.

I guess what I'm asking is are any of my options actually "good", or is there some other option that I should consider?

Ixonal
  • 616
  • 8
  • 18
  • Duplicate of https://stackoverflow.com/questions/28715966/entity-framework-object-materialization-and-dependency-injection? – Steven Feb 13 '17 at 18:52
  • @Steven Saw that question before I wrote this one. Seems to be suggesting double dispatch, which as I stated, feels weird for my use case. – Ixonal Feb 13 '17 at 19:05
  • I don't see how this related to double dispatch, except when you start publishing events, which seems unrelated to your question. – Steven Feb 13 '17 at 19:21

2 Answers2

1

As explained by multiple people here, here and here, using constructor injection to inject dependencies into entities is not a good idea.

Instead, you're much better of using method injection. Example:

public class User {
  public int Id { get; set; }
  public int ServiceId { get; set; }
  public ICollection<Role> Roles { get; set; }

  public string GetFirstName(IUserBehavior behavior) => behavior.GetFirstName(this);

  //etc...
}

Here each method on the entity defines its own set of dependencies, which are injected using method injection. This makes methods on the entities very testable, and prevents the entity from needing to be built up using a container.

Instead, the consuming service will get the required dependencies injected into its constructor and it will pass those dependencies on to the methods it calls.

In this case however, the behavior of the GetFirstName() method is so simple that you are probably better of by letting the service call behavior.GetFirstName(User) itself.

Steven
  • 166,672
  • 24
  • 332
  • 435
0

Use decorator to provide the necessary functionality: https://en.wikipedia.org/wiki/Decorator_pattern

So from your business layer (or wherever you need to access User class, you would call decorator class that would first call EF and then use your custom process to supply FirstName and such.

Renats Stozkovs
  • 2,549
  • 10
  • 22
  • 26
  • That's not the Decorator pattern, since a decorator is a class that implements the same abstraction as it wraps. What you are describing is a Builder or a Pipeline. – Steven Feb 13 '17 at 21:58
  • @Steven So I was thinking in terms of extending original data class with Decorator, calling original function to retrieve data from Decorator and then calling a private method within Decorator class to retrieve additional data. That sounds like a decorator pattern to me. Builder of pipeline would require me to call both Decorator and original data access class from a consumer. Here, I'd only call the Decorator from the consumer – Renats Stozkovs Feb 13 '17 at 22:10