【问题标题】:Conditionally Populate Class Field in LINQ To Entities有条件地填充 LINQ To 实体中的类字段
【发布时间】:2018-10-13 05:10:01
【问题描述】:

我有一个如下所示的 EF 类:

public class Item
public string ItemId{ get; set; } 
public string NormalDescription { get; set; } 
public string LongDescription { get; set; } 
public string ShortDescription { get; set; }
.. <snip>

另外,我有一个如下所示的 DTO:

public class ItemDTO
public string Id { get; set; } 
public string DisplayName { get; set; }
.. <snip>

将“Item”类中的数据加载到 DTO 后,我需要根据配置设置有条件地设置“DisplayName”。换句话说,我正在寻找类似于:

return _repo.GetAsQueryable<Item>()
    .Select(i=> new ItemDTO
    {
        Id = i.ItemId,
        DisplayName = (setting == 1) ? i.NormalDescription :
                      (setting == 2) ? i.LongDescription :
                      (setting == 3) ? i.ShortDescription :
                      String.Empty
    }

当然,这会导致一些非常低效的 SQL(使用“CASE”来评估每个可能的值)被发送到数据库。这是一个性能问题,因为项目上有大量的描述字段。

话虽如此,有没有办法只选择填充“DisplayName”值所需的字段?

换句话说,我只想根据我的应用程序配置设置检索一个描述值,而不是填充“CASE WHEN”逻辑的查询。

【问题讨论】:

  • 它只会得到现在的 Id 和 DisplayName。有什么问题?
  • @CodingYoshi 是的,它填充了 Id 和 DisplayName,但是它使用了非常低效的 SQL。换句话说,它使用大量的“CASE WHEN”逻辑来查询数据库。我希望有一种确定的方式来填充 LINQ 选择投影..
  • case when 非常有效。你的绩效目标是什么?您是否进行了基准测试?
  • 您的选择是案例语句或查询所有列并在内存中进行映射或为每个案例单独查询。
  • 一个更彻底的改变是重组您的数据库表,使其具有一个描述表,该表具有项目表的外键以及描述名称和类型。然后您的设置可以与类型匹配。

标签: c# sql entity-framework linq linq-to-entities


【解决方案1】:

您应该动态创建lambda Expression

var typeOfItem = typeof(Item);
var argParam = Expression.Parameter(typeOfItem, "x");
var itemIdProperty = Expression.Property(argParam, "ItemId");

var properties = typeOfItem.GetProperties();
Expression descriptionProperty;
if (setting < properties.Count())            
    descriptionProperty = Expression.Property(argParam, properties[setting].Name);
else
    descriptionProperty = Expression.Constant(string.Empty);

var ItemDTOType = typeof(ItemDTO);

var newInstance = Expression.MemberInit(
    Expression.New(ItemDTOType),
    new List<MemberBinding>()
    {
        Expression.Bind(ItemDTOType.GetMember("Id")[0], itemIdProperty),
        Expression.Bind(ItemDTOType.GetMember("DisplayName")[0], descriptionProperty),
    }
);

var lambda = Expression.Lambda<Func<Item, ItemDTO>>(newInstance, argParam);

return _repo.GetAsQueryable<Item>().Select(lambda);

【讨论】:

    【解决方案2】:

    这样的?

    var repo = _repo.GetAsQueryable<Item>();
    
    if (setting == 1)
    {
        return repo.Select(i => new ItemDTO
        {
            Id = i.ItemId,
            DisplayName = i.NormalDescription
        });
    }
    if (setting == 2)
    {
        return repo.Select(i => new ItemDTO
        {
            Id = i.ItemId,
            DisplayName = i.LongDescription
        });
    }
    if (setting == 3)
    {
        return repo.Select(i => new ItemDTO
        {
            Id = i.ItemId,
            DisplayName = i.ShortDescription
        });
    }
    return repo.Select(i => new ItemDTO
    {
        Id = i.ItemId,
        DisplayName = String.Empty
    });
    

    编辑

    您可以像 Slava Utesinov 展示的那样动态创建表达式。如果你不想构建整个表达式,你可以只替换你想要的部分:

    public class UniRebinder : ExpressionVisitor
    {
        readonly Func<Expression, Expression> replacement;
    
        UniRebinder(Func<Expression, Expression> replacement)
        {
            this.replacement = replacement;
        }
    
        public static Expression Replace(Expression exp, Func<Expression, Expression> replacement)
        {
            return new UniRebinder(replacement).Visit(exp);
        }
    
        public override Expression Visit(Expression p)
        {
            return base.Visit(replacement(p));
        }
    }
    
    Expression<Func<Item, ItemDTO>> ReplaceProperty(
        int setting, Expression<Func<Item, ItemDTO>> value)
    {
        Func<MemberExpression, Expression> SettingSelector(int ss)
        {
            switch (ss)
            {
                case 1: return x => Expression.MakeMemberAccess(x.Expression, typeof(Item).GetProperty(nameof(Item.NormalDescription)));
                case 2: return x => Expression.MakeMemberAccess(x.Expression, typeof(Item).GetProperty(nameof(Item.LongDescription)));
                case 3: return x => Expression.MakeMemberAccess(x.Expression, typeof(Item).GetProperty(nameof(Item.ShortDescription)));
                default: return x => Expression.Constant(String.Empty);
            }
        }
    
        return (Expression<Func<Item, ItemDTO>>)UniRebinder.Replace(
            value,
            x =>
            {
                if (x is MemberExpression memberExpr
                    && memberExpr.Member.Name == nameof(Item.NormalDescription))
                {
                    return SettingSelector(setting)(memberExpr);
                }
    
                return x;
            });
    }
    
    
    private void Test()
    {
        var repo = (new List<Item>() {
            new Item() {
                ItemId ="1",
                LongDescription = "longd1",
                NormalDescription = "normald1",
                ShortDescription = "shortd1" },
            new Item() {
                ItemId ="2",
                LongDescription = "longd2",
                NormalDescription = "normald2",
                ShortDescription = "shortd2" }
        }).AsQueryable();
    
        for (int selector = 1; selector < 5; ++selector)
        {
            var tst = repo.Select(ReplaceProperty(selector,
                i => new ItemDTO
                {
                    Id = i.ItemId,
                    DisplayName = i.NormalDescription
                })).ToList();
    
    
            Console.WriteLine(selector + ": " + string.Join(", ", tst.Select(x => x.DisplayName)));
            //Output:
            //1: normald1, normald2
            //2: longd1, longd2
            //3: shortd1, shortd2
            //4: , 
        }
    }
    

    【讨论】:

    • 我想过这个,但不幸的是,项目上有大约 30 个不同的描述字段..所以我必须为每个设置重复上述内容...
    • @TelJanini 您可以在 if 语句中创建 Select 使用的表达式。如果您的实际查询更复杂,这可能会很有用。
    • @juharr 这听起来正是我所需要的,有什么有用的链接可以告诉我怎么做吗?
    猜你喜欢
    • 1970-01-01
    • 2016-02-14
    • 1970-01-01
    • 2018-06-26
    • 2010-10-10
    • 1970-01-01
    • 2019-10-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多