Thanks for all the discussing and proposed solutions. They were really helpful and will probably work for 90% of people that have a similar issue.
If you can alter the database and do not need to pass in parameters, E-Bat suggested making proper updatable views with proper relationships and using those instead.
If you don't need eager loading, create a [NonMapped] computed property or helper method.
We ended up with a different solution, I cannot post all technical details of our eventual solution as it's about 1k code lines long...
Anyways, we really wanted to model the 1-many relationship as a 1-1 (for a given time) in a way so that we could eagerly load the relationship, lazy load it, or use it in a database query (filter on CurrentAddress etc).
First, we created a property on User of type Address, and added the 'NotMapped' attribute:
public class User{
public string Name { get; set; } //simple property
public virtual ICollection<Address> Addresses { get; set;} //normal navigation collection
[NotMapped] public virtual Address CurrentAddress { get; set;} //We added this
}
Then, we had a look at how you would normally get this address property filled in:
var users = from user in dbo.Users
join address in dbo.Addresses.Where(a => a.IsActive)
on user.Id equals address.UserId
select new User{
Name = user.Name
Addresses = user.Addresses
CurrentAddress = address
};
We made a fluent API so we can define this join just once in a way similar to how you'd use EF fluent API to define normal relationships against the ModelBuilder.
this.Entity<User>()
.HasJITRelationship(u => u.CurrentAddress)
.From(dbo => dbo.Addresses
.Where(a => a.IsActive)
.On(u => u.Id)
.Equals(a => a.UserId);
Lastly, suppose we have:
var usaUsers = dbo.Users.Where(c => c.CurrentAddress.Country == "USA");
foreach(var user in usaUsers.ToList()) { //...
This IQueryable is executed only when the ToList() is called. At that point, the expression tree of the usaUsers IQueryable, contains the info that the CurrentAddress join is needed.
We actually made our DBContext a bit smarter. Instead of dbo.Users returning a normal DBSet(), we're actually returning a DBSet with some injected logic. Based on this particular usage (usa-users example), we investigate the expression tree and swap out (at runtime) the normal use of dbo.Users, for a new IQueryable where we inject the 'join', using an ExpressionVisitor.
Basically, since we defined the join once using the fluent API, and we know the usage (IQueryable.Expression investigation), we can inject the join 'Just-In-Time' on a case-by-case bases.
I'm leaving out about 900 other lines of technical details...