C# Fluent API 动态函数<>构造
C# Fluent API With Dynamic Func<> Construction
我正在创建一个 SQL 流利 API 的小型库,我想做这样的事情:
var person = connection.GetOne<Person>("select * from [Person] where [Id] = 1")
.WithMany<Pet>("select * from [Pet] where [PersonId] = 1")
.WithMany<Address>("select * from [Address] where [PersonId] = 1]")
.Build((person, pets, addresses) =>
{
person.Pets = pets;
person.Addresses = addresses;
return person;
});
我之前已经构建了很多流畅的 API,但是所有的都简单得多,并且没有那么依赖泛型。我的问题具体是如何着手实施 Build() 结束函数。我不确定它是否可能(看起来不像,但也许使用 Expression 是关键?)但是我如何跟踪在调用更高链方法(例如 GetOne<>( ), WithMany<>()) 以便在调用 .Build() 时所需的 Func<> 类型正确?
在上面的示例中,我希望 Func<> 为 Func, IEnumerable> 以便开发人员可以按照他们需要的任何方式构造根项(人) to - 在这种情况下,用一个->多个查询的结果填充几个集合。
有什么办法可以做到这一点还是我运气不好?似乎很多地方我都在寻找类似的东西:
Func<In1, TResult>
Func<In1, In2, TResult>
Func<In1, In2, In3, TResult>
...etc, etc
...显然将您限制为函数参数的最大数量的事物类型。
任何帮助或指点将不胜感激。
这取决于您希望能够添加多少方法,但每个方法都应该 return 具有相同方法的 T
类型的另一个对象。这要求您为构建函数中的每个类型参数重复所有函数。
也许不是你问题的答案,但你可以放弃静态类型检查并使用动态的东西:
public class Builder
{
List<Type> _types = new List<Type>();
List<object> _values = new List<Object>();
public Builder GetOne<T>()
{
_types.Add(typeof(T));
// Do stuff
_values.Add(someObjectYouRetrieved);
return this;
}
public T Build<T1, T>(Func<T1, T2, T> func) =>
func(_values[0] as T1);
public T Build<T1, T2, T>(Func<T1, T2, T> func) =>
func(_values[0] as T1, _values[1] as T2);
public T Build<T1, T2, T3, T>(Func<T1, T2, T3, T> func) =>
func(_values[0] as T1, _values[1] as T2, _values[2] as T3);
// Add more for the amount of type params you want to allow
}
这不会为您提供无限的灵活性,但至少可以防止方法激增。
它也不给你静态类型检查,但既然你在做 SQL,你首先就没有类型安全。
虽然这是一个权衡。
如果您想要强大的自动完成功能并防止有人在您期望 (person, pet) => {}
时编写 .Build(person => {})
,则您需要在构建器中详细说明。
下面是三个深度的示例:
class Person { public IEnumerable<Pet> Pets { get; set;} } class Pet {} class Address{}
public static class Builder
{
public static Level1<T> GetOne<T>(this object obj, string blah) {
return new Level1<T>();
}
}
public class Level1<T1> {
public Level2<T1, T2> WithMany<T2>(string blah) { return new Level2<T1, T2>(); }
public T1 Build(Func<T1, T1> pred) { return pred(default(T1)); }
}
public class Level2<T1, T2>
{
public Level3<T1, T2, T3> WithMany<T3>(string blah) { return new Level3<T1, T2, T3>(); }
public T1 Build(Func<T1, IEnumerable<T2>, T1> pred) { return pred(default(T1), default(IEnumerable<T2>)); }
}
public class Level3<T1, T2, T3>
{
public T1 Build(Func<T1, IEnumerable<T2>, IEnumerable<T3>, T1> pred) {
return pred(default(T1), default(IEnumerable<T2>), default(IEnumerable<T3>));
}
}
我们在这里获得强类型化的地方:
obj.GetOne<Person>("select * from [Person] where [Id] = 1")
.WithMany<Pet>("select * from [Pet] where [PersonId] = 1")
.WithMany<Address>("select * from [Address] where [PersonId] = 1]")
.Build((person, pets, addresses) => {
person.Pets = pets;
return person;
});
或
obj.GetOne<Person>("select * from [Person] where [Id] = 1")
.WithMany<Pet>("select * from [Pet] where [PersonId] = 1")
.Build((person, pets) => { return person; });
关于您关于限制参数数量的说明 - 是的。不幸的是,我不相信在保持强类型输入的同时可以解决这个问题。
我正在创建一个 SQL 流利 API 的小型库,我想做这样的事情:
var person = connection.GetOne<Person>("select * from [Person] where [Id] = 1")
.WithMany<Pet>("select * from [Pet] where [PersonId] = 1")
.WithMany<Address>("select * from [Address] where [PersonId] = 1]")
.Build((person, pets, addresses) =>
{
person.Pets = pets;
person.Addresses = addresses;
return person;
});
我之前已经构建了很多流畅的 API,但是所有的都简单得多,并且没有那么依赖泛型。我的问题具体是如何着手实施 Build() 结束函数。我不确定它是否可能(看起来不像,但也许使用 Expression 是关键?)但是我如何跟踪在调用更高链方法(例如 GetOne<>( ), WithMany<>()) 以便在调用 .Build() 时所需的 Func<> 类型正确?
在上面的示例中,我希望 Func<> 为 Func
有什么办法可以做到这一点还是我运气不好?似乎很多地方我都在寻找类似的东西:
Func<In1, TResult>
Func<In1, In2, TResult>
Func<In1, In2, In3, TResult>
...etc, etc
...显然将您限制为函数参数的最大数量的事物类型。
任何帮助或指点将不胜感激。
这取决于您希望能够添加多少方法,但每个方法都应该 return 具有相同方法的 T
类型的另一个对象。这要求您为构建函数中的每个类型参数重复所有函数。
也许不是你问题的答案,但你可以放弃静态类型检查并使用动态的东西:
public class Builder
{
List<Type> _types = new List<Type>();
List<object> _values = new List<Object>();
public Builder GetOne<T>()
{
_types.Add(typeof(T));
// Do stuff
_values.Add(someObjectYouRetrieved);
return this;
}
public T Build<T1, T>(Func<T1, T2, T> func) =>
func(_values[0] as T1);
public T Build<T1, T2, T>(Func<T1, T2, T> func) =>
func(_values[0] as T1, _values[1] as T2);
public T Build<T1, T2, T3, T>(Func<T1, T2, T3, T> func) =>
func(_values[0] as T1, _values[1] as T2, _values[2] as T3);
// Add more for the amount of type params you want to allow
}
这不会为您提供无限的灵活性,但至少可以防止方法激增。
它也不给你静态类型检查,但既然你在做 SQL,你首先就没有类型安全。
虽然这是一个权衡。
如果您想要强大的自动完成功能并防止有人在您期望 (person, pet) => {}
时编写 .Build(person => {})
,则您需要在构建器中详细说明。
下面是三个深度的示例:
class Person { public IEnumerable<Pet> Pets { get; set;} } class Pet {} class Address{}
public static class Builder
{
public static Level1<T> GetOne<T>(this object obj, string blah) {
return new Level1<T>();
}
}
public class Level1<T1> {
public Level2<T1, T2> WithMany<T2>(string blah) { return new Level2<T1, T2>(); }
public T1 Build(Func<T1, T1> pred) { return pred(default(T1)); }
}
public class Level2<T1, T2>
{
public Level3<T1, T2, T3> WithMany<T3>(string blah) { return new Level3<T1, T2, T3>(); }
public T1 Build(Func<T1, IEnumerable<T2>, T1> pred) { return pred(default(T1), default(IEnumerable<T2>)); }
}
public class Level3<T1, T2, T3>
{
public T1 Build(Func<T1, IEnumerable<T2>, IEnumerable<T3>, T1> pred) {
return pred(default(T1), default(IEnumerable<T2>), default(IEnumerable<T3>));
}
}
我们在这里获得强类型化的地方:
obj.GetOne<Person>("select * from [Person] where [Id] = 1")
.WithMany<Pet>("select * from [Pet] where [PersonId] = 1")
.WithMany<Address>("select * from [Address] where [PersonId] = 1]")
.Build((person, pets, addresses) => {
person.Pets = pets;
return person;
});
或
obj.GetOne<Person>("select * from [Person] where [Id] = 1")
.WithMany<Pet>("select * from [Pet] where [PersonId] = 1")
.Build((person, pets) => { return person; });
关于您关于限制参数数量的说明 - 是的。不幸的是,我不相信在保持强类型输入的同时可以解决这个问题。