Hello,

Now I'm diving into this DataLoadOptions land with a patch.

Basically, the patch adds support for validation of LoadWith() argument
that should 1) allow only property or field access and 2) reject cyclic
loading. They are with tests and I think this part is ready to get
committed. (Note that it does not implement the actual eager loading.)

A problem that I found during this hack was that there are two 
EntitySet<T> and EntityRef<T>: one in DbLinq.Data.Linq and another in
System.Data.Linq.

To my understanding, this kind of conflicts are usually resolved as

        - DbLinq.Data.Linq.EntitySet<T> in non-strict mode and
        - System.Data.Linq.EntitySet<T> in strict mode.

But what I've found in running Test_NUnit_MsSql was that 
System.Data.Linq.EntitySet<T> was used in non-strict mode. Hence I had
to add dirty #if !MONO_STRICT block in DataLoadOptions.cs.

Is this kind of complexity expected? Or is it some kind of bug that
System.Data.Linq.EntitySet<T> is populated in non-strict mode?

And another question - is there any ongoing effort on eager loading
i.e. inner join and left join? ;)

Atsushi Eno

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"DbLinq" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/dblinq?hl=en
-~----------~----~----~----~------~----~------~--~---

Index: tests/Test_NUnit/DataLoadOptions_Test.cs
===================================================================
--- tests/Test_NUnit/DataLoadOptions_Test.cs    (revision 0)
+++ tests/Test_NUnit/DataLoadOptions_Test.cs    (revision 0)
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using Test_NUnit;
+
+#if !MONO_STRICT
+using nwind;
+using DbLinq.Data.Linq;
+#else
+using MsNorthwind;
+using System.Data.Linq;
+#endif
+
+#if MONO_STRICT
+namespace MsNorthwind
+#else
+namespace nwind
+#endif
+{
+    public partial class Customer
+    {
+        public object ExtraneousMethod()
+        {
+            return null;
+        }
+    }
+}
+
+#if MYSQL
+    namespace Test_NUnit_MySql
+#elif ORACLE
+#if ODP
+        namespace Test_NUnit_OracleODP
+#else
+        namespace Test_NUnit_Oracle
+#endif
+#elif POSTGRES
+namespace Test_NUnit_PostgreSql
+#elif SQLITE
+    namespace Test_NUnit_Sqlite
+#elif INGRES
+    namespace Test_NUnit_Ingres
+#elif MSSQL
+#if MONO_STRICT
+namespace Test_NUnit_MsSql_Strict
+#else
+    namespace Test_NUnit_MsSql
+#endif
+#elif FIREBIRD
+    namespace Test_NUnit_Firebird
+#else
+    #error unknown target
+#endif
+{
+    [TestFixture]
+    public class DataLoadOptions_Test : TestBase
+    {
+        static object ThrowException()
+        {
+            throw new ApplicationException();
+        }
+
+        [Test]
+        [ExpectedException(typeof(InvalidOperationException))]
+        public void LoadWith_BadExpression1()
+        {
+            new DataLoadOptions().LoadWith<Customer>(cc => 
cc.ExtraneousMethod());
+        }
+
+        [Test]
+        [ExpectedException(typeof(InvalidOperationException))]
+        public void LoadWith_BadExpression2()
+        {
+            new DataLoadOptions().LoadWith<Customer>(cc => 1);
+        }
+
+        [Test]
+        [ExpectedException(typeof(InvalidOperationException))]
+        public void LoadWith_BadExpression3()
+        {
+            new DataLoadOptions().LoadWith<Customer>(cc => ThrowException());
+        }
+
+        [Test]
+        [ExpectedException(typeof(InvalidOperationException))]
+        public void LoadWith_BadExpression4()
+        {
+            new DataLoadOptions().LoadWith<Customer>(cc => cc.Orders.Select(o 
=> o));
+        }
+
+        [Test]
+        [ExpectedException(typeof(InvalidOperationException))]
+        public void LoadWith_BadExpression5()
+        {
+            new DataLoadOptions().LoadWith<Order> (o => o.Customer.Orders);
+        }
+
+        [Test]
+        [ExpectedException(typeof(InvalidOperationException))]
+        public void LoadWith_BadCycles1()
+        {
+            var lo = new DataLoadOptions();
+            lo.LoadWith<Customer>(c => c.Orders);
+            lo.LoadWith<Order>(o => o.Customer);
+        }
+
+        [Test]
+        [ExpectedException(typeof(InvalidOperationException))]
+        public void LoadWith_BadCycles2()
+        {
+            var lo = new DataLoadOptions();
+            lo.LoadWith<Order>(o => o.Customer);
+            lo.LoadWith<Customer>(c => c.Orders);
+        }
+
+        [Test]
+        public void LoadWith_Good1()
+        {
+            var lo = new DataLoadOptions();
+            lo.LoadWith<Customer>(c => c.Orders);
+            lo.LoadWith<Order>(o => o.Employee);
+        }
+
+        [Test]
+        public void LoadWith_Good2()
+        {
+            var lo = new DataLoadOptions();
+            lo.LoadWith<Order>(o => o.Employee);
+            lo.LoadWith<Customer>(c => c.Orders);
+        }
+    }
+}
Index: tests/Test_NUnit/Test_NUnit_MsSql.csproj
===================================================================
--- tests/Test_NUnit/Test_NUnit_MsSql.csproj    (revision 960)
+++ tests/Test_NUnit/Test_NUnit_MsSql.csproj    (working copy)
@@ -68,6 +68,7 @@
     </Compile>
     <Compile Include="Attach.cs" />
     <Compile Include="CompositePK_Test.cs" />
