Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set operations need to assign correct nullability to shaper #19253

Closed
LostAsk opened this issue Dec 9, 2019 · 6 comments · Fixed by #23227
Closed

Set operations need to assign correct nullability to shaper #19253

LostAsk opened this issue Dec 9, 2019 · 6 comments · Fixed by #23227
Assignees
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported punted-for-5.0 type-bug
Milestone

Comments

@LostAsk
Copy link

LostAsk commented Dec 9, 2019

using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace AbpEfCore
{
    public class JoinResult<TLeft, TRight>
    {
        public TLeft Left { get; set; }

        public TRight Right { get; set; }
    }

    public static class QueryLinqHelper
    {
        public static IQueryable<TResult> InnerJoin<TOuter, TInner, TKey, TResult>(
            this IQueryable<TOuter> outer,
            IQueryable<TInner> inner,
            Expression<Func<TOuter, TKey>> outerKeySelector,
            Expression<Func<TInner, TKey>> innerKeySelector,
            Expression<Func<TOuter, TInner, TResult>> resultSelector)

        {
            return outer.Join(inner, outerKeySelector, innerKeySelector, resultSelector);
        }

        public static IQueryable<JoinResult<TOuter, TInner>> InnerJoin<TOuter, TInner, TKey>(
            this IQueryable<TOuter> outer,
            IQueryable<TInner> inner,
            Expression<Func<TOuter, TKey>> outerKeySelector,
            Expression<Func<TInner, TKey>> innerKeySelector
            )
        {
            return outer.Join(inner, outerKeySelector, innerKeySelector, (x, y) => new JoinResult<TOuter, TInner> { Left = x, Right = y });
        }


        public static IQueryable<JoinResult<TLeft, TRight>> LeftJoin<TLeft, TRight, TKey>(
            this IQueryable<TLeft> leftItems,
            IQueryable<TRight> rightItems,
            Expression<Func<TLeft, TKey>> leftKeySelector,
            Expression<Func<TRight, TKey>> rightKeySelector
           )

        {
            return leftItems.GroupJoin(rightItems, leftKeySelector, rightKeySelector, (left, rightg) => new { left, rightg })
                .SelectMany(r => r.rightg.DefaultIfEmpty()
                , (x, y) => new JoinResult<TLeft, TRight> { Left = x.left, Right = y });
        }

        public static IQueryable<JoinResult<TLeft, TRight>> LeftExcludingJoin<TLeft, TRight, TKey>(
            this IQueryable<TLeft> leftItems,
            IQueryable<TRight> rightItems,
            Expression<Func<TLeft, TKey>> leftKeySelector,
            Expression<Func<TRight, TKey>> rightKeySelector
           )

		{

            return leftItems.GroupJoin(rightItems, leftKeySelector, rightKeySelector, (left, rightg) => new { left, rightg })
                .SelectMany(r => r.rightg.DefaultIfEmpty()
                , (x, y) => new JoinResult<TLeft, TRight> { Left = x.left, Right = y }).Where(z => z.Right.Equals(null));
        }
        public static IQueryable<JoinResult<TLeft, TRight>> RightJoin<TLeft, TRight, TKey>(
            this IQueryable<TLeft> leftItems,
            IQueryable<TRight> rightItems,
            Expression<Func<TLeft, TKey>> leftKeySelector,
            Expression<Func<TRight, TKey>> rightKeySelector
            )
        {
            return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right })
                             .SelectMany(l => l.leftg.DefaultIfEmpty(),
                             (x, y) => new JoinResult<TLeft, TRight> { Left = y, Right = x.right });
        }


        public static IQueryable<JoinResult<TLeft, TRight>> RightExcludingJoin<TLeft, TRight, TKey>(
            this IQueryable<TLeft> leftItems,
            IQueryable<TRight> rightItems,
            Expression<Func<TLeft, TKey>> leftKeySelector,
            Expression<Func<TRight, TKey>> rightKeySelector
            )
        {
            return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right })
                             .SelectMany(l => l.leftg.DefaultIfEmpty(),
                             (x, y) => new JoinResult<TLeft, TRight> { Left = y, Right = x.right })
                             .Where(z => z.Left.Equals(null))
                             ;
        }


        public static IQueryable<JoinResult<TLeft, TRight>> FullJoin<TLeft, TRight, TKey>(
            this IQueryable<TLeft> leftItems,
            IQueryable<TRight> rightItems,
            Expression<Func<TLeft, TKey>> leftKeySelector,
            Expression<Func<TRight, TKey>> rightKeySelector
           )
        {
            return leftItems.LeftJoin(rightItems, leftKeySelector, rightKeySelector).Concat(leftItems.RightExcludingJoin(rightItems, leftKeySelector, rightKeySelector));
        }


    }


    public class Program
    {
        public static void Main(string[] args)
        {
            using (var db = new MyContext())
            {
                db.Database.EnsureDeleted();
                db.Database.EnsureCreated();
                var tmp_a=new A[]{
               new A{Id=1,a="a0",a1="a1",forkey="a"},
               new A{Id=3,a="a2",a1="a1",forkey="d"},
                };
                var tmp_b=new B[]{
               new B{Id=1,b="b0",b1="b1",forkey="a"},
               new B{Id=3,b="b2",b1="b1",forkey="c"},
                };
               db.A.AddRange(tmp_a);
               db.B.AddRange(tmp_b);
                db.SaveChanges();
            }

            using (var db = new MyContext())
            {
                // Run queries
                var query = db.A.FullJoin(db.B,x=>x.forkey,y=>y.forkey).ToList();
            }
            Console.WriteLine("Program finished.");
        }
    }

