dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.65k stars 3.15k forks source link

FromSql: Support multiple resultsets #8127

Open Eilon opened 7 years ago

Eilon commented 7 years ago

From @rpundlik on March 22, 2017 12:9

While retrieving the results using stored procedure how can I retrieve and store multiple result set in view model in .net core

For e.g. from stored procedure I am returning records for below queries

Select * LMS_Survey
Select * from LMS_SurveyQuestion
Select * from LMS_SurveyQuestionOptionChoice

and below is view model

public class LMS_SurveyTraineeViewModel
{
    public LMS_SurveyDetailsViewModel SurveyDetailsViewModel { get; set; }
    public LMS_SurveyQuestionsViewModel SurveyQuestionsViewModel { get; set; }
    public LMS_SurveyQuestionOptionChoiceViewModel SurveyQuestionOptionChoiceViewModel { get; set; }
}

This is how I am executing the stored procedure

public List<LMS_SurveyTraineeViewModel> GetTraineeSurvey(int surveyID)
        {
            try
            {
                List<LMS_SurveyTraineeViewModel> modelList = new List<LMS_SurveyTraineeViewModel>();

                modelList = dbcontext.Set<LMS_SurveyTraineeViewModel>().FromSql("LMSSP_GetTraineeSurvey @surveyID = {0},@LanguageID = {1}", surveyID, AppTenant.SelectedLanguageID).ToList();

                return modelList;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

How can stored the multiple result set using stored procedure in view model ?

Right now I need to call the three separate stored procedures to get data and merging them into one view model

Thanks for the help !

Copied from original issue: aspnet/Mvc#6011

Eilon commented 7 years ago

I think this question is more about retrieving multiple result sets from a sproc, and less so about MVC view models.

Ricky-G commented 7 years ago

@Eilon it looks there was an issue raised in the past #6026 but it was closed for some reason. Is there any current plan to include this feature in the 2.0 milestone.

Eilon commented 7 years ago

@RickyG-Akl , the friendly folks in this repo will give you an answer 😄

ajcvickers commented 7 years ago

@rpundlik @RickyG-Akl There isn't currently any support for this when using EF and FromSql. The workaround is to drop down to ADO.NET and use the DbConnection directly to run the query and handle the multiple resultsets. Putting this issue on the backlog so that we can consider adding support in the future.

ajayvikas commented 7 years ago

@ajcvickers In EF6 we have ObjectContext.Translate api, which we could use in this kind of scenario. There it was possible to materialize SqlDataReader into the EF entity. However this is missing in EF Core 2.0. The issue has been raised in #4675. We use this feature extensively and without this we won't be able to migrate from EF6 to EF Core.

divega commented 7 years ago

@ppn2, I am curious about how you are using Translate(). Can you confirm if you need to have readers with multiple results?

ajayvikas commented 7 years ago

Generally when the entities are simple for example order, orderdetails etc kind, we don't need to use Translate. The include() method works just fine. There are many cases where we need to return a complex object graph from the stored procedure. Generally this stored procedure will have complex business rules and will return multiple resultsets (in our case about 15-20 resultsets). In these cases we run the ExecuteReader command and load SqlDataReader and using translate apis to dematerialize the entities one by one.

ajayvikas commented 6 years ago

@ajcvickers, @divega By looking at past responses and the source code, we came up with the following procedure which is equivalent to the EF6 ObjectContext.Translate (We could extend it to include the merge options, similar to EF6 Translate provides). We have tested this code and seems to be working. We do not have deep knowledge of he source code, so our confidence level is not very high. Is it possible for somebody with deeper knowledge to review this code? This will really help us to move forward with the migration to EF Core 2.0.

        public static List<T> Translate<T>(this DbSet<T> set, DbDataReader reader) where T : class
        {
            var entityList = new List<T>();
            if (reader == null || reader.HasRows == false) return entityList;
            var entityType = set.GetService<IModel>().FindEntityType(typeof(T));
            var valueBufferParameter = Expression.Parameter(typeof(ValueBuffer));
            var entityMaterializerSource = set.GetService<IEntityMaterializerSource>();
            var valueBufferFactory = set.GetService<IRelationalValueBufferFactoryFactory>().Create(new[] { typeof(T) }, null);
            var stateManager = set.GetService<IStateManager>() as StateManager;
            Func<ValueBuffer, T> materializer = Expression.Lambda<Func<ValueBuffer, T>>(
                    entityMaterializerSource.CreateMaterializeExpression(entityType, valueBufferParameter), valueBufferParameter)
                .Compile();
            stateManager.BeginTrackingQuery();
            while (reader.Read())
            {
                ValueBuffer valueBuffer = valueBufferFactory.Create(reader);
                var entity = materializer.Invoke(valueBuffer);
                var entry = stateManager.StartTrackingFromQuery(entityType, entity, valueBuffer, null);
                entityList.Add((T)entry.Entity);
            }
            return entityList;
        }

The way to use this code will be

            using (var ctx = new ApplicationDbContext())
            {
                using (var cnn = ctx.Database.GetDbConnection())
                {
                    var cmm = cnn.CreateCommand();
                    cmm.CommandType = System.Data.CommandType.Text;
                    cmm.CommandText = "SELECT AccountId, AccountBalance, AccountName FROM Accounts; SELECT CustomerId, AccountId, CustomerName FROM Customers";
                    cmm.Connection = cnn;
                    cnn.Open();
                    using (var reader = cmm.ExecuteReader())
                    {
                        var accounts = ctx.Accounts.Translate(reader);
                        reader.NextResult();
                        var customers = ctx.Customers.Translate(reader);
                    }
                }
            }
ErikEJ commented 6 years ago

@sjh37 FYI!

sjh37 commented 6 years ago

Multiple result sets from a sproc are supported via the Entity Framework Reverse Poco Generator. This generates code for EF6, which you could copy and put into your EF.Core project. Download at https://marketplace.visualstudio.com/items?itemName=SimonHughes.EntityFrameworkReversePOCOGenerator

ErikEJ commented 6 years ago

@sjh37 Notice that ObjectContext is not present in EF Core

ajcvickers commented 6 years ago

@ppn2 We looked through your code in triage and we think it should be okay for simple cases. Some things to keep in mind:

Overall, if you are in full control of the types, the queries, and the state of the context, then this should work, but as a general solution it will fall down in some places.

srini1978 commented 6 years ago

@sjh37 . On your comment that we can use the multiple resultsets from stored proceudure in an EF core project, did you mean that as part of that hack :

  1. We can add the EF6 nuget to the EF core project and generate the context
  2. USe the generated context from reverse poco instead of the Context from Core project?
sjh37 commented 6 years ago

@srini1978 I thought it would of been a simple task to generate the code using the generator, then copy n paste the stored proc and result set class to your ef core db context. However I didn't realise at the time that ObjectContext is not present in EF Core.

anildbest83 commented 6 years ago

Is there any update on this? can I use EF Core and FromSql to get multiple result set using stored procedure in model?

ajcvickers commented 6 years ago

@anildbest83 This issue is in the Backlog milestone. We haven't done any work on it yet and that it is currently not planned for for the 2.2 or 3.0 release. We will re-assess the backlog following the 3.0 release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources.

spudcud commented 6 years ago

This is a big deal for us because we have very complex load SPs that do a lot of calculating/coalescing to figure out what records need to be included. This work can only be done once and we can return from 10-30 tables in one load SP. I attempted the code written by @ppn2 above but it did not work in the latest EF version (not overly surprised considering the Internals warning). I attempted to take a different approach to load and I was able to get it to work by simply taking data (ADO.NET) from the database and manually creating records and then attaching them to the context. This worked fine and would potentially work for us since we code gen a lot so we would know what tables are where with what columns so code gening this load logic wouldn't be terrible even though it is not ideal as EF should have this capability built in. I have no idea what the performance cost is for this as it wires up all relationship on attach. I do batch AttachRange hoping that would be better but I don't know. Once the context was loaded all entity access would then be done using the Local cache only.

Obviously this is not a generic solution that will work for any entity type automatically but at least on the surface seems like a possibility. Wanted to share to hopfully up priority on this issue as well as get any feedback on this approach.

anildbest83 commented 6 years ago

Somehow I managed to tackle my situation using the code written by @ppn2, thanks

spudcud commented 6 years ago

@anildbest83 What did you do to get it to work? Can you post a snippet?

anildbest83 commented 6 years ago

Something like this

Used SqlHerper.cs class (attached)

SqlHerpler.txt

and cosnume it like this

           `_context.LoadStoredProc("get_user_by_lastupdateddate]")
                .WithSqlParam("@lastupdatedfrom", lastUpdatedFrom)
                .WithSqlParam("@startwithuserid", startWithUserId)
                .ExecuteStoredProc((handler) =>
               {
                      usersSql.AddRange( handler.ReadToList<Users>());
                      handler.NextResult();

                      usersSql.AddRange(handler.ReadToList<Users2>());
                      handler.NextResult();

                      usersSql.AddRange( handler.ReadToList<Users3>());
                      handler.NextResult();
                });`
mscappini commented 5 years ago

I was wondering if anyone had managed to wrangle a decent workaround for this in EF Core 2.1. I've been attempting to dig into the internals of EF Core to figure out how to materialize and track an entity using a DbDataReader, but I get lost pretty quickly with what's going on.

As @spudcud pointed out, it looks like the workaround provided by @ppn2 no longer works for EF Core 2.1. I also attempted to hook up a workaround from #4675 by @jnm2, but see an identical problem there, too.

The crux of it is that I receive an exception System.InvalidOperationException: 'No mapping to a relational type can be found for the CLR type 'MyType'.' when attempting to call Create(new[] { typeof(TEntity) }, null) on the resolved IRelationalValueBufferFactoryFactory service resolved from my DbSet<TEntity>. I assume there used to be mappings for the DbContext types in that service (or its dependencies), but that may no longer be the case.

I guess two real questions holding me up are:

  1. Is there a way to resolve a service or create a type IRelationalValueBufferFactory from CLR type to be used for the Create function on the IRelationalValueBufferFactoryFactory implementation?
  2. Once that IRelationalValueBufferFactory is created/resolved, how can I avoid using the obsolete IRelationalValueBufferFactory.Create(valueTypes, indexMap) function in favor of the IRelationalValueBufferFactory.Create(types) function?

We have a rather large solution that I am working to convert, and this is one of the last hurdles preventing me from completely migrating to EF Core. Any guidance is greatly appreciated. Thanks!

WellspringCS commented 5 years ago

Crickets chirping...

Same here, @mscappini. It never occurred to me that a mature product like EF Core 2.x would not have any means by which to return a stored procedure returned recordset back to the caller. Moving on to ADO.NET, I guess, per @ajcvickers's guidance.

BTW, why is this issue titled "Support multiple resultsets"? Best I can tell, EF doesn't even really support single resultsets. (I am disregarding solutions such as this that look painful and awkward.)

I very much look forward to the version of EF that not only supports stored procedures, but can import them into projects automatically, as edmx did in the past. Until then, I guess I either live with ancient versions of EF or use some other tool, because I use stored procedures extensively. Am I that unusual?

ajcvickers commented 5 years ago

@WellspringCS This issue is titled "Support multiple resultsets" because that's what it's about. Returning single result sets is supported for query types and entity types..

albertosilva commented 5 years ago

Following this thread because we we're looking for a way to have EF.Core handling SPs returning multiple resultsets, and ended going with Dapper, which supports it nicely, on a ASP.Net Core project.

WellspringCS commented 5 years ago

Apologies if I came off rudely. I was (and am) frustrated, but I never forget that these tools are free. I'm thankful for so much of what Microsoft does.

I said what I said because after a day or two of conversion work, I came down to code like this...


        public virtual int spCopyQuestionColumns(Nullable<System.Guid> fromQuestionGuid, Nullable<System.Guid> toQuestionGuid)
        {
            var fromQuestionGuidParameter = fromQuestionGuid.HasValue ?
                new SqlParameter("FromQuestionGuid", fromQuestionGuid) :
                new SqlParameter("FromQuestionGuid", typeof(System.Guid));

            var toQuestionGuidParameter = toQuestionGuid.HasValue ?
                new SqlParameter("ToQuestionGuid", toQuestionGuid) :
                new SqlParameter("ToQuestionGuid", typeof(System.Guid));

            return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("spCopyQuestionColumns", fromQuestionGuidParameter, toQuestionGuidParameter);
        }

Tons of it... dozens of routines. All used to be autogenerated by EF edmx. A few seconds and hey presto, you're ready to rock.

I naively assumed that by 2.x EF would have a suitable replacement for that feature. No.

OK.... sigh... so.... I'll convert it by hand. But soon enough I find that lines like this...

return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("spCopyQuestionColumns", fromQuestionGuidParameter, toQuestionGuidParameter);

...have no counterpart in Core 2.x. The only solution that I found (this being a single-resultset stored procedure, you'll note) was to do something like this (which I've neither run nor tested yet, because my project doesn't even compile yet.

        public virtual List<int> spUnusedCategories(Nullable<int> surveyId, Nullable<int> interviewId)
        {
            var surveyIdParameter = surveyId.HasValue ?
                new SqlParameter("SurveyId", surveyId) :
                new SqlParameter("SurveyId", typeof(int));

            var interviewIdParameter = interviewId.HasValue ?
                new SqlParameter("InterviewId", interviewId) :
                new SqlParameter("InterviewId", typeof(int));
            this.Database.OpenConnection();
            DbCommand cmd = this.Database.GetDbConnection().CreateCommand();
            cmd.CommandText = "spUnusedCategories";
            cmd.Parameters.Add(surveyIdParameter);
            cmd.Parameters.Add(interviewIdParameter);
            using (DbDataReader dr = cmd.ExecuteReader())
            {
                DataTable tbl = new DataTable();
                tbl.Load(dr);
                DataView dv = new DataView(tbl);
                var enumer = dv.ToTable().AsEnumerable().Select(r => System.Convert.ToInt32(r[0])).ToList();
                return enumer;
            }

So for a couple dozen routines, I find myself painstakingly converting each routine... not even knowing how much debugging and fixing will be needed when I can at least run the code.

If I understood your comment, @ajcvickers, you seem to be referring to solutions for single-resultsets that fall along the lines of this...

var students = context.Students.FromSql("GetStudents @p0","Bill").ToList(); and if the resultset doesn't exactly match an existing entity, I'm outta luck.

I could be revealing ignorance, but that doesn't look like a good solution to me. It feels like a hack.

All that said, I am very new to much of this, I totally accept that I'm may be going about this conversion completely the wrong way. But from what I've seen online... I cannot easily use .NET Core with anything like a large pile of stored procedures... (such as I have!) unless I want to create perfect matching entities for each by hand and then code up magic strings (slight exaggeration) to execute them.

I'm still thankful for all the tools, but I'm thinking I may not be able to use EF in the new world. Not yet. If I can, please just point me to a good link and I'll study the material I find there.

BladeWise commented 5 years ago

@WellspringCS You can use QueryTypes to define the output of your SQL/SP/TVF, so you use an actual Entity if and only if the resultset is an exact match. Otherwise, just declare a QueryType matching the output and you are set.

Regarding multiple active resultsets, it's not that there is no support in EFCore, the fact is that there is no official way to materialize the output of a multiple resultsets query. Moreover, some functionalities (i.e. navigation properties) could not work as expected or at all. All in all, at least in my experience, this is not an issue: when dealing with multiple result sets there is no need for tracking, navigation properties and such things

The following code materializes the contents of a DbDataReader into an IEnumerable<T>

        public static IEnumerable<T> Materialize<T>(this DbContext dbContext, DbDataReader reader)
        {
            var materializerSource = dbContext.GetService<IEntityMaterializerSource>();
            var type = typeof(T);
            var entityType = dbContext.GetService<IModel>()
                                      .FindEntityType(type);
            if (entityType == null)
                throw new InvalidOperationException(string.Format("Unable to find a an entity type (or query type) matching '{0}'", type));
            var materializationAction = materializerSource.GetMaterializer(entityType);
            var relationalTypeMappingSource = dbContext.GetService<IRelationalTypeMappingSource>();
            var columns = Enumerable.Range(0, reader.FieldCount)
                                    .Select(x => new
                                                 {
                                                         Index = x,
                                                         Name = reader.GetName(x)
                                                 })
                                    .ToList();

            var propertiesTypeMaterializationInfo = entityType.GetProperties()
                                                              .Select(x =>
                                                                      {
                                                                          var column = columns.FirstOrDefault(y => string.Equals(y.Name,
                                                                                                                                 x.Relational()
                                                                                                                                  ?.ColumnName ?? x.Name)) ?? throw new InvalidOperationException(string.Format("Unable to find a column mapping property '{0}'.", x.Name));
                                                                          return new TypeMaterializationInfo(x.PropertyInfo.PropertyType, x, relationalTypeMappingSource, column.Index);
                                                                      })
                                                              .ToArray();
            var valueBufferFactory = dbContext.GetService<IRelationalValueBufferFactoryFactory>()
                                              .Create(propertiesTypeMaterializationInfo);

            while (reader.Read())
            {
                var valueBuffer = valueBufferFactory.Create(reader);
                yield return (T)materializationAction(new MaterializationContext(valueBuffer, dbContext));
            }
        }

A possible usage looks like this

            var optionsBuilder = new DbContextOptionsBuilder<TestDbContext>().UseSqlServer("Server=localhost;Database=Playground;Trusted_Connection=True;MultipleActiveResultSets=true");

            using (var dbContext = new TestDbContext(optionsBuilder.Options))
            {
                var connection = dbContext.Database.GetDbConnection();
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = @"SELECT f.*
  FROM (VALUES (1, 'One')
             , (2, 'Two')
             , (3, 'Three')
             , (4, 'Four')
             , (5, 'Five')) f (Id, Name);

SELECT s.*
  FROM (VALUES (1, 'ChildOfOne_1', 1)
             , (2, 'ChildOfOne_2', 1)
             , (3, 'ChildOfThree_1', 3)
             , (4, 'ChildOfFive_1', 5)
             , (5, 'ChildOfFour_1', 4)) s (Id, Name, ParentId);

SELECT t.*
  FROM (VALUES (1, 'ChildOfChildOfOne_1_1', 1)
             , (2, 'ChildOfChildOfOne_2_1', 2)
             , (3, 'ChildOfChildOfThree_1_1', 3)
             , (4, 'ChildOfChildOfFive_1_1', 4)
             , (5, 'ChildOfChildOfFive_1_2', 4)) t (Id, Name, ParentId);";

                    if (connection.State == ConnectionState.Closed)
                        connection.Open();

                    using (var reader = command.ExecuteReader())
                    {
                        var firstEntities = dbContext.Materialize<FirstResultSetEntity>(reader)
                                                     .ToList();
                        reader.NextResult();
                        var secondEntities = dbContext.Materialize<SecondResultSetEntity>(reader)
                                                      .ToList();
                        reader.NextResult();
                        var thirdEntities = dbContext.Materialize<ThirdResultSetEntity>(reader)
                                                     .ToList();
                    }
                }
            }

where resultset types have been defined as Query types in the model

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

        public string Name { get; set; }
    }

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

        public string Name { get; set; }

        public int ParentId { get; set; }
    }

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

        public string Name { get; set; }

        public int ParentId { get; set; }
    }

    public class TestDbContext : DbContext
    {
        public TestDbContext(DbContextOptions<TestDbContext> options)
                : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Query<FirstResultSetEntity>();
            modelBuilder.Query<SecondResultSetEntity>();
            modelBuilder.Query<ThirdResultSetEntity>();
        }
    }

Notice that you can get creative with extensions, and hide the connection/command boilerplate, or even try to create code looking like FromSql, mimicking the SqlParameter naming resolution logic

        public static (IReadOnlyCollection<T1> FirstResultSet, IReadOnlyCollection<T2> SecondResultSet, IReadOnlyCollection<T3> ThirdResultSet) MultiResultSetsFromSql<T1, T2, T3>(this DbContext dbContext, RawSqlString sql, params object[] parameters)
        {
            var resultSetMappingTypes = new[]
                                        {
                                                typeof(T1), typeof(T2), typeof(T3)
                                        };

            var resultSets = new List<object>();

            var connection = dbContext.Database.GetDbConnection();
            var parameterGenerator = dbContext.GetService<IParameterNameGeneratorFactory>()
                                              .Create();
            var parameterBuilder = dbContext.GetService<IRelationalCommandBuilderFactory>()
                                            .Create()
                                            .ParameterBuilder;
            foreach (var parameter in parameters)
            {
                var generatedName = parameterGenerator.GenerateNext();
                if (parameter is DbParameter dbParameter)
                    parameterBuilder.AddRawParameter(generatedName, dbParameter);
                else
                    parameterBuilder.AddParameter(generatedName, generatedName);
            }

            using (var command = connection.CreateCommand())
            {
                command.CommandType = CommandType.Text;
                command.CommandText = sql.Format;
                command.Connection = connection;
                for (var i = 0; i < parameterBuilder.Parameters.Count; i++)
                {
                    var relationalParameter = parameterBuilder.Parameters[i];
                    relationalParameter.AddDbParameter(command, parameters[i]);
                }

                var materializerSource = dbContext.GetService<IEntityMaterializerSource>();
                if (connection.State == ConnectionState.Closed)
                    connection.Open();

                using (var reader = command.ExecuteReader())
                {
                    foreach (var pair in resultSetMappingTypes.Select((x, i) => (Index: i, Type: x)))
                    {
                        var i = pair.Index;
                        var resultSetMappingType = pair.Type;
                        if (i > 0 && !reader.NextResult())
                            throw new InvalidOperationException(string.Format("No result set at index {0}, unable to map to {1}.", i, resultSetMappingType));

                        var type = resultSetMappingType;
                        var entityType = dbContext.GetService<IModel>()
                                                  .FindEntityType(type);
                        if (entityType == null)
                            throw new InvalidOperationException(string.Format("Unable to find a an entity type (or query type) matching '{0}'", type));
                        var materializationAction = materializerSource.GetMaterializer(entityType);
                        var relationalTypeMappingSource = dbContext.GetService<IRelationalTypeMappingSource>();
                        var columns = Enumerable.Range(0, reader.FieldCount)
                                                .Select(x => new
                                                             {
                                                                     Index = x,
                                                                     Name = reader.GetName(x)
                                                             })
                                                .ToList();

                        var propertiesTypeMaterializationInfo = entityType.GetProperties()
                                                                          .Select(x =>
                                                                                  {
                                                                                      var column = columns.FirstOrDefault(y => string.Equals(y.Name,
                                                                                                                                             x.Relational()
                                                                                                                                              ?.ColumnName ?? x.Name)) ?? throw new InvalidOperationException(string.Format("Unable to find a column mapping property '{0}'.", x.Name));
                                                                                      return new TypeMaterializationInfo(x.PropertyInfo.PropertyType, x, relationalTypeMappingSource, column.Index);
                                                                                  })
                                                                          .ToArray();
                        var valueBufferFactory = dbContext.GetService<IRelationalValueBufferFactoryFactory>()
                                                          .Create(propertiesTypeMaterializationInfo);

                        var resultSetValues = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
                        while (reader.Read())
                        {
                            var valueBuffer = valueBufferFactory.Create(reader);
                            resultSetValues.Add(materializationAction(new MaterializationContext(valueBuffer, dbContext)));
                        }

                        resultSets.Add(resultSetValues);
                    }
                }
            }

            return ((IReadOnlyCollection<T1>)resultSets[0], (IReadOnlyCollection<T2>)resultSets[1], (IReadOnlyCollection<T3>)resultSets[2]);
        }

A possible usage is like this

var (firstResultSet, secondResultSet, thirdResultSet) = dbContext.MultiResultSetsFromSql<FirstResultSetEntity, SecondResultSetEntity, ThirdResultSetEntity>(SQL);

(This extension expects 3 resultsets... you can create more generic versions if a different number of resultsets is expected).

Even in this case, the materialization relies on internal/infrastructure services, so it could break with future version of EFCore.

mscappini commented 5 years ago

@BladeWise Yesssss, thank you VERY much for the excellent post! The entity materialization from TypeMaterializationInfo[] was a missing piece of the puzzle and illuminated quite a bit for me. I think @ppn2 had posted a working solution for a previous version of EF Core (I presently receive deprecated messages from that solution while working in EF Core 2.1.4).

I was able to use information from solution posted by @BladeWise and information from the solution posted by @ppn2 to derive a solution that works for me.

I was able to get entities materialized and use navigational properties with proxy types using the following solution.

public static IEnumerable<T> Materialize<T>(this DbContext dbContext, DbDataReader reader)
    where T : class
{
    var materializerSource = dbContext.GetService<IEntityMaterializerSource>();
    var type = typeof(T);
    var entityType = dbContext.GetService<IModel>()
                              .FindEntityType(type);
    if (entityType == null)
        throw new InvalidOperationException(string.Format("Unable to find a an entity type (or query type) matching '{0}'", type));
    var materializationAction = materializerSource.GetMaterializer(entityType);
    var relationalTypeMappingSource = dbContext.GetService<IRelationalTypeMappingSource>();
    var columns = Enumerable.Range(0, reader.FieldCount)
                            .Select(x => new
                            {
                                Index = x,
                                Name = reader.GetName(x)
                            })
                            .ToList();

    var propertiesTypeMaterializationInfo = entityType.GetProperties()
                                                      .Select(x => {
                                                          var column = columns.FirstOrDefault(y => string.Equals(y.Name, x.Relational()?.ColumnName ?? x.Name, StringComparison.InvariantCultureIgnoreCase))
                                                          ?? throw new InvalidOperationException(string.Format("Unable to find a column mapping property '{0}'.", x.Name));
                                                          return new TypeMaterializationInfo(x.PropertyInfo.PropertyType, x, relationalTypeMappingSource, column.Index);
                                                      })
                                                      .ToArray();

    var valueBufferFactory = dbContext.GetService<IRelationalValueBufferFactoryFactory>()
                                      .Create(propertiesTypeMaterializationInfo);

    var stateManager = dbContext.Set<T>().GetService<IStateManager>() as StateManager;

    stateManager.BeginTrackingQuery();

    while (reader.Read())
    {
        var valueBuffer = valueBufferFactory.Create(reader);
        T entity = (T)materializationAction(new MaterializationContext(valueBuffer, dbContext));
        stateManager.StartTrackingFromQuery(entityType, entity, valueBuffer, null);
        yield return entity;
    }
}

Thanks very much for the contributions!

I believe @ajcvickers had mentioned it before, but I'm not entirely sure this will work everywhere and may fall down in some places. Though it seems to work well enough for me!

ravi-darshankar commented 5 years ago

Hello, Support of multiple result set using SP is rectified in EF Core 2.2??

ajcvickers commented 5 years ago

@ravi-darshankar This issue is in the Backlog milestone. This means that it is not going to happen for the 2.2 or 3.0 release. We will re-assess the backlog following the 3.0 release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources.

nilendrat commented 5 years ago

Hi

With reflections and data-readers , we could archive this. Following is the link to Git Repo

https://github.com/nilendrat/EfCoreMultipleResults

Hope this will be of help.

triynko commented 5 years ago

Can this be done in EF6? It sucks that ObjectContext.Translate cannot handle TPH types, but surely there's a way to do by writing custom code like this, since EF itself is able to do it for normal queries.

ajcvickers commented 5 years ago

@triynko I'm not sure exactly which part of this issue you are referring to, but we don't plan to do any work in this area for EF6.

Shane32 commented 4 years ago

I'd really like to see support for multiple resultsets returned from stored procedures

deepundneni commented 4 years ago

Any update on this? Or do you have any good workaround for this issue?

ajcvickers commented 4 years ago

@deepundneni This issue is in the Backlog milestone. This means that it is not planned for the next release (EF Core 5.0). We will re-assess the backlog following the this release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources.

Potential workarounds are discussed above.

decriptor commented 4 years ago

@ajcvickers I appreciate the ping, but I think you might have tagged the wrong person ;)

siimnelis commented 4 years ago

Upvote, if entity framework core could support something like this from dapper: https://dapper-tutorial.net/querymultiple

Loading object graph to memory from multiple results sets instead of big cartesian product in one go.

spasarto commented 4 years ago

Here is an update to @BladeWise to support EFCore 3.1 and TPH.

public static async Task<IList<IList>> MultiResultSetsFromSql(this DbContext dbContext, ICollection<Type> resultSetMappingTypes, string sql, params object[] parameters)
{
    var resultSets = new List<IList>();

    var connection = dbContext.Database.GetDbConnection();
    var parameterGenerator = dbContext.GetService<IParameterNameGeneratorFactory>()
                                        .Create();
    var commandBuilder = dbContext.GetService<IRelationalCommandBuilderFactory>()
                                    .Create();

    foreach (var parameter in parameters)
    {
        var generatedName = parameterGenerator.GenerateNext();
        if (parameter is DbParameter dbParameter)
            commandBuilder.AddRawParameter(generatedName, dbParameter);
        else
            commandBuilder.AddParameter(generatedName, generatedName);
    }

    using var command = connection.CreateCommand();
    command.CommandType = CommandType.Text;
    command.CommandText = sql;
    command.Connection = connection;
    for (var i = 0; i < commandBuilder.Parameters.Count; i++)
    {
        var relationalParameter = commandBuilder.Parameters[i];
        relationalParameter.AddDbParameter(command, parameters[i]);
    }

    var materializerSource = dbContext.GetService<IEntityMaterializerSource>();
    if (connection.State == ConnectionState.Closed)
        await connection.OpenAsync();

    using var reader = await command.ExecuteReaderAsync();
    foreach (var pair in resultSetMappingTypes.Select((x, i) => (Index: i, Type: x)))
    {
        var i = pair.Index;
        var resultSetMappingType = pair.Type;
        if (i > 0 && !(await reader.NextResultAsync()))
            throw new InvalidOperationException(string.Format("No result set at index {0}, unable to map to {1}.", i, resultSetMappingType));

        var type = resultSetMappingType;
        var entityType = dbContext.GetService<IModel>()
                                    .FindEntityType(type);
        if (entityType == null)
            throw new InvalidOperationException(string.Format("Unable to find a an entity type (or query type) matching '{0}'", type));
        var relationalTypeMappingSource = dbContext.GetService<IRelationalTypeMappingSource>();
        var columns = Enumerable.Range(0, reader.FieldCount)
                                .Select(x => new
                                {
                                    Index = x,
                                    Name = reader.GetName(x)
                                })
                                .ToList();
        var relationalValueBufferFactoryFactory = dbContext.GetService<IRelationalValueBufferFactoryFactory>();
        int discriminatorIdx = -1;
        var discriminatorProperty = entityType.GetDiscriminatorProperty();
        var entityTypes = entityType.GetDerivedTypesInclusive();

        var instanceTypeMapping = entityTypes.Select(et => new
        {
            EntityType = et,
            Properties = et.GetProperties()
                            .Select(x =>
                            {
                                var column = columns.FirstOrDefault(y => string.Equals(y.Name,
                                                                                        x.GetColumnName() ?? x.Name, StringComparison.OrdinalIgnoreCase)) ?? throw new InvalidOperationException(string.Format("Unable to find a column mapping property '{0}'.", x.Name));

                                if (x == discriminatorProperty)
                                    discriminatorIdx = column.Index;
                                return new TypeMaterializationInfo(x.PropertyInfo.PropertyType, x, relationalTypeMappingSource, column.Index);
                            })
                            .ToArray()
        })
        .Select(x => new
        {
            EntityType = x.EntityType,
            Properties = x.Properties,
            ValueBufferFactory = relationalValueBufferFactoryFactory.Create(x.Properties)
        })
        .ToDictionary(e => e.EntityType.GetDiscriminatorValue() ?? e.EntityType, e => e)
        ;

        var resultSetValues = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
        while (await reader.ReadAsync())
        {
            var instanceInfo = discriminatorIdx < 0 ? instanceTypeMapping[entityType] : instanceTypeMapping[reader[discriminatorIdx]];

            var valueBuffer = instanceInfo.ValueBufferFactory.Create(reader);

            var materializationAction = materializerSource.GetMaterializer(instanceInfo.EntityType);
            resultSetValues.Add(materializationAction(new MaterializationContext(valueBuffer, dbContext)));
        }

        resultSets.Add(resultSetValues);
    }

    return resultSets;

And the extension typed methods

public static async Task<(IReadOnlyCollection<T1> FirstResultSet, IReadOnlyCollection<T2> SecondResultSet)> MultiResultSetsFromSql<T1, T2>(this DbContext dbContext, string sql, params object[] parameters)
{
    var resultSetMappingTypes = new[]
                                {
                                        typeof(T1), typeof(T2)
                                };

    var resultSets = await MultiResultSetsFromSql(dbContext, resultSetMappingTypes, sql, parameters);

    return ((IReadOnlyCollection<T1>)resultSets[0], (IReadOnlyCollection<T2>)resultSets[1]);
}

public static async Task<(IReadOnlyCollection<T1> FirstResultSet, IReadOnlyCollection<T2> SecondResultSet, IReadOnlyCollection<T3> ThirdResultSet)> MultiResultSetsFromSql<T1, T2, T3>(this DbContext dbContext, string sql, params object[] parameters)
{
    var resultSetMappingTypes = new[]
                                {
                                        typeof(T1), typeof(T2), typeof(T3)
                                };

    var resultSets = await MultiResultSetsFromSql(dbContext, resultSetMappingTypes, sql, parameters);

    return ((IReadOnlyCollection<T1>)resultSets[0], (IReadOnlyCollection<T2>)resultSets[1], (IReadOnlyCollection<T3>)resultSets[2]);
}
vaclav-antosik commented 3 years ago

In EFCore 5 the [Obsolete] public TypeMaterializationInfo( [NotNull] Type modelClrType, [CanBeNull] IProperty property, [CanBeNull] IRelationalTypeMappingSource typeMappingSource, int index = -1) : this(modelClrType, property, typeMappingSource, null, index) { } has been switched to obsolete. New constructor does not include the index parameter. So there is no way how to map order or reader columns to object columns. In case that order does not match, it's game over (for workarounds mentioned above).

I hope that this missing feature will be implemented before the index parameter finally disappears from the API.

mwgolden commented 3 years ago

Here is an update to @BladeWise to support EFCore 3.1 and TPH.

public static async Task<IList<IList>> MultiResultSetsFromSql(this DbContext dbContext, ICollection<Type> resultSetMappingTypes, string sql, params object[] parameters)
{
    var resultSets = new List<IList>();

    var connection = dbContext.Database.GetDbConnection();
    var parameterGenerator = dbContext.GetService<IParameterNameGeneratorFactory>()
                                        .Create();
    var commandBuilder = dbContext.GetService<IRelationalCommandBuilderFactory>()
                                    .Create();

    foreach (var parameter in parameters)
    {
        var generatedName = parameterGenerator.GenerateNext();
        if (parameter is DbParameter dbParameter)
            commandBuilder.AddRawParameter(generatedName, dbParameter);
        else
            commandBuilder.AddParameter(generatedName, generatedName);
    }

    using var command = connection.CreateCommand();
    command.CommandType = CommandType.Text;
    command.CommandText = sql;
    command.Connection = connection;
    for (var i = 0; i < commandBuilder.Parameters.Count; i++)
    {
        var relationalParameter = commandBuilder.Parameters[i];
        relationalParameter.AddDbParameter(command, parameters[i]);
    }

    var materializerSource = dbContext.GetService<IEntityMaterializerSource>();
    if (connection.State == ConnectionState.Closed)
        await connection.OpenAsync();

    using var reader = await command.ExecuteReaderAsync();
    foreach (var pair in resultSetMappingTypes.Select((x, i) => (Index: i, Type: x)))
    {
        var i = pair.Index;
        var resultSetMappingType = pair.Type;
        if (i > 0 && !(await reader.NextResultAsync()))
            throw new InvalidOperationException(string.Format("No result set at index {0}, unable to map to {1}.", i, resultSetMappingType));

        var type = resultSetMappingType;
        var entityType = dbContext.GetService<IModel>()
                                    .FindEntityType(type);
        if (entityType == null)
            throw new InvalidOperationException(string.Format("Unable to find a an entity type (or query type) matching '{0}'", type));
        var relationalTypeMappingSource = dbContext.GetService<IRelationalTypeMappingSource>();
        var columns = Enumerable.Range(0, reader.FieldCount)
                                .Select(x => new
                                {
                                    Index = x,
                                    Name = reader.GetName(x)
                                })
                                .ToList();
        var relationalValueBufferFactoryFactory = dbContext.GetService<IRelationalValueBufferFactoryFactory>();
        int discriminatorIdx = -1;
        var discriminatorProperty = entityType.GetDiscriminatorProperty();
        var entityTypes = entityType.GetDerivedTypesInclusive();

        var instanceTypeMapping = entityTypes.Select(et => new
        {
            EntityType = et,
            Properties = et.GetProperties()
                            .Select(x =>
                            {
                                var column = columns.FirstOrDefault(y => string.Equals(y.Name,
                                                                                        x.GetColumnName() ?? x.Name, StringComparison.OrdinalIgnoreCase)) ?? throw new InvalidOperationException(string.Format("Unable to find a column mapping property '{0}'.", x.Name));

                                if (x == discriminatorProperty)
                                    discriminatorIdx = column.Index;
                                return new TypeMaterializationInfo(x.PropertyInfo.PropertyType, x, relationalTypeMappingSource, column.Index);
                            })
                            .ToArray()
        })
        .Select(x => new
        {
            EntityType = x.EntityType,
            Properties = x.Properties,
            ValueBufferFactory = relationalValueBufferFactoryFactory.Create(x.Properties)
        })
        .ToDictionary(e => e.EntityType.GetDiscriminatorValue() ?? e.EntityType, e => e)
        ;

        var resultSetValues = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
        while (await reader.ReadAsync())
        {
            var instanceInfo = discriminatorIdx < 0 ? instanceTypeMapping[entityType] : instanceTypeMapping[reader[discriminatorIdx]];

            var valueBuffer = instanceInfo.ValueBufferFactory.Create(reader);

            var materializationAction = materializerSource.GetMaterializer(instanceInfo.EntityType);
            resultSetValues.Add(materializationAction(new MaterializationContext(valueBuffer, dbContext)));
        }

        resultSets.Add(resultSetValues);
    }

    return resultSets;

And the extension typed methods

public static async Task<(IReadOnlyCollection<T1> FirstResultSet, IReadOnlyCollection<T2> SecondResultSet)> MultiResultSetsFromSql<T1, T2>(this DbContext dbContext, string sql, params object[] parameters)
{
    var resultSetMappingTypes = new[]
                                {
                                        typeof(T1), typeof(T2)
                                };

    var resultSets = await MultiResultSetsFromSql(dbContext, resultSetMappingTypes, sql, parameters);

    return ((IReadOnlyCollection<T1>)resultSets[0], (IReadOnlyCollection<T2>)resultSets[1]);
}

public static async Task<(IReadOnlyCollection<T1> FirstResultSet, IReadOnlyCollection<T2> SecondResultSet, IReadOnlyCollection<T3> ThirdResultSet)> MultiResultSetsFromSql<T1, T2, T3>(this DbContext dbContext, string sql, params object[] parameters)
{
    var resultSetMappingTypes = new[]
                                {
                                        typeof(T1), typeof(T2), typeof(T3)
                                };

    var resultSets = await MultiResultSetsFromSql(dbContext, resultSetMappingTypes, sql, parameters);

    return ((IReadOnlyCollection<T1>)resultSets[0], (IReadOnlyCollection<T2>)resultSets[1], (IReadOnlyCollection<T3>)resultSets[2]);
}

I'm having some trouble getting this solution to work. It looks like the materializerAction isn't getting assigned a delegate. But I don't think I understand this code enough to properly troubleshoot.

image

instead, I was able to get something working like this:

`using (connection) { var command = new SqlCommand(sql, connection); command.CommandType = System.Data.CommandType.StoredProcedure; foreach (DbParameter param in parameters) { command.Parameters.Add(param); } connection.Open(); var reader = await command.ExecuteReaderAsync(); int resultidx = 0; do { Type type = resultSetMappingTypes[resultidx];

                var resultSetValues = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
                var columns = Enumerable.Range(0, reader.FieldCount).Select(reader.GetName).ToList();
                while (reader.Read())
                {
                    var obj = Activator.CreateInstance(type);
                    foreach (var column in columns)
                    {
                        var value = reader[column] == DBNull.Value ? null : reader[column];
                        obj.GetType().GetProperty(column).SetValue(obj,value);
                    }
                    resultSetValues.Add(obj);
                }
                resultSets.Add(resultSetValues);
                resultidx++;

            } while (reader.NextResult());
        }`
ZarDBA commented 3 years ago

It works with this tiny change on EF core 5 according to @mwgolden update change command.CommandType = CommandType.Text; to command.CommandType = CommandType.StoredProcedure;

and as sql parameter value for this extension method type your stored procedure name "dbo.testproc" example of usage: var t1 = await _context.MultiResultSetsFromSql(new [] {typeof(proctestprocResult) },"dbo.testproc", sqlParameters);

works for me

excelthoughts commented 2 years ago

This is not working for me. When I call my SP, it is conditional in what SELECT statements returns. Additionally, there are Instead of Insert Triggers returning from other tables being inserted to. I cannot capture the Errors (return when SP raises error) or Ids (SP success.) All I am getting back is the 2 Trigger returns then nothing. Any ideas? What details can I share?

Pseudo code (SP)

IF x Select Id
IF y Select Id, DateTime
IF z Select * From Errors

Pseudo code (Triggers)

Select Id, Bool, Bool From Table1
Select Id, byte[] From Table2

Code calling the MultiResultSetsFromSql

            var reader = await ((DbContext)_dbContext).MultiResultSetsFromSql<InsertTrigger1, InsertTrigger2, Error, int?, Receipt>("SP");

            List<insertTrigger1> insertTrigger1s= reader.FirstResultSet.ToList(); //if an insert has happened to table1
            List<insertTrigger2> insertTrigger2s = reader.SecondResultSet.ToList(); //if an insert has happened to table2
            IEnumerable<Error> errors = reader.ThirdResultSet.AsEnumerable(); //if error
            List<int?> ids= reader.FourthResultSet.ToList(); //if success
            List<Receipt> receipts = reader.FifthResultSet.ToList(); //if receipt
RickSandstrom commented 1 year ago

@mwgolden @BladeWise @ZarDBA @spasarto We have been using the solution provided by @BladeWise in EF Core 3.1 successfully for a few years. Now we have upgraded to EF Core 6 and I can't get this to work.

TypeMaterializationInfo does not take a RelationalTypeMappingSource as parameter anymore, but a RelationalTypeMapping and I can't seem to get one of those.

The update from @ZarDBA takes me a bit further, but not to a working solution. Maybe I should try rewriting like @mwgolden did?

update: Rewriting worked like a charm. The updated solution provided by @mwgolden seems to be working, and with much less code (and more understandable code). Thank you!

ArnaudValensi commented 1 year ago

Based on what @mwgolden suggested, here is a working solution for dotnet core 6:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;

public static class DbContextExtensions
{
    public static async Task<IList<IList>> QueryStoredProcedureWithMultipleResults(
        this DbContext dbContext,
        List<Type> resultSetMappingTypes,
        string storedProcedureName,
        params object[] parameters
    )
    {
        var resultSets = new List<IList>();

        var connection = dbContext.Database.GetDbConnection();
        var parameterGenerator = dbContext.GetService<IParameterNameGeneratorFactory>()
            .Create();
        var commandBuilder = dbContext.GetService<IRelationalCommandBuilderFactory>()
            .Create();

        foreach (var parameter in parameters)
        {
            var generatedName = parameterGenerator.GenerateNext();
            if (parameter is DbParameter dbParameter)
                commandBuilder.AddRawParameter(generatedName, dbParameter);
            else
                commandBuilder.AddParameter(generatedName, generatedName);
        }

        await using var command = connection.CreateCommand();
        command.CommandType = CommandType.StoredProcedure;
        command.CommandText = storedProcedureName;
        command.Connection = connection;
        for (var i = 0; i < commandBuilder.Parameters.Count; i++)
        {
            var relationalParameter = commandBuilder.Parameters[i];
            relationalParameter.AddDbParameter(command, parameters[i]);
        }

        if (connection.State == ConnectionState.Closed)
            await connection.OpenAsync();

        await using var reader = await command.ExecuteReaderAsync();

        int resultIndex = 0;
        do
        {
            Type type = resultSetMappingTypes[resultIndex];

            var resultSetValues = (IList) Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
            var columns = Enumerable.Range(0, reader.FieldCount).Select(reader.GetName).ToList();
            while (reader.Read())
            {
                var obj = Activator.CreateInstance(type);
                if (obj == null)
                {
                    throw new Exception($"Cannot create object from type '{type}'");
                }

                foreach (var column in columns)
                {
                    var value = reader[column] == DBNull.Value ? null : reader[column];
                    obj!.GetType().GetProperty(column)?.SetValue(obj, value);
                }

                resultSetValues!.Add(obj);
            }

            resultSets.Add(resultSetValues);
            resultIndex++;
        } while (reader.NextResult());

        return resultSets;
    }

    public static async Task<(IReadOnlyCollection<T1> FirstResultSet, IReadOnlyCollection<T2> SecondResultSet)>
        QueryStoredProcedureWithMultipleResults<T1, T2>(
            this DbContext dbContext,
            string storedProcedureName,
            params object[] parameters
        )
    {
        List<Type> resultSetMappingTypes = new List<Type>() {typeof(T1), typeof(T2)};

        var resultSets =
            await QueryStoredProcedureWithMultipleResults(dbContext, resultSetMappingTypes, storedProcedureName,
                parameters);

        return ((IReadOnlyCollection<T1>) resultSets[0], (IReadOnlyCollection<T2>) resultSets[1]);
    }

    public static async
        Task<(IReadOnlyCollection<T1> FirstResultSet, IReadOnlyCollection<T2> SecondResultSet,
            IReadOnlyCollection<T3>
            ThirdResultSet)> QueryStoredProcedureWithMultipleResults<T1, T2, T3>(
            this DbContext dbContext,
            string storedProcedureName,
            params object[] parameters
        )
    {
        List<Type> resultSetMappingTypes = new List<Type>() {typeof(T1), typeof(T2), typeof(T3)};

        var resultSets =
            await QueryStoredProcedureWithMultipleResults(dbContext, resultSetMappingTypes, storedProcedureName,
                parameters);

        return ((IReadOnlyCollection<T1>) resultSets[0], (IReadOnlyCollection<T2>) resultSets[1],
            (IReadOnlyCollection<T3>) resultSets[2]);
    }
}

Usage:

var (result1, result2, result3) = await dbContext.QueryStoredProcedureWithMultipleResults<Model1, Model2, Model3>("StoredProcedureName");
fgalarraga commented 1 year ago

Is there any update on this for EF Core 7 or potentially included in EF Core 8?

roji commented 1 year ago

This issue is in the Backlog milestone. This means that it is not planned for the next release (EF Core 8.0). We will re-assess the backlog following the this release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources. Make sure to vote (👍) for this issue if it is important to you.

yogeshfirke07 commented 1 year ago

how to replace IObjectContextAdapter and replace them with IInfrastructure i am migrating the code from entity framework 6 to entity framework core 6 but i am getting error for IObjectContextAdapter for the below code please suggest -

db.Database.Connection.Open(); var reader = cmd.ExecuteReader(); var blogs = ((IObjectContextAdapter)db) .ObjectContext .Translate(reader, "Blogs", MergeOption.AppendOnly);

                foreach (var item in blogs)
                {
                    Console.WriteLine(item.Name);
                }
anildbest83 commented 1 year ago

In Entity Framework Core 6, the IObjectContextAdapter interface is no longer available, and instead, the IInfrastructure interface is used.

To replace IObjectContextAdapter with IInfrastructure, you can modify your code as follows:

db.Database.GetDbConnection().Open();
var reader = cmd.ExecuteReader();
var blogs = db.GetService<IStateManager>()
    .RegisterStreamedResults<Blog>(reader, "Blogs")
    .ToList();

foreach (var item in blogs)
{
    Console.WriteLine(item.Name);
}

In the above code, GetService() is used to get an instance of the IStateManager interface, which is used to manage the state of entities in Entity Framework Core. Then RegisterStreamedResults is called to register the streamed results with the state manager and the ToList method is called to materialize the results.

Note that RegisterStreamedResults takes the entity type Blog as its generic type argument and the name of the result set as its second argument. Also, you need to use the GetDbConnection method to get the underlying DbConnection object and call its Open method to open the connection before executing the command.

yogeshfirke07 commented 1 year ago

In Entity Framework Core 6, the IObjectContextAdapter interface is no longer available, and instead, the IInfrastructure interface is used.

To replace IObjectContextAdapter with IInfrastructure, you can modify your code as follows:

db.Database.GetDbConnection().Open();
var reader = cmd.ExecuteReader();
var blogs = db.GetService<IStateManager>()
    .RegisterStreamedResults<Blog>(reader, "Blogs")
    .ToList();

foreach (var item in blogs)
{
    Console.WriteLine(item.Name);
}

In the above code, GetService() is used to get an instance of the IStateManager interface, which is used to manage the state of entities in Entity Framework Core. Then RegisterStreamedResults is called to register the streamed results with the state manager and the ToList method is called to materialize the results.

Note that RegisterStreamedResults takes the entity type Blog as its generic type argument and the name of the result set as its second argument. Also, you need to use the GetDbConnection method to get the underlying DbConnection object and call its Open method to open the connection before executing the command.

i am getting error at RegisterStreamedResults method i am using Microsoft.EntityFrameworkCore -Version 6.0.15

Nefcanto commented 1 year ago

I can't believe that after almost 5 years this is not implemented yet. It's an extremely useful feature for performance.