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>