public class MyContext : DbContext
    {
        private static ILoggerFactory ContextLoggerFactory
            => LoggerFactory.Create(b =>
            {
                b
                .AddConsole()
                .AddFilter("", LogLevel.Debug);
            });

        public DbSet<A> A{ get; set; }
        public DbSet<B> B{ get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            // Select 1 provider
            optionsBuilder
                .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=_ModelApp;Trusted_Connection=True;Connect Timeout=5;ConnectRetryCount=0")
                .EnableSensitiveDataLogging()
                .UseLoggerFactory(ContextLoggerFactory);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Configure model
        }
    }

    public class A
    {
        public int Id { get; set; }
        public string a { get; set; }
        public string a1  { get; set; }
        public string forkey	{get;set;}

    }

    public class B
    {
        public int Id { get; set; }
        public string b { get; set; }
        public string b1  { get; set; }
        public string forkey	{get;set;}
    }
}

Throw:
System.InvalidOperationException:“Unable to track an entity of type 'A' because primary key property 'Id' is null.”

@LostAsk LostAsk changed the title System.InvalidOperationException:“Unable to track an entity of type 'A' because primary key property 'Id' is null.” EF CORE 3.System.InvalidOperationException:“Unable to track an entity of type 'A' because primary key property 'Id' is null.” Dec 9, 2019
@LostAsk LostAsk changed the title EF CORE 3.System.InvalidOperationException:“Unable to track an entity of type 'A' because primary key property 'Id' is null.” EF CORE 3.1 System.InvalidOperationException:“Unable to track an entity of type 'A' because primary key property 'Id' is null.” Dec 9, 2019
@ajcvickers
Copy link
Member

@LostAsk When I run this code I get the following exception, which makes sense given the model defined and the way it is used. It is not the exception you are reporting.

Unhandled exception. Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
 ---> Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert explicit value for identity column in table 'A' when IDENTITY_INSERT is set to OFF.
Cannot insert explicit value for identity column in table 'B' when IDENTITY_INSERT is set to OFF.
   at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at Microsoft.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
   at Microsoft.Data.SqlClient.SqlDataReader.get_MetaData()
   at Microsoft.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted)
   at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean isAsync, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
   at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String method)
   at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
   at Microsoft.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
   at Microsoft.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
   at System.Data.Common.DbCommand.ExecuteReader()
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject)
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
ClientConnectionId:e465a788-cf67-4f68-8c21-688c204d4bc1
Error Number:544,State:1,Class:16
   --- End of inner exception stack trace ---
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList`1 entries)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList`1 entriesToSave)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
   at AbpEfCore.Program.Main(String[] args) in 

@LostAsk
Copy link
Author

LostAsk commented Dec 10, 2019