+    <Compile Include="DataLoadOptions_Test.cs" />
     <Compile Include="ReadTests_AnyCountFirst.cs" />
     <Compile Include="ReadTests_Conversions.cs" />
     <Compile Include="ReadTests_DateTimeFunctions.cs" />
Index: tests/Test_NUnit/ReadTests_EntitySet.cs
===================================================================
--- tests/Test_NUnit/ReadTests_EntitySet.cs     (revision 960)
+++ tests/Test_NUnit/ReadTests_EntitySet.cs     (working copy)
@@ -288,8 +288,8 @@
             db.LoadOptions = loadoptions;
 
             var customer = db.Customers.First();
-            Assert.IsFalse(customer.Orders.IsDeferred);
-            Assert.IsTrue(customer.Orders.HasLoadedOrAssignedValues);
+            Assert.IsFalse(customer.Orders.IsDeferred, "#1");
+            Assert.IsTrue(customer.Orders.HasLoadedOrAssignedValues, "#2");
         }
 
         [Test]
Index: tests/Test_NUnit/Test_NUnit_MsSql_Strict.csproj
===================================================================
--- tests/Test_NUnit/Test_NUnit_MsSql_Strict.csproj     (revision 960)
+++ tests/Test_NUnit/Test_NUnit_MsSql_Strict.csproj     (working copy)
@@ -68,6 +68,7 @@
     </Compile>
     <Compile Include="Attach.cs" />
     <Compile Include="CompositePK_Test.cs" />
+    <Compile Include="DataLoadOptions_Test.cs" />
     <Compile Include="ReadTest_Subquery.cs" />
     <Compile Include="DynamicLinqTest.cs" />
     <Compile Include="ExecuteCommand_Test.cs" />
Index: src/DbLinq/Data/Linq/DataLoadOptions.cs
===================================================================
--- src/DbLinq/Data/Linq/DataLoadOptions.cs     (revision 960)
+++ src/DbLinq/Data/Linq/DataLoadOptions.cs     (working copy)
@@ -100,10 +100,45 @@
         {
             // TODO: ensure we have an EntitySet<>
             var memberInfo = ReflectionUtility.GetMemberInfo(expression);
+            if (memberInfo == null)
+                throw new InvalidOperationException("The argument expression 
must be a property access or a field access where the target object is the 
parameter");
             if (!eagerLoading.Contains(memberInfo))
+            {
+                VerifyMemberAccessCycles(memberInfo);
                 eagerLoading.Add(memberInfo);
+            }
         }
 
+        private void VerifyMemberAccessCycles(MemberInfo member)
+        {
+            var mt = GetMemberEntityType (member);
+            var d = member.DeclaringType;
+            foreach (var m in eagerLoading)
+            {
+                if (m.DeclaringType == mt && GetMemberEntityType (m) == d)
+                    throw new InvalidOperationException("Illegal cycles are 
detected in the argument expression among other eager-loading expressions");
+            }
+        }
+
+        private Type GetMemberEntityType(MemberInfo member)
+        {
+            var mt = member.GetMemberType();
+            if (mt.IsGenericType)
+            {
+#if !MONO_STRICT
+                if (mt.GetGenericTypeDefinition() == typeof(EntitySet<>)) // 
DbLinq.Data.Linq.EntitySet
+                    mt = mt.GetGenericArguments()[0];
+                else if (mt.GetGenericTypeDefinition() == typeof(EntityRef<>)) 
// DbLinq.Data.Linq.EntityRef
+                    mt = mt.GetGenericArguments()[0];
+#endif
+                if (mt.GetGenericTypeDefinition() == 
typeof(System.Data.Linq.EntitySet<>))
+                    mt = mt.GetGenericArguments()[0];
+                else if (mt.GetGenericTypeDefinition() == 
typeof(System.Data.Linq.EntityRef<>))
+                    mt = mt.GetGenericArguments()[0];
+            }
+            return mt;
+        }
+
         /// <summary>
         /// Tells if we do eager or lazy loading
         /// </summary>
Index: src/DbLinq/Data/Linq/EntityRef.cs
===================================================================
--- src/DbLinq/Data/Linq/EntityRef.cs   (revision 960)
+++ src/DbLinq/Data/Linq/EntityRef.cs   (working copy)
@@ -54,7 +54,6 @@
             hasLoadedOrAssignedValue = true;
         }
 
-        [DbLinqToDo]
         public EntityRef(IEnumerable<TEntity> source)
         {
             this.source = source;
@@ -64,9 +63,14 @@
 
         public EntityRef(EntityRef<TEntity> entityRef)
         {
-            this.source = null;
-            this.entity = entityRef.Entity;
-            hasLoadedOrAssignedValue = true;
+            this.entity = entityRef.entity;
+            if (entityRef.entity == null && entityRef.source is ICloneable)
+            {
+                source = 
(IEnumerable<TEntity>)((ICloneable)entityRef.source).Clone();
+            }
+            else
+                source = null;
+            hasLoadedOrAssignedValue = entityRef.hasLoadedOrAssignedValue;
         }
 
         public TEntity Entity
Index: src/DbLinq/Util/ReflectionUtility.cs
===================================================================
--- src/DbLinq/Util/ReflectionUtility.cs        (revision 960)
+++ src/DbLinq/Util/ReflectionUtility.cs        (working copy)
@@ -66,34 +66,39 @@
         }
 
         /// <summary>
-        /// Extracts a MemberInfo with more or less digging
+        /// Returns MemberInfo specified in the lambda, optional parameter 
expression constraint.
         /// </summary>
         /// <param name="expression"></param>
         /// <returns></returns>
-        private static MemberInfo GetMemberInfo(Expression expression)
+        public static MemberInfo GetMemberInfo(LambdaExpression expression)
         {
-            switch (expression.NodeType)
-            {
-            // the ReflectionUtility Get** methods return the value as a 
object. If the value is a struct, we get a cast,
-            // that we must unwrap
-            case ExpressionType.Convert:
-            case ExpressionType.ConvertChecked:
-                return GetMemberInfo(((UnaryExpression)expression).Operand);
-            case ExpressionType.MemberAccess:
-                return ((MemberExpression)expression).Member;
-            default:
-                return null;
-            }
+            var paramExpr = expression.Parameters.Count == 1 ? 
expression.Parameters[0] : null;
+            return GetMemberInfo(paramExpr, expression.Body);
         }
 
         /// <summary>
-        /// Returns MemberInfo specified in the lambda
+        /// Returns MemberInfo specified in the lambda, optional parameter 
expression constraint.
         /// </summary>
         /// <param name="lambdaExpression"></param>
         /// <returns></returns>
-        public static MemberInfo GetMemberInfo(LambdaExpression 
lambdaExpression)
+        private static MemberInfo GetMemberInfo(Expression optionalParamExpr, 
Expression expression)
         {
-            return GetMemberInfo(lambdaExpression.Body);
+            switch (expression.NodeType)
+            {
+                // the ReflectionUtility Get** methods return the value as a 
object. If the value is a struct, we get a cast,
+                // that we must unwrap
+                case ExpressionType.Convert:
+                case ExpressionType.ConvertChecked:
+                    return GetMemberInfo(optionalParamExpr, 
((UnaryExpression)expression).Operand);
+                case ExpressionType.MemberAccess:
+                    var me = (MemberExpression)expression;
+                    if (optionalParamExpr == null || me.Expression == 
optionalParamExpr)
+                        return me.Member;
+                    else
+                        return null;
+                default:
+                    return null;
+            }
         }
 
         /// <summary>

Reply via email to