Advancing well next we need to check null before calling toList()

This commit is contained in:
David Lebée 2018-03-22 18:16:57 -05:00
parent be22a2a0dd
commit 190ac86bc4
7 changed files with 110 additions and 34 deletions

View File

@ -68,4 +68,22 @@ namespace PoweredSoft.DynamicLinq.Dal.Configurations
HasRequired(t => t.Post).WithMany(t => t.Comments).HasForeignKey(t => t.PostId).WillCascadeOnDelete(false); HasRequired(t => t.Post).WithMany(t => t.Comments).HasForeignKey(t => t.PostId).WillCascadeOnDelete(false);
} }
} }
public class CommentLikeConfiguration : EntityTypeConfiguration<CommentLike>
{
public CommentLikeConfiguration() : this("dbo")
{
}
public CommentLikeConfiguration(string schema)
{
ToTable("CommentLike", schema);
HasKey(t => t.Id);
Property(t => t.Id).HasColumnName("Id").HasColumnType("bigint").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(t => t.CommentId).HasColumnName("CommentId").HasColumnType("bigint").IsRequired();
Property(t => t.CreateTime).HasColumnName("CreateTime").HasColumnType("datetimeoffset").IsRequired();
HasRequired(t => t.Comment).WithMany(t => t.CommentLikes).HasForeignKey(t => t.CommentId).WillCascadeOnDelete(false);
}
}
} }

View File

@ -14,5 +14,6 @@ namespace PoweredSoft.DynamicLinq.Dal.Pocos
public string Email { get; set; } public string Email { get; set; }
public string CommentText { get; set; } public string CommentText { get; set; }
public Post Post { get; set; } public Post Post { get; set; }
public ICollection<CommentLike> CommentLikes { get; set; }
} }
} }

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PoweredSoft.DynamicLinq.Dal.Pocos
{
public class CommentLike
{
public long Id { get; set; }
public long CommentId { get; set; }
public DateTimeOffset CreateTime { get; set; }
public virtual Comment Comment { get; set; }
}
}

View File

@ -50,6 +50,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="BlogContext.cs" /> <Compile Include="BlogContext.cs" />
<Compile Include="Configurations\Configurations.cs" /> <Compile Include="Configurations\Configurations.cs" />
<Compile Include="Pocos\CommentLike.cs" />
<Compile Include="Pocos\Post.cs" /> <Compile Include="Pocos\Post.cs" />
<Compile Include="Pocos\Author.cs" /> <Compile Include="Pocos\Author.cs" />
<Compile Include="Pocos\Comment.cs" /> <Compile Include="Pocos\Comment.cs" />

View File

@ -80,8 +80,20 @@ namespace PoweredSoft.DynamicLinq.Test
[TestMethod] [TestMethod]
public void SelectNullChecking() public void SelectNullChecking()
{ {
// TODO: null checking in select operations :D var query = TestData.Authors.AsQueryable();
throw new Exception("TODO");
query.Select(t => new
{
Comments = t.Posts == null ? null : t.Posts.SelectMany(t2 => t2.Comments).ToList()
});
var querySelect = query.Select(t =>
{
t.NullChecking(true);
t.PathToList("Posts.Comments", selectCollectionHandling: SelectCollectionHandling.SelectMany);
});
var list = querySelect.ToDynamicClassList();
} }
} }
} }

View File