@ajcvickers
sorry!
Code:

        public static void Main(string[] args)
        {
            using (var db = new MyContext())
            {
                db.Database.EnsureDeleted();
                db.Database.EnsureCreated();
                var tmp_a = new A[]{
               new A{a="a0",a1="a1",forkey="a"},
               new A{a="a2",a1="a1",forkey="d"},
                };
                var tmp_b = new B[]{
               new B{b="b0",b1="b1",forkey="a"},
               new B{b="b2",b1="b1",forkey="c"},
                };
                db.A.AddRange(tmp_a);
                db.B.AddRange(tmp_b);
                db.SaveChanges();
            }

            using (var db = new MyContext())
            {
                // Run queries
                var query = db.A.FullJoin(db.B, x => x.forkey, y => y.forkey).ToList();
            }
            Console.WriteLine("Program finished.");
        }

@ajcvickers
Copy link
Member

The query results in creation of entity instances which will then be tracked. However, if any of those instances are null then we are still trying to track them when we should not.

@LostAsk Using AsNoTracking() on the query allowed me to get the results. As a workaround you could manually tack the entities afterwards if you need tracking.

Results look like:

image

Simplified repro:

namespace AbpEfCore
{
    public class JoinResult<TLeft, TRight>
    {
        public TLeft Left { get; set; }

        public TRight Right { get; set; }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            using (var db = new MyContext())
            {
                db.Database.EnsureDeleted();
                db.Database.EnsureCreated();
                var tmp_a = new A[]
                {
                    new A {a = "a0", a1 = "a1", forkey = "a"},
                    new A {a = "a2", a1 = "a1", forkey = "d"},
                };
                var tmp_b = new B[]
                {
                    new B {b = "b0", b1 = "b1", forkey = "a"},
                    new B {b = "b2", b1 = "b1", forkey = "c"},
                };
                db.A.AddRange(tmp_a);
                db.B.AddRange(tmp_b);
                db.SaveChanges();
            }

            using (var db = new MyContext())
            {
                // Run queries
                Expression<Func<A, string>> leftKeySelector = x => x.forkey;
                Expression<Func<B, string>> rightKeySelector = y => y.forkey;

                var query = db.A.AsNoTracking().GroupJoin(
                        db.B,
                        leftKeySelector,
                        rightKeySelector,
                        (left, rightg) => new
                        {
                            left,
                            rightg
                        })
                    .SelectMany(
                        r => r.rightg.DefaultIfEmpty(),
                        (x, y) => new JoinResult<A, B>
                        {
                            Left = x.left,
                            Right = y
                        })
                    .Concat(
                        db.B.GroupJoin(
                                db.A,
                                rightKeySelector,
                                leftKeySelector,
                                (right, leftg) => new {leftg, right})
                            .SelectMany(l => l.leftg.DefaultIfEmpty(),
                                (x, y) => new JoinResult<A, B>
                                {
                                    Left = y,
                                    Right = x.right
                                })
                            .Where(z => z.Left.Equals(null)))
                    .ToList();
            }
        }
    }

    public class MyContext : DbContext
    {
        private static ILoggerFactory ContextLoggerFactory
            => LoggerFactory.Create(b =>
            {
                b
                    .AddConsole()
                    .AddFilter("", LogLevel.Debug);
            });

        public DbSet<A> A { get; set; }
        public DbSet<B> B { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            // Select 1 provider
            optionsBuilder
                .UseSqlServer(
                    @"Server=(localdb)\mssqllocaldb;Database=_ModelApp;Trusted_Connection=True;Connect Timeout=5;ConnectRetryCount=0")
                .EnableSensitiveDataLogging()
                .UseLoggerFactory(ContextLoggerFactory);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Configure model
        }
    }

    public class A
    {
        public int Id { get; set; }
        public string a { get; set; }
        public string a1 { get; set; }
        public string forkey { get; set; }

    }

    public class B
    {
        public int Id { get; set; }
        public string b { get; set; }
        public string b1 { get; set; }
        public string forkey { get; set; }
    }
}

@smitpatel
Copy link
Member

smitpatel commented Dec 10, 2019

Set operation did not assign proper nullability to entity shaper.

@LostAsk LostAsk closed this as completed Dec 11, 2019
@LostAsk
Copy link
Author

LostAsk commented Dec 11, 2019

OK! Thanks!

@LostAsk LostAsk reopened this Dec 11, 2019
@LostAsk
Copy link
Author

LostAsk commented Dec 11, 2019

@ajcvickers @smitpatel
I refer to other people's query helper and modify it myself
ref:https://www.codenong.com/5489987/
the ef6 and EF core 2 operate normally, but EF core 3 reports an error

using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace AbpEfCore
{
    public static class QueryLinqHelper
    {
        public static IQueryable<TResult> InnerJoin<TOuter, TInner, TKey, TResult>(
            this IQueryable<TOuter> outer,
            IQueryable<TInner> inner,
            Expression<Func<TOuter, TKey>> outerKeySelector,
            Expression<Func<TInner, TKey>> innerKeySelector,
            Expression<Func<TOuter, TInner, TResult>> resultSelector)
        {
            return outer.Join(inner, outerKeySelector, innerKeySelector, resultSelector);
        }
        //private static Expression<Func<TP, TC, TResult>> CastSMBody<TP, TC, TResult>(LambdaExpression ex, TP unusedP, TC unusedC, TResult unusedRes) => (Expression<Func<TP, TC, TResult>>)ex;

        public static IQueryable<TResult> LeftJoin<TLeft, TRight, TKey, TResult>(
            this IQueryable<TLeft> leftItems,
            IQueryable<TRight> rightItems,
            Expression<Func<TLeft, TKey>> leftKeySelector,
            Expression<Func<TRight, TKey>> rightKeySelector,
            Expression<Func<TLeft, TRight, TResult>> resultSelector) where TLeft : class where TRight : class where TResult : class
        {
            Expression<Func<TP, TC, TResult1>> CastSMBody<TP, TC, TResult1>(LambdaExpression ex, TP unusedP, TC unusedC, TResult1 unusedRes) => (Expression<Func<TP, TC, TResult1>>)ex;
            // (lrg,r) => resultSelector(lrg.left, r)
            var sampleAnonLR = new { left = (TLeft)null, rightg = (IEnumerable<TRight>)null };
            var parmP = Expression.Parameter(sampleAnonLR.GetType(), "lrg");
            var parmC = Expression.Parameter(typeof(TRight), "r");
            var argLeft = Expression.PropertyOrField(parmP, "left");
            var newleftrs = CastSMBody(Expression.Lambda(resultSelector.Apply(argLeft, parmC), parmP, parmC), sampleAnonLR, (TRight)null, (TResult)null);

            return leftItems.GroupJoin(rightItems, leftKeySelector, rightKeySelector, (left, rightg) => new { left, rightg }).SelectMany(r => r.rightg.DefaultIfEmpty(), newleftrs);
        }

        public static IQueryable<TResult> LeftExcludingJoin<TLeft, TRight, TKey, TResult>(
            this IQueryable<TLeft> leftItems,
            IQueryable<TRight> rightItems,
            Expression<Func<TLeft, TKey>> leftKeySelector,
            Expression<Func<TRight, TKey>> rightKeySelector,
            Expression<Func<TLeft, TRight, TResult>> resultSelector) where TLeft : class where TRight : class where TResult : class
        {
            Expression<Func<TParm, TResult1>> CastSBody<TParm, TResult1>(LambdaExpression ex, TParm unusedP, TResult1 unusedRes)
            {
                return (Expression<Func<TParm, TResult1>>)ex;
            }

            var sampleAnonLR = new { leftg = (TLeft)null, right = (TRight)null };
            var parmLgR = Expression.Parameter(sampleAnonLR.GetType(), "lrg");
            var argLeft = Expression.Constant(null, typeof(TRight));
            var argRight = Expression.PropertyOrField(parmLgR, "leftg");
            var newrightrs = CastSBody(Expression.Lambda(resultSelector.Apply(argRight, argLeft), parmLgR), sampleAnonLR, (TResult)null);

            return leftItems.GroupJoin(rightItems, leftKeySelector, rightKeySelector, (leftg, right) => new { leftg, right })
                .SelectMany(r => r.right.DefaultIfEmpty(), (leftg, right) => new { leftg.leftg, right })
                .Where(lrg => lrg.right==null).Select(newrightrs);

        }


        public static IQueryable<TResult> RightJoin<TLeft, TRight, TKey, TResult>(
            this IQueryable<TLeft> leftItems,
            IQueryable<TRight> rightItems,
            Expression<Func<TLeft, TKey>> leftKeySelector,
            Expression<Func<TRight, TKey>> rightKeySelector,
            Expression<Func<TLeft, TRight, TResult>> resultSelector) where TLeft : class where TRight : class where TResult : class
        {
            Expression<Func<TP, TC, TResult1>> CastSMBody<TP, TC, TResult1>(LambdaExpression ex, TP unusedP, TC unusedC, TResult1 unusedRes) => (Expression<Func<TP, TC, TResult1>>)ex;
            // (lgr,l) => resultSelector(l, lgr.right)
            var sampleAnonLR = new { leftg = (IEnumerable<TLeft>)null, right = (TRight)null };
            var parmP = Expression.Parameter(sampleAnonLR.GetType(), "lgr");
            var parmC = Expression.Parameter(typeof(TLeft), "l");
            var argRight = Expression.PropertyOrField(parmP, "right");
            var newrightrs = CastSMBody(Expression.Lambda(resultSelector.Apply(parmC, argRight), parmP, parmC), sampleAnonLR, (TLeft)null, (TResult)null);

            return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right })
                             .SelectMany(l => l.leftg.DefaultIfEmpty(), newrightrs);
        }

        public static IQueryable<TResult> RightExcludingJoin<TLeft, TRight, TKey, TResult>(
            this IQueryable<TLeft> leftItems,
            IQueryable<TRight> rightItems,
            Expression<Func<TLeft, TKey>> leftKeySelector,
            Expression<Func<TRight, TKey>> rightKeySelector,
            Expression<Func<TLeft, TRight, TResult>> resultSelector) where TLeft : class where TRight : class where TResult : class
        {
            Expression<Func<TParm, TResult1>> CastSBody<TParm, TResult1>(LambdaExpression ex, TParm unusedP, TResult1 unusedRes) => (Expression<Func<TParm, TResult1>>)ex;
            // newrightrs = lgr => resultSelector((TLeft)null, lgr.right)
            var sampleAnonLgR = new { leftg = (TLeft)null, right = (TRight)null };
            var parmLgR = Expression.Parameter(sampleAnonLgR.GetType(), "lgr");
            var argLeft = Expression.Constant(null, typeof(TLeft));
            var argRight = Expression.PropertyOrField(parmLgR, "right");
            var newrightrs = CastSBody(Expression.Lambda(resultSelector.Apply(argLeft, argRight), parmLgR), sampleAnonLgR, (TResult)null);

            return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right })
                .SelectMany(l => l.leftg.DefaultIfEmpty(), (right, leftg) => new { leftg, right.right })
                .Where(lgr => lgr.leftg==null)
                .Select(newrightrs);
        }

        public static IQueryable<TResult> FullJoin<TLeft, TRight, TKey, TResult>(
            this IQueryable<TLeft> leftItems,
            IQueryable<TRight> rightItems,
            Expression<Func<TLeft, TKey>> leftKeySelector,
            Expression<Func<TRight, TKey>> rightKeySelector,
            Expression<Func<TLeft, TRight, TResult>> resultSelector) where TLeft : class where TRight : class where TResult : class
        {

            var left = leftItems.LeftJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector);
            var right = leftItems.RightExcludingJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector);
             var all= left.Concat(right);
            return all;
        }

        public static Expression Apply(this LambdaExpression e, params Expression[] args)
        {
            var b = e.Body;

            foreach (var pa in e.Parameters.Cast<ParameterExpression>().Zip(args, (p, a) => new { p, a }))
            {
                b = b.Replace(pa.p, pa.a);
            }

            return b.PropagateNull();
        }

        public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
        public class ReplaceVisitor : System.Linq.Expressions.ExpressionVisitor
        {
            public readonly Expression from;
            public readonly Expression to;

            public ReplaceVisitor(Expression _from, Expression _to)
            {
                from = _from;
                to = _to;
            }

            public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
        }

        public static Expression PropagateNull(this Expression orig) => new NullVisitor().Visit(orig);
        public class NullVisitor : System.Linq.Expressions.ExpressionVisitor
        {
            public override Expression Visit(Expression node)
            {
                if (node is MemberExpression nme && nme.Expression is ConstantExpression nce && nce.Value == null)
                    return Expression.Constant(null, nce.Type.GetMember(nme.Member.Name).Single().GetMemberType());
                else
                    return base.Visit(node);
            }
        }

        public static Type GetMemberType(this MemberInfo member)
        {
            return member switch
            {
                FieldInfo mfi => mfi.FieldType,
                PropertyInfo mpi => mpi.PropertyType,
                EventInfo mei => mei.EventHandlerType,
                _ => throw new ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", nameof(member)),
            };
            //switch (member)
            //{
            //    case FieldInfo mfi: return mfi.FieldType;
            //    case PropertyInfo mpi: return mpi.PropertyType;
            //    case EventInfo mei: return mei.EventHandlerType;
            //    default: throw new ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", nameof(member));
            //}
        }
    }

        public class Program
    {
        public static void Main(string[] args)
        {
            using (var db = new MyContext())
            {
                db.Database.EnsureDeleted();
                db.Database.EnsureCreated();
                var tmp_a = new A[]
                {
                    new A {a = "a0", a1 = "a1", forkey = "a"},
                    new A {a = "a2", a1 = "a1", forkey = "d"},
                };
                var tmp_b = new B[]
                {
                    new B {b = "b0", b1 = "b1", forkey = "a"},
                    new B {b = "b2", b1 = "b1", forkey = "c"},
                };
                db.A.AddRange(tmp_a);
                db.B.AddRange(tmp_b);
                db.SaveChanges();
            }

            using (var db = new MyContext())
            {
                var query=db.A.AsNoTracking().FullJoin(db.B, (x)=>x.forkey, (y)=>y.forkey, (u, v) => new { left=u,right=v }).ToList();
            }
        }
    }

    public class MyContext : DbContext
    {
        private static ILoggerFactory ContextLoggerFactory
            => LoggerFactory.Create(b =>
            {
                b
                    .AddConsole()
                    .AddFilter("", LogLevel.Debug);
            });

        public DbSet<A> A { get; set; }
        public DbSet<B> B { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            // Select 1 provider
            optionsBuilder
                .UseSqlServer(
                    @"Server=(localdb)\mssqllocaldb;Database=_ModelApp;Trusted_Connection=True;Connect Timeout=5;ConnectRetryCount=0")
                .EnableSensitiveDataLogging()
                .UseLoggerFactory(ContextLoggerFactory);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Configure model
        }
    }

    public class A
    {
        public int Id { get; set; }
        public string a { get; set; }
        public string a1 { get; set; }
        public string forkey { get; set; }

    }

    public class B
    {
        public int Id { get; set; }
        public string b { get; set; }
        public string b1 { get; set; }
        public string forkey { get; set; }
    }
}

Throw Exception:
System.InvalidOperationException:When performing a set operation, both operands must have the same Include operations.”

   at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ProcessSetOperation(NavigationExpansionExpression outerSource, MethodInfo genericMethod, NavigationExpansionExpression innerSource)
   at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.Expand(Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)

@roji roji changed the title EF CORE 3.1 System.InvalidOperationException:“Unable to track an entity of type 'A' because primary key property 'Id' is null.” Set operations need to assign correct nullability to shaper Dec 12, 2019
@roji roji self-assigned this Dec 12, 2019
@ajcvickers ajcvickers added this to the 5.0.0 milestone Dec 13, 2019
@ajcvickers ajcvickers modified the milestones: 5.0.0, Backlog Jun 9, 2020
@ajcvickers ajcvickers modified the milestones: Backlog, 6.0.0 Nov 2, 2020
@smitpatel smitpatel added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Nov 6, 2020
@smitpatel smitpatel assigned smitpatel and unassigned roji Nov 6, 2020
smitpatel added a commit that referenced this issue Nov 6, 2020
Also improved In-Memory set operation implementation

Resolves #19253
@ghost ghost closed this as completed in #23227 Nov 19, 2020
ghost pushed a commit that referenced this issue Nov 19, 2020
…ns (#23227)

Also improved In-Memory set operation implementation

Resolves #19253
@ajcvickers ajcvickers modified the milestones: 6.0.0, 6.0.0-preview1 Jan 27, 2021
@ajcvickers ajcvickers modified the milestones: 6.0.0-preview1, 6.0.0 Nov 8, 2021
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported punted-for-5.0 type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants