Skip to content

Commit

Permalink
+ nested class ReplaceParameterTypeVisitor
Browse files Browse the repository at this point in the history
* move param `keySelector` from method `HasKey()` & `SplittingHasKey()` to primary ctor and rename methods to `HasBaseTable()` & `SplitToTable()`
* renamed from `ModelBuilderExtension`
@ `RevisionWithSplitting.ModelBuilder`
@ c#/crawler
  • Loading branch information
n0099 committed May 31, 2024
1 parent 3aa2edf commit 135870a
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 27 deletions.
38 changes: 21 additions & 17 deletions c#/crawler/src/Db/CrawlerDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,26 +77,30 @@ protected override void OnModelCreating(ModelBuilder b)
.HasOne(e => e.Content).WithOne().HasForeignKey<SubReplyContent>(e => e.Spid);
b.Entity<SubReplyContent>().ToTable($"tbmc_f{Fid}_subReply_content");

var thread = new RevisionWithSplitting<BaseThreadRevision>.ModelBuilderExtension(b, "tbmcr_thread");
thread.HasKey<ThreadRevision>(e => new {e.Tid, e.TakenAt});
thread.SplittingHasKey<SplitViewCount>("viewCount", e => new {e.Tid, e.TakenAt});
_ = new RevisionWithSplitting<BaseThreadRevision>
.ModelBuilder(b, "tbmcr_thread", e => new {e.Tid, e.TakenAt})
.HasBaseTable<ThreadRevision>()
.SplitToTable<SplitViewCount>("viewCount");

var reply = new RevisionWithSplitting<BaseReplyRevision>.ModelBuilderExtension(b, "tbmcr_reply");
reply.HasKey<ReplyRevision>(e => new {e.Pid, e.TakenAt});
reply.SplittingHasKey<ReplyRevision.SplitAgreeCount>("agreeCount", e => new {e.Pid, e.TakenAt});
reply.SplittingHasKey<SplitSubReplyCount>("subReplyCount", e => new {e.Pid, e.TakenAt});
reply.SplittingHasKey<SplitFloor>("floor", e => new {e.Pid, e.TakenAt});
_ = new RevisionWithSplitting<BaseReplyRevision>
.ModelBuilder(b, "tbmcr_reply", e => new {e.Pid, e.TakenAt})
.HasBaseTable<ReplyRevision>()
.SplitToTable<ReplyRevision.SplitAgreeCount>("agreeCount")
.SplitToTable<SplitSubReplyCount>("subReplyCount")
.SplitToTable<SplitFloor>("floor");

var subReply = new RevisionWithSplitting<BaseSubReplyRevision>.ModelBuilderExtension(b, "tbmcr_subReply");
subReply.HasKey<SubReplyRevision>(e => new {e.Spid, e.TakenAt});
subReply.SplittingHasKey<SubReplyRevision.SplitAgreeCount>("agreeCount", e => new {e.Spid, e.TakenAt});
subReply.SplittingHasKey<SplitDisagreeCount>("disagreeCount", e => new {e.Spid, e.TakenAt});
_ = new RevisionWithSplitting<BaseSubReplyRevision>
.ModelBuilder(b, "tbmcr_subReply", e => new {e.Spid, e.TakenAt})
.HasBaseTable<SubReplyRevision>()
.SplitToTable<SubReplyRevision.SplitAgreeCount>("agreeCount")
.SplitToTable<SplitDisagreeCount>("disagreeCount");

var user = new RevisionWithSplitting<BaseUserRevision>.ModelBuilderExtension(b, "tbmcr_user");
user.HasKey<UserRevision>(e => new {e.Uid, e.TakenAt});
user.SplittingHasKey<SplitIpGeolocation>("ipGeolocation", e => new {e.Uid, e.TakenAt});
user.SplittingHasKey<SplitPortraitUpdatedAt>("portraitUpdatedAt", e => new {e.Uid, e.TakenAt});
user.SplittingHasKey<SplitDisplayName>("displayName", e => new {e.Uid, e.TakenAt});
_ = new RevisionWithSplitting<BaseUserRevision>
.ModelBuilder(b, "tbmcr_user", e => new {e.Uid, e.TakenAt})
.HasBaseTable<UserRevision>()
.SplitToTable<SplitIpGeolocation>("ipGeolocation")
.SplitToTable<SplitPortraitUpdatedAt>("portraitUpdatedAt")
.SplitToTable<SplitDisplayName>("displayName");

b.Entity<SplitDisplayName>().Property(e => e.DisplayName).HasConversion<byte[]>();
b.Entity<User>().Property(e => e.DisplayName).HasConversion<byte[]>();
Expand Down
57 changes: 47 additions & 10 deletions c#/crawler/src/Db/Revision/Splitting/RevisionWithSplitting.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.ObjectModel;

namespace tbm.Crawler.Db.Revision.Splitting;

public abstract class RevisionWithSplitting<TBaseRevision> : BaseRevisionWithSplitting
Expand All @@ -23,16 +25,51 @@ protected void SetSplitEntityValue<TSplitEntity, TValue>
_splitEntities[typeof(TSplitEntity)] = entityFactory();
}

public class ModelBuilderExtension(ModelBuilder builder, string baseTableName)
public class ModelBuilder(
Microsoft.EntityFrameworkCore.ModelBuilder builder,
string baseTableName,
Expression<Func<TBaseRevision, object?>> keySelector)
{
public void HasKey<TRevision>(Expression<Func<TRevision, object?>> keySelector)
where TRevision : class, TBaseRevision =>
builder.Entity<TRevision>().ToTable(baseTableName).HasKey(keySelector);

public void SplittingHasKey<TRevisionWithSplitting>
(string tableNameSuffix, Expression<Func<TRevisionWithSplitting, object?>> keySelector)
where TRevisionWithSplitting : RevisionWithSplitting<TBaseRevision> =>
builder.Entity<TRevisionWithSplitting>().Ignore(e => e.NullFieldsBitMask)
.ToTable($"{baseTableName}_{tableNameSuffix}").HasKey(keySelector);
public ModelBuilder HasBaseTable<TRevision>() where TRevision : TBaseRevision
{
var visitor = new ReplaceParameterTypeVisitor<TBaseRevision, TRevision>();
_ = builder.Entity<TRevision>().ToTable(baseTableName)
.HasKey((Expression<Func<TRevision, object?>>)

Check failure on line 37 in c#/crawler/src/Db/Revision/Splitting/RevisionWithSplitting.cs

View workflow job for this annotation

GitHub Actions / build (crawler)

Operator '(Expression<Func<TRevision, object?>>)' should not appear at the end of a line. (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1003.md)

Check failure on line 37 in c#/crawler/src/Db/Revision/Splitting/RevisionWithSplitting.cs

View workflow job for this annotation

GitHub Actions / build (crawler)

Operator '(Expression<Func<TRevision, object?>>)' should not appear at the end of a line. (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1003.md)
visitor.Visit(keySelector));
return this;
}

public ModelBuilder SplitToTable<TRevisionWithSplitting>(string tableNameSuffix)
where TRevisionWithSplitting : RevisionWithSplitting<TBaseRevision>
{
var visitor = new ReplaceParameterTypeVisitor<TBaseRevision, TRevisionWithSplitting>();
_ = builder.Entity<TRevisionWithSplitting>()
.Ignore(e => e.NullFieldsBitMask)
.ToTable($"{baseTableName}_{tableNameSuffix}")
.HasKey((Expression<Func<TRevisionWithSplitting, object?>>)

Check failure on line 49 in c#/crawler/src/Db/Revision/Splitting/RevisionWithSplitting.cs

View workflow job for this annotation

GitHub Actions / build (crawler)

Operator '(Expression<Func<TRevisionWithSplitting, object?>>)' should not appear at the end of a line. (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1003.md)

Check failure on line 49 in c#/crawler/src/Db/Revision/Splitting/RevisionWithSplitting.cs

View workflow job for this annotation

GitHub Actions / build (crawler)

Operator '(Expression<Func<TRevisionWithSplitting, object?>>)' should not appear at the end of a line. (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1003.md)
visitor.Visit(keySelector));
return this;
}

/// <see>https://stackoverflow.com/questions/38316519/replace-parameter-type-in-lambda-expression/38345590#38345590</see>
private sealed class ReplaceParameterTypeVisitor<TSource, TTarget> : ExpressionVisitor
{
private ReadOnlyCollection<ParameterExpression>? _parameters;

protected override Expression VisitParameter(ParameterExpression node) =>
_parameters?.FirstOrDefault(p => p.Name == node.Name) ??
(node.Type == typeof(TSource) ? Expression.Parameter(typeof(TTarget), node.Name) : node);

protected override Expression VisitLambda<T>(Expression<T> node)
{
_parameters = VisitAndConvert(node.Parameters, nameof(VisitLambda));
return Expression.Lambda(Visit(node.Body), _parameters);
}

protected override Expression VisitMember(MemberExpression node) =>
node.Member.DeclaringType == typeof(TSource)
? Expression.Property(Visit(node.Expression)!, node.Member.Name)
: base.VisitMember(node);
}
}
}

0 comments on commit 135870a

Please sign in to comment.