Domanda

I'm using LINQ to Entities (EF5) to get and filter a list of products from an existing database. Products have a price which may or may not have a discount (flat rate or percentage). I'd like a calculation to happen server-side so that I can easily filter, sort, and retrieve the discounted price without writing a lot of duplicated Linq with some client code on top that re-calculates it yet again.

I know that Linq can't magically translate client-side functions into SQL, but it's my understanding that I could write an Expression that can get translated to SQL. It seems like I ought to be able to write an Expression and store that value as a "column."

I've seen some examples that appear to do what I want:

http://blog.cincura.net/230786-using-custom-properties-as-parameters-in-queries-in-ef/

Include derived property into a linq to entity query

Trouble is, I can't get the expression/property to work on a basic level. Forget the discount calculation for a moment...

Below, I have a simple expression to evaluate as "true" if the product id is greater than 3. I have a property in my Product model to return the result of the expression. When I try to get the property, I get a NotSupportedException: "The specified type member 'test' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported."

public partial class ProductList
{
    protected void Page_Load(object sender, EventArgs e)
    {           
        using (var db = new eTailerContext())
        {           
            var products = db.products
                       .Select(p => new
                       {
                           id = p.id,
                           name = p.name,
                           mytest = p.test
                       });
        }
    }
}

public class Product
{
    public id { get; set; }
    public name { get; set; }

    public bool test
    {
        get { return testExpression.Compile()(this); }
    }

    public static Expression<Func<Product, bool>> testExpression
    {
        get { return t => t.id > 3; }
    }
}

Alternatively, I've tried

mytest = Product.testExpression.Compile()(p)

but that throws a different NotSupported exception: "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." Is there really no way to do this? It seems like it would be a pretty common need.

Here is the configuration code:

public class ProductMap : EntityTypeConfiguration<Product>
{
    public ProductMap()
    {
        this.HasKey(t => t.id);
        this.ToTable("Products");
        this.Property(t => t.id).HasColumnName("ProductID");
        this.Property(t => t.name).HasColumnName("ProductName").HasMaxLength(255);

        this.Ignore(t => t.test);
    }
}

public class MyDbContext : DbContext
{
    public DbSet<Product> products { get; set; }

    public MyDbContext() : base("Name=SiteSqlServer")
    { }

    static MyDbContext()
    {
        Database.SetInitializer<MyDbContext>(null);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    { modelBuilder.Configurations.Add(new ProductMap()); }
}
È stato utile?

Soluzione

You'd need to wrap the entire select as an expression.

public class Product
{
    public int id { get; set; }
    public string name { get; set; }

    public bool test
    {
        get { return id > 3;}
    }

    public static Expression<Func<Product, dynamic>> TestExpression = 
                    p => new
                       {
                           id = p.id,
                           name = p.name,
                           mytest = p.id > 3
                       };
}

Then you can use

var products = db.products
                   .Select(Product.TestExpression);

Which will produce and SQL statement like

SELECT 
    [Extent1].[ProductID] AS [ProductID], 
    [Extent1].[ProductName] AS [ProductName], 
    CASE WHEN ([Extent1].[ProductID] > 3) THEN cast(1 as bit) WHEN ( NOT ([Extent1].[ProductID] > 3)) THEN cast(0 as bit) END AS [C1]
    FROM [dbo].[Products] AS [Extent1]

Note: You should probably use a real class instead of dynamic

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top