在有人问之前,我确实知道已经有一个 Neo4jClient 存储库,但它已经过时,并且尚未针对 Neo4j 2.x 和新的 Neo4jClient 代码进行更新。
我的目标是实现这样的目标:
var profiles = profileRepository.Get((Data.Model.Profile profile) => profile.Age > 20);
我首先尝试通过将谓词传递给Where调用并在CypherFluentQuery上执行Return来手动构建表达式,但谓词参数与我在Return调用中的参数不匹配:
return client.Cypher
.Match("(entity:" + _entityTypeName + ")")
.Where(predicate)
.Return(entity => entity.As<TEntity>)
.Results.FirstOrDefault();
这就是我决定需要动态构建返回表达式的最终原因,以便它能够正确命名要传递给 Neo4jClient 的参数,并且不会返回未定义的异常。
我已经开始研究 Linq 表达式树和 lambda 表达式,以在 .NET 4.5 和 Neo4j 2.x 中构建基于谓词的通用 Neo4jClient 存储库。然而,我还不完全理解如何构建表达式树。这是我的 Neo4jRepository Get:
using Neo4jClient;
using Neo4jClient.Cypher;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Data.Repository
{
public class Neo4jRepository<TEntity> : IRepository<TEntity>
{
private List<TEntity> _entities;
private string _entityTypeName;
public Neo4jRepository()
{
_entities = new List<TEntity>();
Type entityType = typeof(TEntity);
_entityTypeName = entityType.Name;
}
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> predicate)
{
var client = new GraphClient(
new Uri("http://localhost:7474/db/data"));
client.Connect();
ParameterExpression parameter =
Expression.Parameter(typeof(
Expression<Func<ICypherResultItem, TEntity>>),
predicate.Parameters[0].Name);
var exp = Expression.Lambda<Func<ICypherResultItem, TEntity>>(
parameters: parameter,
body: Expression.Call(
instance: Expression.Default(typeof(ICypherResultItem)),
methodName: "As",
typeArguments: new[] { typeof(TEntity) }
)
);
return client.Cypher
.Match("(entity:" + _entityTypeName + ")")
.Where(predicate)
.Return(exp)
.Results;
}
}
}
这是我的 IRepository:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Data.Repository
{
/// <summary>
/// Generic repository that can be used with any data backend
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
interface IRepository<TEntity>
{
IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> predicate);
void Add(TEntity item);
void Update(Func<TEntity,bool> predicate, TEntity item);
void Delete(TEntity item);
void SaveChanges();
}
}
当我尝试使用谓词执行 get 操作时,编译器不接受我的 lambda 表达式:
类型的参数表达式 'System.Linq.Expressions.Expression
2[Neo4jClient.Cypher.ICypherResultItem,Data.Model.Profile]]' 不能用于类型的委托参数 'Neo4jClient.Cypher.ICypherResultItem'1[System.Func
如何根据输入谓词构建具有动态参数名称的表达式,该表达式适合像
entity => entity.As<TEntity>
这样的 Return 调用?
经过一段时间的修改,我想出了一个通用存储库: https://github.com/pcmantinker/Neo4jRepository
这是一些示例用法:
BlogPost.cs
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Neo4jRepository.Data.Model
{
public class BlogPost : Neo4jEntity
{
public string Author { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTimeOffset Created { get; set; }
public BlogPost()
{
Label = "BlogPost";
Created = DateTimeOffset.UtcNow;
}
}
}
BlogPostRepository.cs
using Neo4jRepository.Data.Model;
using System.Threading.Tasks;
namespace Neo4jRepository.Repository
{
public class BlogPostRepository : Neo4jRepository<BlogPost>
{
// any custom methods or overridden methods here
/// <summary>
/// Adds or updates a blog post.
/// </summary>
/// <param name="post">The post.</param>
/// <returns></returns>
public async Task AddOrUpdate(BlogPost post)
{
var found = await this.Single(p => p.Author == post.Author && p.Content == post.Content && p.Title == post.Title);
if(found == null)
{
await Add(post);
}
else
{
await Update(p => p.Author == post.Author && p.Content == post.Content && p.Title == post.Title, post);
}
}
}
}
示例程序:
BlogPostRepository _repo = new BlogPostRepository();
BlogPost post = new BlogPost()
{
Author = "John Smith",
Title = "Hello Blog!",
Content = "Test blog content"
};
BlogPost post2 = new BlogPost()
{
Author = "Jane Smith",
Title = "Hello Blog!",
Content = "Test blog content"
};
await _repo.AddOrUpdate(post);
await _repo.AddOrUpdate(post2);
IEnumerable<BlogPost> blogPosts = await _repo.All();
IEnumerable<BlogPost> janesPosts = await _repo.Where(b => b.Author == "Jane Smith");
实体必须继承自 Neo4jEntity 才能引用该实体节点上的标签。通过使存储库通用,我们可以轻松地为从 Neo4jRepository 继承的任何具有标准 CRUD 操作的存储库建立存储库。