Skip to content

Commit a5e7f62

Browse files
authored
Merge pull request #87 from DataObjects-NET/dto-ctor-with-interface-field-usage
Addresses issue when assigning value for DTO field which implements interface can cause error
2 parents f263df5 + b667edc commit a5e7f62

File tree

4 files changed

+187
-24
lines changed

4 files changed

+187
-24
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright (C) 2020 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
5+
using NUnit.Framework;
6+
using System;
7+
using System.Linq;
8+
using Xtensive.Orm;
9+
using Xtensive.Orm.Configuration;
10+
using Xtensive.Orm.Tests;
11+
using Xtensive.Orm.Tests.Issues.IssueJira0800_InterfaceImplementingDtoProblemModel;
12+
13+
namespace Xtensive.Orm.Tests.Issues
14+
{
15+
[TestFixture]
16+
public class IssueJira0800_DtoInterfaceFieldInitializationWithinCtor : AutoBuildTest
17+
{
18+
protected override DomainConfiguration BuildConfiguration()
19+
{
20+
var config = base.BuildConfiguration();
21+
config.Types.Register(typeof(Example));
22+
config.UpgradeMode = DomainUpgradeMode.Recreate;
23+
return config;
24+
}
25+
26+
[Test]
27+
public void FilterThroughInterfaceMethod()
28+
{
29+
using (var session = Domain.OpenSession())
30+
using (var transaction = session.OpenTransaction()) {
31+
var query = session.Query
32+
.All<Example>()
33+
.Select(x => new ExampleDto { EndDate = x.EndDate })
34+
.ApplyFilterUsingInterface();
35+
var expression = query.Expression;
36+
Console.WriteLine(expression);
37+
_ = query.ToArray();
38+
}
39+
}
40+
41+
[Test]
42+
public void FilterThroughBaseClassMethod()
43+
{
44+
using (var session = Domain.OpenSession())
45+
using (var transaction = session.OpenTransaction()) {
46+
var query = session.Query
47+
.All<Example>()
48+
.Select(x => new ExampleDto { EndDate = x.EndDate })
49+
.ApplyFilterUsingBaseClass();
50+
var expression = query.Expression;
51+
Console.WriteLine(expression);
52+
_ = query.ToArray();
53+
}
54+
}
55+
56+
[Test]
57+
public void FilterDirectlyWithInterface()
58+
{
59+
using (var session = Domain.OpenSession())
60+
using (var transaction = session.OpenTransaction()) {
61+
var query = session.Query
62+
.All<Example>()
63+
.Select(x => (IEndOwner) new ExampleDto { EndDate = x.EndDate })
64+
.Where(x => x.EndDate == null);
65+
var expression = query.Expression;
66+
Console.WriteLine(expression);
67+
_ = query.ToArray();
68+
}
69+
}
70+
71+
[Test]
72+
public void FilterDirectlyWithExactType()
73+
{
74+
using (var session = Domain.OpenSession())
75+
using (var transaction = session.OpenTransaction()) {
76+
var query = session.Query
77+
.All<Example>()
78+
.Select(x => new ExampleDto { EndDate = x.EndDate })
79+
.Where(x => x.EndDate == null);
80+
var expression = query.Expression;
81+
Console.WriteLine(expression);
82+
_ = query.ToArray();
83+
}
84+
}
85+
}
86+
}
87+
88+
namespace Xtensive.Orm.Tests.Issues.IssueJira0800_InterfaceImplementingDtoProblemModel
89+
{
90+
public class ExampleDto : EndOwner
91+
{
92+
public long Id { get; set; }
93+
94+
public override DateTime? EndDate { get; set; }
95+
}
96+
97+
[HierarchyRoot]
98+
public class Example : Entity
99+
{
100+
[Field, Key]
101+
public long Id { get; set; }
102+
103+
[Field]
104+
public DateTime? EndDate { get; set; }
105+
}
106+
107+
public static class ContractsExtensions
108+
{
109+
public static IQueryable<TContractOwner> ApplyFilterUsingBaseClass<TContractOwner>(this IQueryable<TContractOwner> query)
110+
where TContractOwner : EndOwner
111+
{
112+
return query.Where(x => x.EndDate == null);
113+
}
114+
115+
public static IQueryable<TContractOwner> ApplyFilterUsingInterface<TContractOwner>(this IQueryable<TContractOwner> query)
116+
where TContractOwner : class, IEndOwner
117+
{
118+
return query.Where(x => x.EndDate == null);
119+
}
120+
}
121+
122+
public abstract class EndOwner : IEndOwner
123+
{
124+
public abstract DateTime? EndDate { get; set; }
125+
}
126+
127+
128+
public interface IEndOwner
129+
{
130+
DateTime? EndDate { get; }
131+
}
132+
}

Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,33 +1228,36 @@ protected override Expression VisitMemberInit(MemberInitExpression mi)
12281228
/// <exception cref="InvalidOperationException"><c>InvalidOperationException</c>.</exception>
12291229
private Expression GetMember(Expression expression, MemberInfo member, Expression sourceExpression)
12301230
{
1231-
if (expression==null)
1231+
if (expression == null) {
12321232
return null;
1233+
}
12331234

1234-
MarkerType markerType;
12351235
expression = expression.StripCasts();
1236-
bool isMarker = expression.TryGetMarker(out markerType);
1236+
var isMarker = expression.TryGetMarker(out var markerType);
12371237
expression = expression.StripMarkers();
12381238
expression = expression.StripCasts();
12391239

12401240
if (expression.IsAnonymousConstructor()) {
12411241
var newExpression = (NewExpression) expression;
1242-
int memberIndex = newExpression.Members.IndexOf(member);
1242+
var memberIndex = newExpression.Members.IndexOf(member);
12431243
if (memberIndex < 0)
12441244
throw new InvalidOperationException(string.Format(Strings.ExCouldNotGetMemberXFromExpression, member));
12451245
var argument = Visit(newExpression.Arguments[memberIndex]);
12461246
return isMarker ? new MarkerExpression(argument, markerType) : argument;
12471247
}
12481248

12491249
var extendedExpression = expression as ExtendedExpression;
1250-
if (extendedExpression==null)
1250+
if (extendedExpression == null) {
12511251
return IsConditionalOrWellknown(expression)
12521252
? GetConditionalMember(expression, member, sourceExpression)
12531253
: null;
1254+
}
12541255

12551256
Expression result = null;
1256-
Func<PersistentFieldExpression, bool> propertyFilter
1257-
= f => f.Name==context.Domain.Handlers.NameBuilder.BuildFieldName((PropertyInfo) member);
1257+
bool propertyFilter(PersistentFieldExpression f)
1258+
{
1259+
return f.Name == context.Domain.Handlers.NameBuilder.BuildFieldName((PropertyInfo) member);
1260+
}
12581261

12591262
switch (extendedExpression.ExtendedType) {
12601263
case ExtendedExpressionType.FullText:
@@ -1266,22 +1269,36 @@ Func<PersistentFieldExpression, bool> propertyFilter
12661269
}
12671270
break;
12681271
case ExtendedExpressionType.Grouping:
1269-
if (member.Name=="Key")
1272+
if (member.Name == "Key") {
12701273
return ((GroupingExpression) expression).KeyExpression;
1274+
}
12711275
break;
12721276
case ExtendedExpressionType.Constructor:
1273-
var bindings = ((ConstructorExpression) extendedExpression).Bindings;
1274-
// only make sure that type has needed member
1275-
if (!bindings.TryGetValue(member, out result)) {
1276-
// Key in bindings might be a property/field reflected from a base type
1277-
// but our member might be reflected from child type.
1278-
var baseMember = member.DeclaringType.GetMember(member.Name).FirstOrDefault();
1279-
if (baseMember==null)
1280-
throw new InvalidOperationException(string.Format(
1281-
Strings.ExMemberXOfTypeYIsNotInitializedCheckIfConstructorArgumentIsCorrectOrFieldInitializedThroughInitializer,
1282-
member.Name, member.ReflectedType.Name));
1283-
}
1284-
result = Visit(result);
1277+
var nativeExpression = ((ConstructorExpression) extendedExpression);
1278+
var bindings = nativeExpression.Bindings;
1279+
// only make sure that type has needed member
1280+
if (!bindings.TryGetValue(member, out result)) {
1281+
// Key in bindings might be a property/field reflected from a base type
1282+
// but our member might be reflected from child type.
1283+
var baseType = member.DeclaringType;
1284+
if (baseType.IsInterface) {
1285+
var implementor = member.GetImplementation(nativeExpression.Type);
1286+
if (implementor == null) {
1287+
throw new InvalidOperationException(string.Format(Strings.ExThereIsNoImplemetationOfXYMemberInZType,
1288+
member.DeclaringType.Name, member.Name, nativeExpression.Type.ToString()));
1289+
}
1290+
_ = bindings.TryGetValue(implementor, out result);
1291+
}
1292+
else {
1293+
var baseMember = baseType.GetMember(member.Name).FirstOrDefault();
1294+
if (baseMember == null) {
1295+
throw new InvalidOperationException(string.Format(
1296+
Strings.ExMemberXOfTypeYIsNotInitializedCheckIfConstructorArgumentIsCorrectOrFieldInitializedThroughInitializer,
1297+
member.Name, member.ReflectedType.Name));
1298+
}
1299+
}
1300+
}
1301+
result = Visit(result);
12851302
break;
12861303
case ExtendedExpressionType.Structure:
12871304
case ExtendedExpressionType.StructureField:
@@ -1295,21 +1312,23 @@ Func<PersistentFieldExpression, bool> propertyFilter
12951312
case ExtendedExpressionType.Entity:
12961313
var entityExpression = (EntityExpression) expression;
12971314
result = entityExpression.Fields.FirstOrDefault(propertyFilter);
1298-
if (result==null) {
1315+
if (result == null) {
12991316
EnsureEntityFieldsAreJoined(entityExpression);
13001317
result = entityExpression.Fields.First(propertyFilter);
13011318
}
13021319
break;
13031320
case ExtendedExpressionType.Field:
1304-
if (isMarker && ((markerType & MarkerType.Single)==MarkerType.Single))
1321+
if (isMarker && ((markerType & MarkerType.Single) == MarkerType.Single)) {
13051322
throw new InvalidOperationException(string.Format(Strings.ExUseMethodXOnFirstInsteadOfSingle, sourceExpression.ToString(true), member.Name));
1306-
if (member.DeclaringType.IsGenericType && member.DeclaringType.GetGenericTypeDefinition()==typeof (Nullable<>))
1323+
}
1324+
if (TypeHelper.IsNullable(member.DeclaringType)) {
13071325
expression = Expression.Convert(expression, member.DeclaringType);
1326+
}
13081327
return Expression.MakeMemberAccess(expression, member);
13091328
case ExtendedExpressionType.EntityField:
13101329
var entityFieldExpression = (EntityFieldExpression) expression;
13111330
result = entityFieldExpression.Fields.FirstOrDefault(propertyFilter);
1312-
if (result==null) {
1331+
if (result == null) {
13131332
EnsureEntityReferenceIsJoined(entityFieldExpression);
13141333
result = entityFieldExpression.Entity.Fields.First(propertyFilter);
13151334
}

Orm/Xtensive.Orm/Strings.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Orm/Xtensive.Orm/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3449,4 +3449,7 @@ Error: {1}</value>
34493449
<data name="ExCannotApplyNodeConfigurationSettingsConnectionIsInUse" xml:space="preserve">
34503450
<value>Cannot apply node configuration: connection is already in use. This may happen if the session was open asynchronously. Use new StorageNode.OpenSession(...) API instead of Session.SelectStorageNode(...)</value>
34513451
</data>
3452+
<data name="ExThereIsNoImplemetationOfXYMemberInZType" xml:space="preserve">
3453+
<value>There is no implemetation of '{0}.{1}' member in '{0}' type.</value>
3454+
</data>
34523455
</root>

0 commit comments

Comments
 (0)