@ -20,13 +20,20 @@ namespace PoweredSoft.DynamicLinq.Fluent
public Type DestinationType { get; set; } public Type DestinationType { get; set; }
public bool Empty => Parts?.Count == 0; public bool Empty => Parts?.Count == 0;
public IQueryable Query { get; protected set; } public IQueryable Query { get; protected set; }
public bool IsNullCheckingEnabled { get; protected set; }
public SelectBuilder(IQueryable query) public SelectBuilder(IQueryable query)
{ {
Query = query; Query = query;
} }
protected void throwIfUsedOrEmpty(string propertyName) public SelectBuilder NullChecking(bool check = true)
{
IsNullCheckingEnabled = check;
return this;
}
protected void ThrowIfUsedOrEmpty(string propertyName)
{ {
if (string.IsNullOrEmpty(propertyName)) if (string.IsNullOrEmpty(propertyName))
throw new ArgumentNullException($"{propertyName} cannot end up be empty."); throw new ArgumentNullException($"{propertyName} cannot end up be empty.");
@ -40,7 +47,7 @@ namespace PoweredSoft.DynamicLinq.Fluent
if (propertyName == null) if (propertyName == null)
propertyName = path.Split('.').LastOrDefault(); propertyName = path.Split('.').LastOrDefault();
throwIfUsedOrEmpty(propertyName); ThrowIfUsedOrEmpty(propertyName);
Parts.Add(new SelectPart Parts.Add(new SelectPart
{ {
@ -57,7 +64,7 @@ namespace PoweredSoft.DynamicLinq.Fluent
if (propertyName == null) if (propertyName == null)
propertyName = path.Split('.').LastOrDefault(); propertyName = path.Split('.').LastOrDefault();
throwIfUsedOrEmpty(propertyName); ThrowIfUsedOrEmpty(propertyName);
Parts.Add(new SelectPart Parts.Add(new SelectPart
{ {
@ -71,7 +78,7 @@ namespace PoweredSoft.DynamicLinq.Fluent
public SelectBuilder Count(string propertyName) public SelectBuilder Count(string propertyName)
{ {
throwIfUsedOrEmpty(propertyName); ThrowIfUsedOrEmpty(propertyName);
Parts.Add(new SelectPart Parts.Add(new SelectPart
{ {
PropertyName = propertyName, PropertyName = propertyName,
@ -82,7 +89,7 @@ namespace PoweredSoft.DynamicLinq.Fluent
public SelectBuilder LongCount(string propertyName) public SelectBuilder LongCount(string propertyName)
{ {
throwIfUsedOrEmpty(propertyName); ThrowIfUsedOrEmpty(propertyName);
Parts.Add(new SelectPart Parts.Add(new SelectPart
{ {
PropertyName = propertyName, PropertyName = propertyName,
@ -96,7 +103,7 @@ namespace PoweredSoft.DynamicLinq.Fluent
if (propertyName == null) if (propertyName == null)
propertyName = path.Split('.').LastOrDefault(); propertyName = path.Split('.').LastOrDefault();
throwIfUsedOrEmpty(propertyName); ThrowIfUsedOrEmpty(propertyName);
Parts.Add(new SelectPart Parts.Add(new SelectPart
{ {
@ -112,7 +119,7 @@ namespace PoweredSoft.DynamicLinq.Fluent
if (propertyName == null) if (propertyName == null)
propertyName = path.Split('.').LastOrDefault(); propertyName = path.Split('.').LastOrDefault();
throwIfUsedOrEmpty(propertyName); ThrowIfUsedOrEmpty(propertyName);
Parts.Add(new SelectPart Parts.Add(new SelectPart
{ {
@ -125,7 +132,7 @@ namespace PoweredSoft.DynamicLinq.Fluent
public SelectBuilder ToList(string propertyName) public SelectBuilder ToList(string propertyName)
{ {
throwIfUsedOrEmpty(propertyName); ThrowIfUsedOrEmpty(propertyName);
Parts.Add(new SelectPart Parts.Add(new SelectPart
{ {
PropertyName = propertyName, PropertyName = propertyName,
@ -139,7 +146,7 @@ namespace PoweredSoft.DynamicLinq.Fluent
if (propertyName == null) if (propertyName == null)
propertyName = path.Split('.').LastOrDefault(); propertyName = path.Split('.').LastOrDefault();
throwIfUsedOrEmpty(propertyName); ThrowIfUsedOrEmpty(propertyName);
Parts.Add(new SelectPart Parts.Add(new SelectPart
{ {
@ -158,7 +165,7 @@ namespace PoweredSoft.DynamicLinq.Fluent
throw new Exception("No select specified, please specify at least one select path"); throw new Exception("No select specified, please specify at least one select path");
var partsTuple = Parts.Select(t => (selectType: t.SelectType, propertyName: t.PropertyName, path: t.Path, selectCollectionHandling: t.SelectCollectionHandling)).ToList(); var partsTuple = Parts.Select(t => (selectType: t.SelectType, propertyName: t.PropertyName, path: t.Path, selectCollectionHandling: t.SelectCollectionHandling)).ToList();
return QueryableHelpers.Select(Query, partsTuple, DestinationType); return QueryableHelpers.Select(Query, partsTuple, DestinationType, nullChecking: IsNullCheckingEnabled);
} }
} }
} }

View File

@ -118,7 +118,7 @@ namespace PoweredSoft.DynamicLinq.Helpers
return result; return result;
} }
public static IQueryable Select(IQueryable query, List<(SelectTypes selectType, string propertyName, string path, SelectCollectionHandling selectCollectionHandling)> parts, Type destinationType = null) public static IQueryable Select(IQueryable query, List<(SelectTypes selectType, string propertyName, string path, SelectCollectionHandling selectCollectionHandling)> parts, Type destinationType = null, bool nullChecking = false)
{ {
// create parameter. // create parameter.
var queryType = query.ElementType; var queryType = query.ElementType;
@ -129,7 +129,7 @@ namespace PoweredSoft.DynamicLinq.Helpers
var partExpressions = new List<(Expression expression, string propertyName)>(); var partExpressions = new List<(Expression expression, string propertyName)>();
parts.ForEach(part => parts.ForEach(part =>
{ {
var partBodyExpression = CreateSelectExpression(query, parameter, part.selectType, part.path, part.selectCollectionHandling); var partBodyExpression = CreateSelectExpression(query, parameter, part.selectType, part.path, part.selectCollectionHandling, nullChecking);
fields.Add((partBodyExpression.Type, part.propertyName)); fields.Add((partBodyExpression.Type, part.propertyName));
partExpressions.Add((partBodyExpression, part.propertyName)); partExpressions.Add((partBodyExpression, part.propertyName));
}); });
@ -146,7 +146,7 @@ namespace PoweredSoft.DynamicLinq.Helpers
return result; return result;
} }
private static Expression CreateSelectExpressionForGrouping(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path, SelectCollectionHandling selectCollectionHandling) private static Expression CreateSelectExpressionForGrouping(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path, SelectCollectionHandling selectCollectionHandling, bool nullChecking)
{ {
if (selectType == SelectTypes.Key) if (selectType == SelectTypes.Key)
{ {
@ -193,23 +193,23 @@ namespace PoweredSoft.DynamicLinq.Helpers
throw new NotSupportedException($"unkown select type {selectType}"); throw new NotSupportedException($"unkown select type {selectType}");
} }
private static Expression CreateSelectExpression(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path, SelectCollectionHandling selectCollectionHandling) private static Expression CreateSelectExpression(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path, SelectCollectionHandling selectCollectionHandling, bool nullChecking)
{ {
if (!IsGrouping(query)) if (!IsGrouping(query))
return CreateSelectExpressionRegular(query, parameter, selectType, path, selectCollectionHandling); return CreateSelectExpressionRegular(query, parameter, selectType, path, selectCollectionHandling, nullChecking);
return CreateSelectExpressionForGrouping(query, parameter, selectType, path, selectCollectionHandling); return CreateSelectExpressionForGrouping(query, parameter, selectType, path, selectCollectionHandling, nullChecking);
} }
private static Expression CreateSelectExpressionRegular(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path, SelectCollectionHandling selectCollectionHandling) private static Expression CreateSelectExpressionRegular(IQueryable query, ParameterExpression parameter, SelectTypes selectType, string path, SelectCollectionHandling selectCollectionHandling, bool nullChecking)
{ {
if (selectType == SelectTypes.Path) if (selectType == SelectTypes.Path)
{ {
return ResolvePathForExpression(parameter, path, selectCollectionHandling); return ResolvePathForExpression(parameter, path, selectCollectionHandling, nullChecking);
} }
else if (selectType == SelectTypes.PathToList) else if (selectType == SelectTypes.PathToList)
{ {
var expr = ResolvePathForExpression(parameter, path, selectCollectionHandling); var expr = ResolvePathForExpression(parameter, path, selectCollectionHandling, nullChecking);
var notGroupedType = expr.Type.GenericTypeArguments.FirstOrDefault(); var notGroupedType = expr.Type.GenericTypeArguments.FirstOrDefault();
if (notGroupedType == null) if (notGroupedType == null)
throw new Exception($"Path must be a Enumerable<T> but its a {expr.Type}"); throw new Exception($"Path must be a Enumerable<T> but its a {expr.Type}");
@ -241,7 +241,7 @@ namespace PoweredSoft.DynamicLinq.Helpers
return result; return result;
} }
internal static Expression InternalResolvePathExpression(ParameterExpression param, List<string> parts, SelectCollectionHandling selectCollectionHandling) internal static Expression InternalResolvePathExpression(int step, ParameterExpression param, List<string> parts, SelectCollectionHandling selectCollectionHandling, bool nullChecking)
{ {
var isLast = parts.Count == 1; var isLast = parts.Count == 1;
var currentPart = parts.First(); var currentPart = parts.First();
@ -251,21 +251,41 @@ namespace PoweredSoft.DynamicLinq.Helpers
if (isLast) if (isLast)
return memberExpression; return memberExpression;
Expression ret = null;
if (!IsEnumerable(memberExpression)) if (!IsEnumerable(memberExpression))
return InternalResolvePathExpression(param, parts.Skip(1).ToList(), selectCollectionHandling); {
// TODO: null checking here too.
// should be easier then collection :=|
// enumerable. ret = InternalResolvePathExpression(step + 1, param, parts.Skip(1).ToList(), selectCollectionHandling, nullChecking);
var listGenericArgumentType = memberExpression.Type.GetGenericArguments().First(); }
else
{
// enumerable.
var listGenericArgumentType = memberExpression.Type.GetGenericArguments().First();
// sub param. // sub param.
var innerParam = Expression.Parameter(listGenericArgumentType); var innerParam = Expression.Parameter(listGenericArgumentType);
var innerExpression = InternalResolvePathExpression(innerParam, parts.Skip(1).ToList(), selectCollectionHandling); var innerExpression = InternalResolvePathExpression(step + 1, innerParam, parts.Skip(1).ToList(), selectCollectionHandling, nullChecking);
var lambda = Expression.Lambda(innerExpression, innerParam); var lambda = Expression.Lambda(innerExpression, innerParam);
if (selectCollectionHandling == SelectCollectionHandling.Select) if (selectCollectionHandling == SelectCollectionHandling.Select)
return Expression.Call(typeof(Enumerable), "Select", new Type[] { listGenericArgumentType, innerExpression.Type }, memberExpression, lambda); ret = Expression.Call(typeof(Enumerable), "Select", new Type[] { listGenericArgumentType, innerExpression.Type }, memberExpression, lambda);
else
ret = Expression.Call(typeof(Enumerable), "SelectMany", new Type[] { listGenericArgumentType, innerExpression.Type.GenericTypeArguments.First() }, memberExpression, lambda);
}
return Expression.Call(typeof(Enumerable), "SelectMany", new Type[] { listGenericArgumentType, innerExpression.Type.GenericTypeArguments.First() }, memberExpression, lambda); if (nullChecking)
{
ret = Expression.Condition(
Expression.Equal(memberExpression, Expression.Constant(null)),
Expression.Constant(null, ret.Type),
ret
);
}
return ret;
} }
/// <summary> /// <summary>
@ -274,10 +294,10 @@ namespace PoweredSoft.DynamicLinq.Helpers
/// <param name="param">Expression.Parameter(typeOfClassOrInterface)</param> /// <param name="param">Expression.Parameter(typeOfClassOrInterface)</param>
/// <param name="path">the path you wish to resolve example Contact.Profile.FirstName</param> /// <param name="path">the path you wish to resolve example Contact.Profile.FirstName</param>
/// <returns></returns> /// <returns></returns>
public static Expression ResolvePathForExpression(ParameterExpression param, string path, SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.Select) public static Expression ResolvePathForExpression(ParameterExpression param, string path, SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.Select, bool nullChecking = false)
{ {
var parts = path.Split('.').ToList(); var parts = path.Split('.').ToList();
return InternalResolvePathExpression(param, parts, selectCollectionHandling); return InternalResolvePathExpression(1, param, parts, selectCollectionHandling, nullChecking);
} }
public static ConstantExpression GetConstantSameAsLeftOperator(Expression member, object value) public static ConstantExpression GetConstantSameAsLeftOperator(Expression member, object value)