Linqkit: Prevent circular references and stackoverflow - c#

I'm using LinqKit with EntityFrameWorkCore to create projections with single element projections. But since some Models are nested I'm getting a stackoverflow. I tried to add a condition to the Projection method (bool mapCollusions) to prevent this, but it seems to be ignored. Does anyone have an idea how to prevent these circluar references?
public static Expression<Func<Transport, TransportModel>> GetMainProjection()
{
return transport => new TransportModel()
{
Inmate = transport.Inmate != null ? InmateProjectionsGetProjection(true).Invoke(transport.Inmate) : null,
};
public static Expression<Func<Inmate, InmateModel>> InmateProjectionsGetProjection(bool mapCollusions)
{
return inmate => new InmateModel()
{
Collusions = mapCollusions ? inmate.Collusions.AsQueryable()
.Select(collusion => CollusionProjectionsGetProjection(false).Invoke(collusion))
.ToList() : null
};
}
public static Expression<Func<Collusion, CollusionModel>> CollusionProjectionsGetProjection(bool mapInmate)
{
return collusion => new CollusionModel()
{
Inmate = mapInmate ? InmateProjectionsGetProjection(false).Invoke(collusion.Inmate) : null,
};
}

Try the following realisation:
public static Expression<Func<Transport, TransportModel>> GetMainProjection()
{
return transport => new TransportModel()
{
Inmate = transport.Inmate != null ? InmateProjectionsGetProjection(true).Invoke(transport.Inmate) : null,
};
}
public static Expression<Func<Inmate, InmateModel>> InmateProjectionsGetProjection(bool mapCollusions)
{
if (!mapCollusions)
return inmate => new InmateModel();
return inmate => new InmateModel()
{
Collusions = nmate.Collusions.AsQueryable()
.Select(collusion => CollusionProjectionsGetProjection(false).Invoke(collusion))
.ToList()
};
}
public static Expression<Func<Collusion, CollusionModel>> CollusionProjectionsGetProjection(bool mapInmate)
{
if (!mapInmate)
return collusion => new CollusionModel();
return collusion => new CollusionModel()
{
Inmate = InmateProjectionsGetProjection(false).Invoke(collusion.Inmate),
};
}

Related

Reusing EF Core Projection Function in Another Function with Arguments

Consider the following Func which takes in the DbSet and returns the result contract:
public static Func<Repository.DataSets.Person, Person> Map(IPersonGetOptions options)
{
return k => new Person()
{
ID = k.ID,
Department = k.Department == null ? null : new Department()
{
ID = k.Department.ID,
};
};
}
Department Func:
public static Func<Repository.DataSets.Department, Department> MapDepartment()
{
return k => new Department()
{
ID = k.ID,
};
}
Usage:
public Person GetById(int id, PersonGetOptions options = null)
{
options ??= new();
var result = db.Persons
.Select(Map(options))
.FirstOrDefault(k => k.ID == id);
return result;
}
// similar for department: ... db.Departments.Select(Map()) ...
I want to reuse MapDepartment in Map in a way EF can translate (I don't want to use AutoMapper).
What I tried:
public static Func<Repository.DataSets.Person, Person> Map(IPersonGetOptions options)
{
return k => new Person()
{
ID = k.ID,
Department = k.Department == null ? null : MapDepartment().Invoke(k.Department)
};
}
This code block won't work as it cannot be translated by efcore (v5). Any idea how to make this work?
With #Svyatoslav Danyliv comment linked to a similar post, What I tried:
With LINQkit, I was able to accomplish a reusable Func without arguments. but it did not seem to be working when adding arguments like options in my example:
This works:
[Expandable(nameof(Map))]
private static Offer MapPerson(Repository.DataSets.Person k)
{
_map ??= Map().Compile();
return _map(k);
}
private static Func<Repository.DataSets.Person, Person> _map;
public static Expression<Func<Repository.DataSets.Person, Person>> Map(){
...
}
// usage
```cs
.Select(k => MapPerson(k))
But when introducing an argument options, it will throw the following:
No method 'Map' on type 'my-namespace.my-class' is compatible with the supplied arguments
Failed code:
private static Offer MapPerson(IPersonGetOptions options, Repository.DataSets.Person k){
_map ??= Map(options).Compile();
return _map(k);
}
private static Func<Repository.DataSets.Person, Person> _map;
public static Expression<Func<Repository.DataSets.Person, Person>> Map(IPersonGetOptions options){
...
}
You can use LINQKit for such task:
[Expandable(nameof(MapPersonImpl))]
public static Person MapPerson(Repository.DataSets.Person p)
{
throw new NotImplementedException();
}
private static Expression<Func<Repository.DataSets.Person, Person>> MapPersonImpl()
{
return p => new Person()
{
ID = p.ID,
Department = p.Department == null ? null : MapDepartment(p.Department)
};
}
[Expandable(nameof(MapDepartmentImpl))]
public static Department MapDepartment(Repository.DataSets.Department d)
{
throw new NotImplementedException();
}
public static Expression<Func<Repository.DataSets.Department, Department>> MapDepartmentImpl()
{
return d => new Department()
{
ID = d.ID,
};
}
Don't forget to enable LINQKit extension:
builder
.UseSqlServer(connectionString)
.WithExpressionExpanding(); // enabling LINQKit extension

Unit Testing - Some difficulties mocking

I'm working with some legacy code which I require to write some unit tests for. There is data access method with the following signature.
Task ExecuteReaderAsync(string procedureName, Parameters procedureParameters,
params Action<System.Data.IDataReader>[] actions);
which there is an implementation in the class i'm testing similar to this
private async Task<CustomObject> GetCustomObject(int id)
{
CustomObject obj = null;
await db.ExecuteReaderAsync("nameOfProcedure", some parameters,
dr =>
{
obj = new CustomObject()
{
Prop1 = dr["Col1"],
Prop2 = dr["Col2"]
}
}
return obj;
}
What I'm struggling with is being able to control the values returned by GetCustomObject. If ExecuteReaderAsync actually returned something I could have a set up like this.
mockDataAccess.Setup(x => x.ExecuteReaderAsync("nameOfProcedure", It.IsAny<Parameters>()))
.Returns(Task.FromResult(new CustomeObject() { prop1 = "abc", prop2 = "def"};));
But the logic for specifying the values is an Action<IDataReader> which I don't control. I'm wondering if there are any tricks I could employ to do what I want,
i.e. control the value of the object returned by GetCustomObject.
Take a look at the following example
[TestClass]
public class LegacyCodeTest {
[TestMethod]
public async Task TestExecuteReaderAsync() {
//Arrange
var mapping = new Dictionary<string, string> {
{ "Col1", "abc" },
{ "Col2", "def" }
};
var mockDataReader = new Mock<IDataReader>();
mockDataReader
.Setup(m => m[It.IsAny<string>()])
.Returns<string>(col => mapping[col])
.Verifiable();
var mockDataAccess = new Mock<IDataAccess>();
mockDataAccess
.Setup(m => m.ExecuteReaderAsync("nameOfProcedure", It.IsAny<Parameters>(), It.IsAny<Action<System.Data.IDataReader>[]>()))
.Returns(Task.FromResult<object>(null))
.Callback((string s, Parameters p, Action<System.Data.IDataReader>[] a) => {
if (a != null && a.Length > 0) {
a.ToList().ForEach(callback => callback(mockDataReader.Object));
}
})
.Verifiable();
var sut = new SUT(mockDataAccess.Object);
//Act
var actual = await sut.MUT(2);
//Assert
mockDataAccess.Verify();
mockDataReader.Verify(m => m["Col1"]);
mockDataReader.Verify(m => m["Col2"]);
actual.Should()
.NotBeNull()
.And
.Match<CustomObject>(c => c.Prop1 == mapping["Col1"] && c.Prop2 == mapping["Col2"]);
}
public interface IDataAccess {
Task ExecuteReaderAsync(string procedureName, Parameters procedureParameters, params Action<System.Data.IDataReader>[] actions);
}
public class Parameters { }
public class CustomObject {
public object Prop1 { get; set; }
public object Prop2 { get; set; }
}
public class SUT {
IDataAccess db;
public SUT(IDataAccess dataAccess) {
this.db = dataAccess;
}
public async Task<CustomObject> MUT(int id) {
var result = await GetCustomObject(id);
return result;
}
private async Task<CustomObject> GetCustomObject(int id) {
CustomObject obj = null;
await db.ExecuteReaderAsync("nameOfProcedure", null,
dr => {
obj = new CustomObject() {
Prop1 = dr["Col1"],
Prop2 = dr["Col2"]
};
});
return obj;
}
}
}
Since you don't have control of the Action<IDataReader>, in this case the most that can be done is to make sure that the action does not fail. So that would mean passing an a mock reader that performs as expected for the action.
var mapping = new Dictionary<string, string> {
{ "Col1", "abc" },
{ "Col2", "def" }
};
var mockDataReader = new Mock<IDataReader>();
mockDataReader
.Setup(m => m[It.IsAny<string>()])
.Returns<string>(col => mapping[col])
.Verifiable();
by using the call back to get access to the passed in parameters
.Callback((string s, Parameters p, Action<System.Data.IDataReader>[] a) => {
if (a != null && a.Length > 0) {
a.ToList().ForEach(callback => callback(mockDataReader.Object));
}
})
the mocked reader can be passed to the actions called within the method under test.
This answer is tailored to the provided example in th OP so some modification may need to be made for it to apply to your specific scenario. This should be enough to get you going for situations like this.

How to add a non-property rule in FluentValidation?

I have this validator class:
internal class CustomerTypeValidator : AbstractValidator<CustomerType>
{
public CustomerTypeValidator()
{
RuleFor(x => x.Number).Must(BeANumber).WithState(x => CustomerTypeError.NoNumber);
}
private bool BeANumber(string number)
{
int temp;
bool ok = int.TryParse(number, out temp);
return ok && temp > 0;
}
}
And I have the service class:
public class CustomerTypeService
{
public CustomerType Save(CustomerType customerType)
{
ValidationResult results = Validate(customerType);
if (results != null && !results.IsValid)
{
throw new ValidationException<CustomerTypeError>(results.Errors);
}
//Save to DB here....
return customerType;
}
public bool IsNumberUnique(CustomerType customerType)
{
var result = customerTypeRepository.SearchFor(x => x.Number == customerType.Number).Where(x => x.Id != customerType.Id).FirstOrDefault();
return result == null;
}
public ValidationResult Validate(CustomerType customerType)
{
CustomerTypeValidator validator = new CustomerTypeValidator();
validator.RuleFor(x => x).Must(IsNumberUnique).WithState(x => CustomerTypeError.NumberNotUnique);
return validator.Validate(customerType);
}
}
However I get the following exception:
Property name could not be automatically determined for expression x => x. Please specify either a custom property name by calling 'WithName'.
Is the above not the correct way to add an extra rule?
With the current version of FluentValidation, it is possible to solve the above problem by doing the following:
public bool IsNumberUnique(CustomerType customerType, int id)
{
var result = customerTypeRepository.SearchFor(x => x.Number == customerType.Number).Where(x => x.Id != customerType.Id).FirstOrDefault();
return result == null;
}
public ValidationResult Validate(CustomerType customerType)
{
CustomerTypeValidator validator = new CustomerTypeValidator();
validator.RuleFor(x => x.Id).Must(IsNumberUnique).WithState(x => CustomerTypeError.NumberNotUnique);
return validator.Validate(customerType);
}

Lambda How to reordering elements in list ascending order and place null value at the back?

Says I have ListA={null,3,2,null}.
ListA.OrderBy(x=>x.ID) //would return me null,null,2,3
If my objective is to get 2,3,null,null, currently I can only think of extracting out the null item, and manually pump into the back.
Is there a clean approach where it will return me 2,3,null,null?
You can use OrderByDescending + ThenBy(assuming that it's aList<int?>):
var orderedList = ListA
.OrderByDescending(x => x.HasValue)
.ThenBy(x => x);
x.HasValue returns true or false where true is higher than false. That's why i'm using OrderByDescending.
If you want to sort the original list i would use List.Sort with a custom Compaison<T> that treats null as highest value:
ListA.Sort((a1, a2) => (a1 ?? int.MaxValue).CompareTo(a2 ?? int.MaxValue));
This is more efficient since it doesn't need to create a new list.
As an alternative to Tim's answer you could write your own IComparer<T> which does the custom sorting algorithm for you.
var array = list.OrderBy(x => x, new NullableIntComparer())
.ToArray();
class NullableIntComparer : IComparer<int?>
{
public int Compare(int? x, int? y)
{
if (x.HasValue && y.HasValue)
{
return x.Value.CompareTo(y.Value);
}
if (x.HasValue)
{
return -1;
}
if (y.HasValue)
{
return 1;
}
return 0;
}
}
Tried the following:
class Program
{
class A
{
public A(){}
public int? ID { get; set; }
}
static void Main(string[] args)
{
var listA = new List<A>
{
new A(){ID = null},
new A(){ID = 2},
new A(){ID = null},
new A(){ID = 3},
};
var result = listA.OrderByDescending(x => x.ID != null).ThenBy(x => x.ID);
foreach (var a in result)
{
Console.WriteLine(a.ID);
}
}
}

LINQ SelectMany and Where extension method ignoring nulls

I have the below example code, and I am interested to know how I can make this any cleaner, possibly through better use of SelectMany(). At this point the QuestionList property will not be null. All I want is a list of answerRows that are not null, but Questions can sometimes be null too.
IEnumerable<IQuestion> questions = survey.QuestionList
.Where(q => q.Questions != null)
.SelectMany(q => q.Questions);
if(questions == null)
return null;
IEnumerable<IAnswerRow> answerRows = questions
.Where(q => q.AnswerRows != null)
.SelectMany(q => q.AnswerRows);
if(answerRows == null)
return null;
I was interested by Jon's comment about Enumerable.SelectMany and Null..
so I wanted to try my example with some fake data to more easily see where the error is, please see the below, specifically how I am using SelectMany() on the result of a SelectMany(), its clearer to me now that the problem was having to make sure you don't use SelectMany() on a null reference, obvious when I actually read the NullReferenceException name :( and finally put things together.
Also while doing this, I realised that the use of try { } catch() { } in this example is useless and as usual Jon Skeet has the answer :) deferred execution..
so if you want to see the exception for row 2, comment out the relevant row 1 bits :P, sorry I couldn't figure out how to stop this error without re-writing the code example.
using System;
using System.Collections.Generic;
using System.Linq;
namespace SelectManyExample
{
class Program
{
static void Main(string[] args)
{
var questionGroupList1 = new List<QuestionGroup>() {
new QuestionGroup() {
Questions = new List<Question>() {
new Question() {
AnswerRows = new List<AnswerRow>() {
new AnswerRow(),
new AnswerRow()
}
},
// empty question, causes cascading SelectMany to throw a NullReferenceException
null,
new Question() {
AnswerRows = new List<AnswerRow>() {
new AnswerRow() {
Answers = new List<Answer>() {
new Answer(),
new Answer()
}
}
}
}
}
}
};
var questionGroupList2 = new List<QuestionGroup>() {
null,
new QuestionGroup()
};
IEnumerable<AnswerRow> answerRows1 = null;
IEnumerable<AnswerRow> answerRows2 = null;
try
{
answerRows1 = questionGroupList1
.SelectMany(q => q.Questions)
.SelectMany(q => q.AnswerRows);
}
catch(Exception e) {
Console.WriteLine("row 1 error = " + e.Message);
}
try
{
answerRows2 = questionGroupList2
.SelectMany(q => q.Questions)
.SelectMany(q => q.AnswerRows);
}
catch (Exception e)
{
Console.WriteLine("row 2 error = " + e.Message);
}
Console.WriteLine("row 1: " + answerRows1.Count());
Console.WriteLine("row 2: " + answerRows2.Count());
Console.ReadLine();
}
}
public class QuestionGroup {
public IEnumerable<Question> Questions { get; set; }
}
public class Question {
public IEnumerable<AnswerRow> AnswerRows { get; set; }
}
public class AnswerRow {
public IEnumerable<Answer> Answers { get; set; }
}
public class Answer {
public string Name { get; set; }
}
}
survey.QuestionList
.Where(l => l.Questions != null)
.SelectMany(l => l.Questions)
.Where(q => q != null && q.AnswerRows != null)
.SelectMany(q => q.AnswerRows);
I'd recommend you ensure your collections are never null. null can be a bit of a nuisance if you don't handle it well. You end up with if (something != null) {} all over your code. Then use:
survey.QuestionList
.SelectMany(l => l.Questions)
.SelectMany(q => q.AnswerRows);
A solution that complies with DRY would be to use the null-coalescing operator ?? in your SelectMany lambda expression.
IEnumerable<IQuestion> questions = survey.QuestionList.SelectMany(q => q.Questions ?? Enumerable.Empty<IQuestion>());
IEnumerable<IAnswerRow> answerRows = questions.SelectMany(q => q.AnswerRows ?? Enumerable.Empty<IAnswerRow>());
In both the OP's code and the above code, questions and answerRows will never be null, so the null checks are not required (you may wish to put .Any() checks depending on your business logic). But the above code will also never result in an exception if q.Questions or q.AnswerRows is null.
public static IEnumerable<TResult> SelectNotNull<TSource, TResult>(
this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
where TResult : class
{
return source.Select(selector)
.Where(sequence => sequence != null)
.SelectMany(x => x)
.Where(item => item != null);
}
This then allows you to do the following:
var allAnswers = survey.QuestionList
.SelectNotNull(list => list.Questions)
.SelectNotNull(question => question.AnswerRows);
I'd like to use something short and reusable:
public static IEnumerable<T> OrEmpty<T>(this IEnumerable<T> enumerable)
{
return enumerable ?? Enumerable.Empty<T>();
}
And then in code it'll look like:
survey.QuestionList.SelectMany(q => q.Questions.OrEmpty())

Categories