当前位置: 首页 > 工具软件 > IronRuby > 使用案例 >

IronRuby像C#一样调用Linq

阎承
2023-12-01

先来看看最终效果吧

首先是正常情况下C#的linq代码:

 var query = db.Users.Where(p => p.Name == "admin");

再看ruby调用linq的代码:

query = $db.Users.where { self.Name == 'admin' }
还原度是不是相当高?
这2段代码能生成相同的query

下面来看看怎么实现的

首先来分析C#的Where调用参数: p=>p.Name=="admin"

这段代码经过编译器大概会经过以下的操作, 编译成一个ExpressionTree:

var param = Expression.Parameter(typeof(User));
var exp = Expression.PropertyOrField(param, "Name");
var exp2 = Expression.Equal(exp, Expression.Constant("admin"));
var exp3 = Expression.Lambda<Func<User, bool>>(exp2, param);

最终的exp3就是传入Where的参数了

熟悉这个转换的人如果听到说要用ruby来实现相同的语法, 可能第一感觉就是: 不用想了, 做不到的, 你知道编译器做了多少工作么?

当然我们要抛弃这种僵化的思维来动手试试, 毕竟ruby的魔法是很厉害的

回头看看我们想要的最终效果: $db.Users.where, 这样我们需要一个小写的where方法, 这个可以直接使用c#的扩展方法来实现

当代码进入where的时候, 我们得到了db.Users, 它的类型是IQueryable<User>, 也就是说, 我们得到了User这个类型

这样, 4座大山的第一座也搞定了


然后来看看ruby的一个魔法: method_missing

如果对象调用了一个未定义的方法, 就会把方法名和参数被转发到method_missing中, 如下:

class User
    def method_missing(method,*args)
        puts "User类没有#{method}这个方法哦"
    end
end

User.new.Name #输出: User类没有Name这个方法哦

如果我们给Expression类加上一个method_missing功能, 那么在调用exp.Name的时候是不是就能生成这个呢?
Expression.PropertyOrField(param, "Name")

这样我们搞定了上面4座大山的第二座

再看: self.Name == 'admin', 我们需要让==运算符执行时来到自己的代码中, 理论上ruby和C#都可以做到

我们重载一个==运算符, 在这个重载里面, 我们能得到运算符左右2边以及运算符本身, 一共3个信息, 足以推翻第三座大山了

到了最后一座大山, 我们已经不需要更多的信息, 直接在where方法里面写出来就行了

已经有了充足的理论, 下面来看看最终的实现代码

由于IronRuby在实现上有一些限制, 导致只用上了ruby的一个魔法, 一开始是打算还要用上openclass和运算符重载的,  这2个功能都用c#来实现了

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Microsoft.Scripting.Hosting;
using IronRuby.Runtime;
using IronRuby;
using Microsoft.Scripting.Hosting.Providers;
using Microsoft.Scripting;
using IronRuby.Builtins;
using System.Linq.Expressions;

namespace IronRubyTest
{
	class Program
	{
		static void Main(string[] args)
		{
			var db = new Context();
			Console.WriteLine("正常调用linq: ");
			var query = db.Users.Where(p => p.Name == "admin");
			Console.WriteLine(query);
			Console.WriteLine();

			Console.WriteLine("Ruby调用Linq: ");

			var ctx = new ServiceScriptContext();
			ctx.Context.SetGlobalVariable(null, "db", db);
			ctx.Engine.Execute(@"
			query = $db.Users.where { self.Name == 'admin' }
			System::Console.WriteLine query
");
		}
	}

	public class RubyExpressionTree
	{
		public ParameterExpression[] Param { get; set; }
		public Expression Exp { get; set; }
		public RubyExpressionTree GetField(string field) => Fork(p => Expression.PropertyOrField(p, field));
		public RubyExpressionTree Fork(Func<Expression, Expression> newExp)
		{
			return new RubyExpressionTree
			{
				Param = Param,
				Exp = newExp(Exp)
			};
		}

		static object Ruby2CSObject(object obj, Type type)
		{
			return Convert.ChangeType(obj.ToString(), type);
		}

		public static RubyExpressionTree operator ==(RubyExpressionTree tree, object obj) => tree.Fork(p => Expression.Equal(p, Expression.Constant(Ruby2CSObject(obj, p.Type))));
		public static RubyExpressionTree operator !=(RubyExpressionTree tree, object obj) => tree.Fork(p => Expression.NotEqual(p, Expression.Constant(Ruby2CSObject(obj, p.Type))));
		public static RubyExpressionTree Create<T>()
		{
			var exp = Expression.Parameter(typeof(T));
			return new RubyExpressionTree
			{
				Exp = exp,
				Param = new ParameterExpression[] { exp }
			};
		}
	}

	public static class RubyExt
	{
		public static IQueryable<T> where<T>(this IQueryable<T> src, BlockParam block)
		{
			var exp = RubyOps.Yield0(block.Proc, RubyExpressionTree.Create<T>(), block) as RubyExpressionTree;
			return src.Where(Expression.Lambda<Func<T, bool>>(exp.Exp, exp.Param));
		}
	}

	public class Context : DbContext
	{
		public DbSet<User> Users { get; set; }
		public Context()
		{
			Database.SetInitializer<Context>(null);
		}
	}

	public class User
	{
		public Guid ID { get; set; } = Guid.NewGuid();
		public string Name { get; set; }
		public int Age { get; set; }
	}
	public class ServiceScriptContext
	{
		public ScriptRuntime Runtime { get; private set; }
		public RubyContext Context { get; private set; }
		public ScriptEngine Engine { get; private set; }
		public static readonly string MainScriptPath = @"main.rb";
		public ServiceScriptContext()
		{
			var setup = ScriptRuntimeSetup.ReadConfiguration();
			setup.AddRubySetup();
			setup.PrivateBinding = true;
			Runtime = Ruby.CreateRuntime(setup);
			Engine = Ruby.GetEngine(Runtime);

			Context = (RubyContext)HostingHelpers.GetLanguageContext(Engine);

			ExecuteFile(MainScriptPath);
		}
		public dynamic ExecuteFile(string file, ScriptScope scope = null)
		{
			return Engine.CreateScriptSourceFromFile(file, Encoding.UTF8, SourceCodeKind.File).Execute(scope ?? Runtime.Globals);
		}
	}

}


main.rb:

load_assembly 'IronRubyTest'
include IronRubyTest
using_clr_extensions IronRubyTest

class RubyExpressionTree
    def method_missing(method,*args)
        GetField method
    end    
end



最终输出:

正常调用linq:
SELECT
    [Extent1].[ID] AS [ID],
    [Extent1].[Name] AS [Name],
    [Extent1].[Age] AS [Age]
    FROM [dbo].[Users] AS [Extent1]
    WHERE N'admin' = [Extent1].[Name]


Ruby调用Linq:
SELECT
    [Extent1].[ID] AS [ID],
    [Extent1].[Name] AS [Name],
    [Extent1].[Age] AS [Age]
    FROM [dbo].[Users] AS [Extent1]
    WHERE N'admin' = [Extent1].[Name]
请按任意键继续. . .


一模一样, 对吧


 类似资料: