简体   繁体   中英

Casting an IEnumerable<TEntity> to an IEnumerable<TResult> while ensuring deferred execution

In my application there are a fair number of existing "service commands" which generally return a List<TEntity> . However, I wrote them in such a way that any queries would not be evaluated until the very last statement, when they are cast ToList<TEntity> (or at least I think I did).

Now I need to start obtaining some "context-specific" information from the commands, and I am thinking about doing the following:

  1. Keep existing commands largely the same as they are today, but make sure they return an IEnumerable<TEntity> rather than an IList<TEntity> .
  2. Create new commands that call the old commands but return IEnumerable<TResult> where TResult is not an entity but rather a view model, result model, etc - some representation of the data that is useful for the application.

The first case in which I have needed this is while doing a search for a Group entity. In my schema, Group s come with User -specific permissions, but it is not realistic for me to spit out the entire list of users and permissions in my result - first, because there could be many users, second, because there are many permissions, and third, because that information should not be available to insufficiently-privileged users (ie a "guest" should not be able to see what a "member" can do).

So, I want to be able to take the result of the original command, an IEnumerable<Group> , and describe how each Group ought to be transformed into a GroupResult , given a specific input of User (by Username in this case).

If I try to iterate over the result of the original command with ForEach I know this will force the execution of the result and therefore potentially result in a needlessly longer execution time. What if I wanted to further compose the result of the "new" command (that returns GroupResult ) and filter out certain groups? Then maybe I would be calculating a ton of privileges for the inputted user, only to filter out the parent GroupResult objects later on anyway!

I guess my question boils down to... how do I tell C# how I'd like to transform each member of the IEnumerable without necessarily doing it at the time the method is run?

To lazily cast an enumerable from one type to another you do this:

IEnumerable<TResult> result = source.Cast<TResult>();

This assumes that the elements of the source enumerable can be cast to TResult . If they can't you need to use a standard projection with .Select(x => ... ) .

Also, be careful returning IEnumerable<T> from a service or database as often there are resources that you need to open to obtain the data so now you would need make sure those resources are open whenever you try to evaluate the enumerable. Keeping a database connection open is a bad idea. I would be more inclined to return an array that you've cast as an IEnumerable<> .

However, if you really want to get an IEnmerable<> from a service or database that is truly lazy and will automatically refresh the data then you need to try Microsoft's Reactive Framework Team's "Interactive Extensions" to help with it.

They have an nice IEnumerable<> extension called Using that makes a "hot" enumerable that opens a resource for each iteration.

It would look something like this:

var d =
    EnumerableEx
        .Using(
            () => new DB(),
            db => db.Data.Where(x => x == 2));

It creates a new DB instance every time the enumerable is iterated and will dispose of the database when the enumerable is completed. Something worth considering.

Use NuGet and look for "Ix-Main" for the Interactive Extensions.

You're looking for the yield return command.

When you define a method returning an IEnumerable , and return its data by yield return , the return value is iterated over in the consuming method. This is what it could look like:

IEnumerable<GroupResult> GetGroups(string userName)
{
    foreach(var group in context.Groups.Where(g => <some user-specific condition>))
    {
        var result = new GroupResult()
        ... // Further compose the result.

        yield return result;

    }
}

In consuming code:

var groups = GetGroups("tacos");

// At this point no eumeration has occurred yet. Any breakpoints in GetGroups
// have not been hit.

foreach(var g in groups)
{
    // Now iteration in GetGroups starts!
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM