diff --git a/docs/documents/querying/linq/index.md b/docs/documents/querying/linq/index.md index badda06657..a955617407 100644 --- a/docs/documents/querying/linq/index.md +++ b/docs/documents/querying/linq/index.md @@ -16,7 +16,7 @@ implements the traditional [IQueryable](https://msdn.microsoft.com/en-us/library /// IMartenQueryable Query(); ``` -snippet source | anchor +snippet source | anchor To query for all documents of a type - not that you would do this very often outside of testing - use the `Query()` method like this: diff --git a/src/LinqTests/Bugs/Bug_3151_using_duplicated_fields_in_linq_queries.cs b/src/LinqTests/Bugs/Bug_3151_using_duplicated_fields_in_linq_queries.cs new file mode 100644 index 0000000000..8aada84c10 --- /dev/null +++ b/src/LinqTests/Bugs/Bug_3151_using_duplicated_fields_in_linq_queries.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Marten; +using Marten.Schema; +using Marten.Testing.Harness; +using Shouldly; +using Xunit.Abstractions; + +namespace LinqTests.Bugs; + +public class Bug_3151_using_duplicated_fields_in_linq_queries : BugIntegrationContext +{ + private readonly ITestOutputHelper _output; + + public Bug_3151_using_duplicated_fields_in_linq_queries(ITestOutputHelper output) + { + _output = output; + } + + + [Fact] + public async Task filtering_by_null_ids_works() + { + var p1 = new Person { ChildId = Guid.NewGuid()}; + var p2 = new Person { ChildId = Guid.NewGuid()}; + theSession.Store(p1,p2); + await theSession.SaveChangesAsync(); + Guid?[] ids = [p1.ChildId]; + var results = await theSession.Query() + .Where(x => x.ChildId.In(ids)) + .ToListAsync(); + results.Single().ChildId.ShouldBe(p1.ChildId); + } + + [Fact] + public async Task filtering_by_non_null_ids_using_value_fails_regression_from_641() + { + var p1 = new Person { ChildId = Guid.NewGuid()}; + var p2 = new Person { ChildId = Guid.NewGuid()}; + theSession.Store(p1,p2); + await theSession.SaveChangesAsync(); + Guid[] ids = [p1.ChildId.Value]; + var results = await theSession.Query() + .Where(x => x.ChildId!.Value.In(ids)) + .ToListAsync(); + results.Single().ChildId.ShouldBe(p1.ChildId); + } + + [Fact] + public async Task Bug_3150_query_multiple_times() + { + // if you call BeginTransactionAsync then the issue goes away.. + //await theSession.BeginTransactionAsync(CancellationToken.None); + + async Task DoQuery() + { + var children = new Dictionary(); + await theSession.Query() + .Include(x => x.ChildId!, children) + .ToListAsync(); + } + + theSession.Logger = new TestOutputMartenLogger(_output); + + await DoQuery(); + + // second invocation throws MartenCommandException "42P01: relation "mt_temp_id_list1" does not exist" + await DoQuery(); + } +} + +public class Person +{ + public Guid Id { get; set; } + + [DuplicateField] + public Guid? ChildId { get; set; } +} + +public class Person3150 +{ + public Guid Id { get; set; } + + [DuplicateField] + public string? Name { get; set; } + public Guid? ChildId { get; set; } +} diff --git a/src/Marten/Internal/Storage/DocumentStorage.cs b/src/Marten/Internal/Storage/DocumentStorage.cs index 97f0258d4b..ad74146983 100644 --- a/src/Marten/Internal/Storage/DocumentStorage.cs +++ b/src/Marten/Internal/Storage/DocumentStorage.cs @@ -46,6 +46,7 @@ public abstract class DocumentStorage: IDocumentStorage, IHaveMe private readonly string[] _selectFields; private ISqlFragment? _defaultWhere; protected Action _setter; + private readonly DocumentMapping _document; public DocumentStorage(StorageStyle storageStyle, DocumentMapping document) { @@ -65,20 +66,7 @@ public DocumentStorage(StorageStyle storageStyle, DocumentMapping document) var fieldSelector = _selectFields.Join(", "); _selectClause = $"select {fieldSelector} from {document.TableName.QualifiedName} as d"; - - if (DuplicatedFields.Any()) - { - var duplicatedFields = DuplicatedFields.Select(x => "d." + x.ColumnName).Where(x => !_selectFields.Contains(x)); - var allFields = _selectFields.Concat(duplicatedFields).ToArray(); - SelectClauseWithDuplicatedFields = new DuplicatedFieldSelectClause(TableName.QualifiedName, $"select {allFields.Join(", ")} from {document.TableName.QualifiedName} as d", - allFields, typeof(T), this); - } - else - { - SelectClauseWithDuplicatedFields = this; - } - - + _document = document; _loaderSql = $"select {fieldSelector} from {document.TableName.QualifiedName} as d where id = $1"; @@ -107,7 +95,26 @@ public DocumentStorage(StorageStyle storageStyle, DocumentMapping document) public bool UseNumericRevisions { get; } - public ISelectClause SelectClauseWithDuplicatedFields { get; } + // TODO -- convert to a method in V8 + // this has to be a new instance every time because of how it gets the FromObject + // renamed in Include() batches + public ISelectClause SelectClauseWithDuplicatedFields + { + get + { + if (DuplicatedFields.Any()) + { + var duplicatedFields = DuplicatedFields.Select(x => "d." + x.ColumnName).Where(x => !_selectFields.Contains(x)); + var allFields = _selectFields.Concat(duplicatedFields).ToArray(); + return new DuplicatedFieldSelectClause(TableName.QualifiedName, $"select {allFields.Join(", ")} from {_document.TableName.QualifiedName} as d", + allFields, typeof(T), this); + } + else + { + return this; + } + } + } MetadataColumn[] IHaveMetadataColumns.MetadataColumns() { diff --git a/src/Marten/Linq/Members/DuplicatedField.cs b/src/Marten/Linq/Members/DuplicatedField.cs index 65df128a43..867c25cbac 100644 --- a/src/Marten/Linq/Members/DuplicatedField.cs +++ b/src/Marten/Linq/Members/DuplicatedField.cs @@ -216,6 +216,8 @@ public virtual TableColumn ToColumn() public IQueryableMember FindMember(MemberInfo member) { + if (member.Name == "Value") return this; + // Only really using this for string ToLower() and ToUpper() if (MemberType == typeof(string)) {