开发者

How to Create a Run-Time Computed (NotMapped) Value in Entity Framework Code First

开发者 https://www.devze.com 2023-03-08 19:18 出处:网络
I have a user class in EF Code First that contains a lot of properties, and each user has a collection of \"Contacts\" which are other users as a subset of the total user population. The other collect

I have a user class in EF Code First that contains a lot of properties, and each user has a collection of "Contacts" which are other users as a subset of the total user population. The other collection "ContactOfOthers" is just the reverse showing who has this user as a contact as this is a many-to-many relationship.

public class User {

        [Key]
        public string UserName { get; set; }

        // Lots of other properties not relevant to this question...

        [NotMapped]
        public bool IsMyContact { get; set; }

        public virtual ICollection<User> Contacts { get; set; }
        public virtual ICollection<User> ContactOfOthers { get; set; }

}

I introduced a not-mapped (not mapped to DB) property called IsMyContact. This is for cases when the user queries for a bunch of users and I need to show in the View which users are already in their contacts list. So this property should be true if the User is part of their "Contacts" collection. It shouldn't be saved to the DB since it can be different for the same user, depending on the user doing the query.

Is there a nice way to do this in a query from the contex开发者_如何学Pythont? It could of course be brute-forced by doing two queries then iterating through the main one, looking for matches to the user's Contacts collection, but I'm wondering if there's a more elegant way to do this from one query, projecting a run-time computed column onto this property?


I don't know a way how to populate the IsMyContact property in the User directly within the query. But an alternative approach could be to introduce a ViewModel which wraps the User and has in addition the IsMyContact flag:

public class UserViewModel
{
    public bool IsMyContact { get; set; }
    public User User { get; set; }
}

(The class User would not have the IsMyContact flag anymore.)

You could then project into this type when you run your query:

string me = "That's me"; // name of the user who is selecting

List<UserViewModel> list = context.Users
   .Where(u => ...some filter...)
   .Select(u => new UserViewModel
          {
              IsMyContact = u.ContactOfOthers.Any(c => c.UserName == me),
              User = u
          })
   .ToList();

The benefits would be: You need only one round trip and you are not forced to load the whole collection of Contacts to determine the IsMyContactFlag (but you can if you want to).

The drawback: You need this additional ViewModel type.


It is possible to do this but it will be far from a "nice way" because you cannot return instances of your User type. You must write custom linq-to-entities query and you must solve two problems:

  • You cannot project to mapped types in linq-to-entities
  • You cannot access non mapped properties in linq-to-entities

So my high level untested idea about doing this is:

var query = from u in ctx.Users
            where u.Id != id // don't include current user - you can add other condition
            join c in ctx.Users
                         .Where(x => x.Id == id) // current user
                         .SelectMany(x => x.Contacts)
                on u.Id equals c.Id into leftJoin
            from y in leftJoin.DefaultIfEmpty()
            select new 
                {
                    UserName = u.UserName,
                    IsMyContact = y != null
                };

This should be a query which will load pairs of UserName and information if the user is contact or not. If you want User instance instead you must do something like this:

var users = query.AsEnumerable()
                 .Select(new User
                      {
                          // Project to list in linq-to-objects
                      });
0

精彩评论

暂无评论...
验证码 换一张
取 消