2

I'm trying a model-first approach on an existing database. I cannot alter the database scheme in any way.

This database has an interesting set of relationships: User has a one-to-many relationship with Address. However, address has an 'IsActive' field and for any given user there is only one address that is active.

I'm looking for a way so make entity framework understand this, so that developers have the relationship in their model as if it were a one-to-zero/one relationship: user.Address.Line1 .

I need a real navigation property here. I am not just trying to avoid having to repeat user.Addresses.SingleOrDefault(c => c.IsActive).Line1 every time. Real navigation properties allow for more complex scenarios, like lazy and or eager loading from the database, or filtering: usaCust = customers.Where(c => c.Address.Country == "USA");.

  • Sounds unlikely-especially when you cannot change the database design, but I'd like to know if any approach can provide the functionality too. – Tolga Evcimen Nov 20 '15 at 13:03
  • 1
    I know that cannot alter the table schema, but can you create an updatable View? That could guarantee the 1-0/1 relationship and map a navigation property to it. – E-Bat Nov 20 '15 at 20:12
  • E-Bat that is a great idea! In my case though, I can't touch the db, and even if I could, some of the scenarios are move complex (like, there is no IsActive property but a Start and End date column, so I'd have to pass the date as a parameter to the view). – Jan Van der Haegen Dec 09 '15 at 00:53

4 Answers4

1

You could simply add an ActiveAddress property to your entity and use it to get/set the appropriate address as active.

This will effectively give each user zero or one ActiveAddresses, which can be set to a member of the Addresses collection, or set to null. Only one Address will be active at a time.

public class User
{
    [Key]
    public int ID { get; set; }

    public virtual List<Address> Addresses { get; set; }

    [NotMapped]
    public Address ActiveAddress
    {
        get { return Addresses == null ? null : Addresses.Where(t => t.IsActive).FirstOrDefault(); }
        set
        {
            if (Addresses != null)
            {
                //set all addresses inactive
                Addresses.ForEach(t => t.IsActive = false); 
                //set specified address as active if not null                
                if(value != null)
                    value.IsActive = true;
            }
        }
    }
}

public class Address
{
    [Key]
    public int ID { get; set; }

    public string FullAddress { get; set; }

    public bool IsActive { get; set; }

    public int UserID { get; set; }

    [ForeignKey("UserID")]
    public virtual User User { get; set; }
}

To query this, you would need to enumerate and use Linq to Objects before referencing your ActiveAddress field, since it does not exist in the db. If you have a large number of records in your customers and/or addresses table this may not be feasible.

var usaCustomer = c.Customers.ToList().Where(t => t.ActiveAddress.Country == "USA").FirstOrDefault();

To use Linq to Entities, the only way I can currently think of is to query your collection through the address dbset, which is backwards from what you would like to do, but will still execute on the db side.

var usaCustomer = c.Customers.Where(t => t.Addresses.Any(a => a.IsActive && a.Country == "USA")).FirstOrDefault();
Brino
  • 2,442
  • 1
  • 21
  • 36
  • Hey Brino! Thank you for your answer using a NotMapped property. I'm sure it will be the answer for a lot of people with similar use cases. In my use case though, I really need a navigation property, something that "EF understands", so that I can do things like eagerly/lazy loading it, or filtering: `usaCust = customers.Where(c => c.Address.Country == "USA");` – Jan Van der Haegen Nov 20 '15 at 18:39
  • ActiveAddress property is just a member of the Addresses collection, which is a navigation property of your User entity.The addresses collection is filterable, and supports lazy loading. – Brino Nov 20 '15 at 18:50
  • True, but not eager loading. Also, it is filterable, but only using LinqToObjects (IEnumerable), not LinqToEntities (IQueryable). As the property is NotMapped, EF cannot do the filter in the database. Instead, you're getting all 3 million users from the DB, then per user (3 million times) you will get the address, then you filter and are left with the 200k in the USA. – Jan Van der Haegen Nov 20 '15 at 19:09
  • I understand, let me rethink this – Brino Nov 20 '15 at 19:27
  • 1
    @Jan For filtering on the database side you can try a model defined function such as explained [here](https://msdn.microsoft.com/en-us/library/dd456845(v=vs.100).aspx) – Canica Nov 20 '15 at 22:03
0

You intend to have a semantic validation for addresses. You could implement a method which expects a DateTime, and return the result of SingleOrDefault for the given query, like this:

public String getFirstLine(DateTime date) {
    return this.Addresses.SingleOrDefault(c => c.FromDate < date && c.ToDate > date).Line1
}

You will be able to use it, like this:

user.getFirstLine(DateTime.Now)

Note, that the method supports other dates as well, if you happen to intend to test for other dates as well.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • Thanks Lajos, but I really need a relationship property, not a function. The function lacks the ability to eagerly include the navigation property unless I eagerly load all addresses. – Jan Van der Haegen Nov 20 '15 at 14:12
  • Note: I simplified the example after this answer to use 'IsActive' vs dates. – Jan Van der Haegen Nov 20 '15 at 14:29
  • Then you can define a predicate and use it repeatedly. – Lajos Arpad Nov 20 '15 at 15:19
  • How so? Could you give an example? – Jan Van der Haegen Nov 20 '15 at 15:48
  • There are many examples. Look here for example: http://stackoverflow.com/questions/19616711/howto-use-predicates-in-linq-to-entities-for-entity-framework-objects. Or look at the documentation here: https://msdn.microsoft.com/en-us/library/bfcke1bz%28v=vs.110%29.aspx. – Lajos Arpad Nov 20 '15 at 16:31
  • Based on the links, I'm assuming you're suggesting to replace `user.Addresses.SingleOrDefault(c => c.IsActive).Line1` with `user.Addresses.SingleOrDefault(Address.IsActivePredicate).Line1`? – Jan Van der Haegen Nov 20 '15 at 17:23
  • I am saying that you can have a predicate called p which can be repeatedly passed to SingleOrDefault instead of duplicating the code. – Lajos Arpad Nov 20 '15 at 17:26
  • Thanks for confirming my assumption on what you meant, Lajos. However your "Then you can define a predicate and use it repeatedly." in no way helps with my " The function lacks the ability to eagerly include the navigation property unless I eagerly load all addresses. ", does it? – Jan Van der Haegen Nov 20 '15 at 17:36
  • That is correct. SingleOrDefault runs the query under the hood. You can modify it to get an IEnumerable of addresses which had some predicates on its own, running this part at the end. Or, you can exclude SingleOrDefault as well, so this method will not force SQL execution. – Lajos Arpad Nov 20 '15 at 17:39
  • Lajos I appreciate your time. – Jan Van der Haegen Nov 20 '15 at 18:36
0

Jan, Try to use Dynamic Global Filters for Entity Framework

https://github.com/jcachat/EntityFramework.DynamicFilters

  • Thanks Norman! I did search in this direction, but my research tells me it can help bring down the number of child records from N to 1, but it cannot help me actually turn the parent->N childs relationship into a parent -> 1 child relationship. :( – Jan Van der Haegen Dec 09 '15 at 00:56
0

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...