سؤال

I'm developing and multi-tier application solution and my solution looks like this

  • Business.DomainObject
  • Business.Process (* Actual business layer)
  • Data.Mapper
  • Data.Repository
  • Data.Sql.Entity (* Actual data layer that has .dbml file)

My Business.Process project (business layer) is only knows Business.DomainObject and Data.Repository projects, so doesn't know and not in relationship with Data.Sql.Entity project
I'm sending business (domain) objects to repository and do mapping inside this project, then I'm doing CRUD process inside repository layer with relationship data layers.

My traditional domain object is like this, it has only properties

public class UserBO
{
public string Email { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}

My business layer class is like this,

Repository r = new UserRepository();
r.Insert(myUserDomainObject);


Everything is OK, but I have some problems about my "predicate" queries. I have a method in my repository interface

bool IsExists(Expression<Func<BusinessObject,bool>> predicate);

and use it in my business layer like this;

Repository r = new UserRepository(); <br/>
r.IsExists(p => p.Email.Equals("email address"));

as you can see, its parameter is "business object", but my actual repository (that connects with database) using "dataobject" that is inside my dbml file.

    public override bool IsExists(Expression<Func<DataObject, bool>> predicate){
return _table.Where(predicate).Any();
}

I want convert this two predicate, for example I'll send UserBO and convert to UserDataObject

how can i do that?

هل كانت مفيدة؟

المحلول

You have to analyze the parameters and the body of the LambdaExpression and build a new one. You can access the body it using predicate.Body and the parameters using predicate.Parameters. The new expression has a parameter of type UserBO instead of UserDataObject. In addition, the ParameterExpression(s) in the body must use the new parameter.
Of course, this assumes that it is a simple scenario where the BO and the DataObject have the same properties. More complex transformations are possible but need a deeper analysis of the expression tree.
In order to vive you a starting Point, I out together a sample with a very similar BusinessObject and DataObject. The key component is a class derived from ExpressionVisitor that performs the conversion:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace ConvertExpression
{
    public class BusinessObject
    {
        public int Value { get; set; }
    }

    public class DataObject
    {
        public int Value { get; set; }
    }

    internal class ExpressionConverter : ExpressionVisitor
    {
        public Expression Convert(Expression expr)
        {
            return Visit(expr);
        }

        private ParameterExpression replaceParam;

        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            if (typeof(T) == typeof(Func<BusinessObject, bool>))
            {
                replaceParam = Expression.Parameter(typeof(DataObject), "p");
                return Expression.Lambda<Func<DataObject, bool>>(Visit(node.Body), replaceParam);
            }
            return base.VisitLambda<T>(node);
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (node.Type == typeof(BusinessObject))
                return replaceParam; // Expression.Parameter(typeof(DataObject), "p");
            return base.VisitParameter(node);
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Member.DeclaringType == typeof(BusinessObject))
            {
                var member = typeof(DataObject).GetMember(node.Member.Name, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).FirstOrDefault();
                if (member == null)
                    throw new InvalidOperationException("Cannot identify corresponding member of DataObject");
                return Expression.MakeMemberAccess(Visit(node.Expression), member);
            }
            return base.VisitMember(node);
        }
    }

    public class ConvertExpression
    {
        public static void Main()
        {
            BusinessObject[] bos = { new BusinessObject() { Value = 123 }, new BusinessObject() { Value = 246 } };
            DataObject[] dos = { new DataObject() { Value = 123 }, new DataObject() { Value = 246 } };
            Expression<Func<BusinessObject, bool>> boExpr = x => x.Value == 123;
            var conv = new ExpressionConverter();
            Expression<Func<DataObject, bool>> doExpr = (Expression<Func<DataObject, bool>>) conv.Convert(boExpr);

            var selBos = bos.Where(boExpr.Compile());
            Console.WriteLine("Matching BusinessObjects: {0}", selBos.Count());
            foreach (var bo in selBos)
                Console.WriteLine(bo.Value);
            var compDoExpr = doExpr.Compile();
            var selDos = dos.Where(doExpr.Compile());
            Console.WriteLine("Matching DataObjects: {0}", selDos.Count());
            foreach (var dataObj in selDos)
                Console.WriteLine(dataObj.Value);
            Console.ReadLine();
        }
    }
}

نصائح أخرى

If you need it i created a small fluent library to create lambda functions on the fly without directly coping with System.Linq.Expressions. Between the other things it contains exactly what you need, just to give an example to accomply the compare on the city you could do like this:

//Cached somewhere
var compareLambda= ExpressionUtil.GetComparer<CityBO>(p => 
  p.Id.Value,ComparaisonOperator.Equal);

//Then in the execution
Repository.IsExists(p=>compareLambda(p,city id));

The code and documentation are here: Kendar Expression Builder with the unit tests that are pretty self explanatory While the nuget package is here: Nuget Expression Builder

your code works well with single property like

Repository.IsExists(p => p.Email.Equals("abc@xyz.com"));

but as i mentioned, my domain object classes have some nested class properties like "City", when i try this

Repository.IsExists(p => p.City.Id.Equals(city id));

it throws an exception;

Property 'Business.DomainObject.SystemCommon.ExtendedProperty.PrimaryKey Id' is not defined for type 'Data.Sql.Entity.LinqDataContext.City'

i understand this exception, because my City class like this;

public class CityBO : IDomainObject
{
  public PrimaryKey Id { get; set; }
  public string Name { get; set; }
  public EntityReferance<CountryBO> Country { get; set; }
  public LocationInfoBO Location { get; set; }
  public StatusInfo Status { get; set; }  
}

and PrimaryKey property (class) is

public class PrimaryKey
{
  public string Value { get; set; }
}

I'm using this property, because of my entities have different Primary Key type and also I'm planning implementation of Nosql db in future..

i want thank you for your code, it is useful for me when I'm query with single property.

regards

giving credit to @Markus, here is a generic converter class...

internal class ExpressionConverter<TInput, TOutput> : ExpressionVisitor
{
    public Expression<Func<TOutput, bool>> Convert(Expression<Func<TInput, bool>> expression)
    {
        return (Expression<Func<TOutput, bool>>)Visit(expression);
    }

    private ParameterExpression replaceParam;

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        if (typeof(T) == typeof(Func<TInput, bool>))
        {
            replaceParam = Expression.Parameter(typeof(TOutput), "p");
            return Expression.Lambda<Func<TOutput, bool>>(Visit(node.Body), replaceParam);
        }
        return base.VisitLambda<T>(node);
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Type == typeof(TInput))
            return replaceParam;
        return base.VisitParameter(node);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Member.DeclaringType == typeof(TInput))
        {
            var member = typeof(TOutput).GetMember(node.Member.Name, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).FirstOrDefault();
            if (member == null)
                throw new InvalidOperationException("Cannot identify corresponding member of DataObject");
            return Expression.MakeMemberAccess(Visit(node.Expression), member);
        }
        return base.VisitMember(node);
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top