How to create a reusable where clause for EF6 - c#

I have recently moved from coding in Java to c# and I am still learning the various elements of c#.
To access an existing database, which I cannot redesign, I am using Entity Frameworks 6 and 'Code First from database' to generate contexts and types representing the database tables. I am using Ling-To-SQL to retrieve the data from the database which is heavily denormalized.
My current task is create a report where each section is read from various tables, which all have a relationship to one base table.
This is my working example:
using(var db = new PaymentContext())
{
var out = from pay in db.Payment
join typ in db.Type on new { pay.ID, pay.TypeID } equals
new { typ.ID, typ.TypeID }
join base in db.BaseTable on
new { pay.Key1, pay.Key2, pay.Key3, pay.Key4, pay.Key5 } equals
new { base.Key1, base.Key2, base.Key3, base.Key4, base.Key5 }
where
base.Cancelled.Equals("0") &&
base.TimeStamp.CompareTo(startTime) > 0 &&
base.TimeStamp.CompareTo(endTime) < 1 &&
.
(other conditions)
.
group new { pay, typ } by new { typ.PaymentType } into grp
select new
{
name = grp.Key,
count = grp.Count(),
total = grp.Sum(x => x.pay.Amount)
};
}
There will be a large number of sections in the report and each section will generate a where clause which will contain the conditions shown. In some sections, the required data will be extracted from tables up to five levels below the BaseTable.
What I want to do is create a resuable where clause for each report section, to avoid a lot of duplicated code.
After a lot of searching, I tried to use the solution suggested here , but this has been superseded in Entity Framework 6.
How do I avoid duplicating code unnecessarily?

I did try to use the extension clauses you suggested, but my generated classes do not extend the BaseTable, so I had to explicitly define the link through the navigation property. As only a small number of tables will be common in the queries, I decided to apply the filters directly to each table as required. I will define these as required.
krillgar suggested moving to straight LINQ syntax, which seems like good advice. We intend to redesign our database in the near future and this will remove some of the SQL dependency. I merged the suggested filters and full LINQ syntax to access my data.
// A class to hold all the possible conditions applied for the report
// Can be applied at various levels within the select
public class WhereConditions
{
public string CancelledFlag { get; set; } = "0"; // <= default setting
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
// Class to define all the filters to be applied to any level of table
public static class QueryExtensions
{
public static IQueryable<BaseTable> ApplyCancellationFilter(this IQueryable<BaseTable> base, WhereConditions clause)
{
return base.Where(bse => bse.CancelFlag.Equals(clause.CancelledFlag));
}
public static IQueryable<BaseTable> ApplyTimeFilter(this IQueryable<BaseTable> base, WhereConditions clause)
{
return base.Where(bse => bse.TimeStamp.CompareTo(clause.StartTime) > 0 &&
bse.TimeStamp.CompareTo(clause.EndTime) < 1);
}
}
And the query is composed as follows:
using (var db = new PaymentContext())
{
IEnumerable<BaseTable> filter = db.BaseTable.ApplyCancellationFilter(clause).ApplyTimeFilter(clause);
var result = db.Payment.
Join(
filter,
pay => new { pay.Key1, pay.Key2, pay.Key3, pay.Key4, pay.Key5 },
bse => new { bse.Key1, bse.Key2, bse.Key3, bse.Key4, bse.Key5 },
(pay, bse) => new { Payment = pay, BaseTable = bse }).
Join(
db.Type,
pay => new { pay.Payment.TypeKey1, pay.Payment.TypeKey2 },
typ => new { typ.TypeKey1, typ.TypeKey2 },
(pay, typ) => new { name = typ.Description, amount = pay.Amount }).
GroupBy(x => x.name).
Select(y => new { name = y.Key,
count = y.Count(),
amount = y.Sum(z => z.amount)});
}
And then to finally execute composed query.
var reportDetail = result.ToArray(); // <= Access database here
As this query is the simplest I will have to apply, future queries will become much more complicated.

The nice thing about LINQ is that methods like Where() return an IEnumerable<T> that you can feed into the next method.
You could refactor the where clauses into extension methods akin to:
public static class PaymentQueryExtensions {
public static IQueryable<T> ApplyNotCancelledFilter(
this IQueryable<T> payments)
where T : BaseTable {
// no explicit 'join' needed to access properties of base class in EF Model
return payments.Where(p => p.Cancelled.Equals("0"));
}
public static IQueryable<T> ApplyTimeFilter(
this IQueryable<T> payments, DateTime startTime, DateTime endTime)
where T: BaseTable {
return payments.Where(p => p.TimeStamp.CompareTo(startTime) > 0
&& p.TimeStamp.CompareTo(endTime) < 1);
}
public static IGrouping<Typ, T> GroupByType(
this IQueryable<T> payments)
where T: BaseTable {
// assuming the relationship Payment -> Typ has been set up with a backlink property Payment.Typ
// e.g. for EF fluent API:
// ModelBuilder.Entity<Typ>().HasMany(t => t.Payment).WithRequired(p => p.Typ);
return payments.GroupBy(p => p.Typ);
}
}
And then compose your queries using these building blocks:
IEnumerable<Payment> payments = db.Payment
.ApplyNotCancelledFilter()
.ApplyTimeFilter(startTime, endTime);
if (renderSectionOne) {
payments = payments.ApplySectionOneFilter();
}
var paymentsByType = payments.GroupByType();
var result = paymentsByType.Select(new
{
name = grp.Key,
count = grp.Count(),
total = grp.Sum(x => x.pay.Amount)
}
);
Now that you have composed the query, execute it by enumerating. No DB access has happened until now.
var output = result.ToArray(); // <- DB access happens here
Edit After the suggestion of Ivan, I looked at our codebase. As he mentioned, the Extension methods should work on IQueryable instead of IEnumerable. Just take care that you only use expressions that can be translated to SQL, i.e. do not call any custom code like an overriden ToString() method.
Edit 2 If Payment and other model classes inherit BaseTable, the filter methods can be written as generic methods that accept any child type of BaseTable. Also added example for grouping method.

Related

Dapper with Mapping by code: Multi-Mapping with repeating column names

I'm trying to perform a simple query and the result data is almost all null.
I have this table structure
Table Registros
ID | Autonumeric
TareaM_Id | Numeric
Fecha | Date/Time
and Macro_tareas table
ID | Autonumeric
Nombre | Short Text
I have mapped the classes in C# like this:
[Table("Registros")]
public class Registro
{
[Column("ID")]
public virtual int ID { get; set; }
[Column("Fecha")]
public virtual DateTime Fecha { get; set; }
[Column("TareaM_Id")]
public virtual int TareaM_Id { get; set; }
public virtual MacroTarea MacroT { get; set; }
}
[Table("Macro_tarea")]
public class MacroTarea
{
[Column("ID")]
public virtual int ID { get; set; }
[Column("Nombre")]
public virtual string Nombre{ get; set; }
public virtual ICollection<Registro> Registros { get; set; }
}
This is the query i'm trying to use
string sql = #"SELECT reg.ID, mac.ID
FROM Registros as reg INNER JOIN Macro_tarea as mac on reg.TareaM_Id = mac.ID
WHERE Fecha = #Fecha";
using (IDbConnection db = new OleDbConnection(ConnectionString))
{
var result = db.Query<Registro,MacroTarea, Registro>(sql,
(reg,mac) =>
{
reg.MacroTarea = mac;
return reg;
}
,new { #Fecha = new DateTime(2019, 1, 4).Date }
, splitOn: "mac.ID")
.AsList();
}
I'm trying to only retrieve ids, but both id become null why is this happening?
The thing is, if I add Registros.Fecha and Macro_tarea.Nombre to the query, it got the value correctly. But id keep coming null.
Apparently the issue is happening only with ids. I suspect this issue is due to duplicate column names.
I'm working with Microsoft Access just in cast that matters.
My question is not similar to the possible duplicate because I have the classes defined as they should be mapped.
Renaming your database columns because your code cannot cope with the data is not a good idea. In the world of separation of concerns, why should your database care? There are good database reasons to name ID columns "Id", and you may not even have the option to change them.
There's another issue with Dapper mapping that renaming columns does not get around; repeated types. If you are trying to map to more than one instance of a class Dapper gets confused, and renaming columns won't work because you will rename both instances.
Here is the solution I have come up with. It's similar to a lot of examples that use a dictionary, except:
it can nest to as many levels as you like
can cope with Dappers 7 item limit
can cope with duplicates of the same class
can be reused e.g., for Get, GetCurrent and GetAll
In this example there is an Auction that has many Lots. Each Lot may have 1 or many Items. Items might be packs of Items. The Items are from a limited catalogue and we like relational data, so a Things table contains the details on each Item, like colour, size, etc. Here we are only getting a single Lot, but getting an Auction is the same with another level on top for Auction.
Parameter 1 - The SQL to get everything in one go
Parameter 2 - A Type array of each object we'll get back. For this reason it's best to order your SELECT to group the fields into the classes
Parameter 3 - Call the method we're about to write with the SQL result
Parameter 4 - Standard parameter array for the SQL. SQL Injection is bad, m'kay?
public async Task<List<Lot>> GetAll(int auctionId)
{
using (var connection = new SqlConnection(_appSettings.ConnectionString))
{
await connection.OpenAsync();
var result = new List<Lot>();
await connection.QueryAsync($#"
SELECT [Lot].*,
[Item].[Id],
[Item].[LotId],
[Item].[Notes],
itemDetails.[Id],
itemDetails.[ThingId],
itemDetails.[Colour],
itemDetails.[Size],
[SubItem].[Id],
[SubItem].[ItemId],
[SubItem].[Notes],
subItemDetails.[Id],
subItemDetails.[ThinId],
subItemDetails.[Colour],
subItemDetails.[Size]
FROM [Lot]
INNER JOIN [Item] ON [Item].[LotId] = [Lot].[Id]
LEFT JOIN [Thing] AS itemDetails ON itemDetails.[Id] = [Item].[ThingId]
LEFT JOIN [SubItem] ON [SubItem].[ItemId] = [Item].[Id]
LEFT JOIN [Thing] AS subItemDetails ON subItemDetails.[Id] = [SubItem].[ThingId]
WHERE [AuctionId] = #{nameof(auctionId)}
ORDER BY [Lot].[Id], [Item].[Id], [Expansion].[Id];",
new Type[] {
typeof(Lot),
typeof(Item),
typeof(Thing),
typeof(Expansion),
typeof(Thing)
},
MapResult(result),
new
{
AuctionId = auctionId
}
);
return result.ToList();
}
}
private Func<object[], Lot> MapResult(List<Lot> result)
{
return (obj) =>
{
Lot lot = (Lot)obj[0];
Item item = (Item)obj[1];
Thing itemDetails = (Thing)obj[2];
SubItem subItem = (SubItem)obj[3];
Thing subItemDetails = (Thing)obj[4];
if (lot != null)
{
if (result.Any(a => a.Id == lot.Id))
{
lot = result.First(a => a.Id == lot.Id);
}
else
{
result.Add(lot);
}
}
if (item != null)
{
if (lot.Items.Any(i => i.Id == item.Id))
{
item = lot.Items.First(i => i.Id == item.Id);
}
else
{
lot.Items.Add(item.FromThing(itemDetails));
}
}
if (expansion != null)
{
if (item.SubItems.Any(e => e.Id == subItem.Id) == false)
{
item.SubItems.Add(subItem.FromThing(subItemDetails));
}
}
return null;
};
}
MapResult is the meat of the code. It returns a Func with two types, the Type array we defined above and the return Type, and takes a List of the top level object.
I then map each item from the object array to another of it's actual type. This keeps the code easier to read, and enables properties and methods of the object to be accessed without issue.
Then it's a case of stepping down the hierarchy, checking at each step if one already exists with a matching id, and swapping the iterator to a reference to it if it does. This means that following code will add to the existing item.
In the particular case I've also added a FromThing function to allow easier combining of object properties.
As we discussed in comments, this is an issue due to duplicate column names in two tables. This is where the similar issue and solution could be found. But, it does not include "mapping by code" as you said. So it is not exact duplicate.
I suggest you change the names of ID fields in your tables to avoid colliding them. Of-course, you should also change the name of your POCO properties and mappings accordingly.
If you cannot change the column names in table, change the POCO property name, and use the column alias in SQL query to match those new property names.
I hope this helps you.
The problem was effectively the name of the properties.
I solved it using Custom Column Mapping to do it i got two possible solutions:
Without extensions
First, we define a Dictionary with the name of the column as key, and the name of the property as value
IDictionary<string, string> columnMaps = new Dictionary<string, string>()
{
{ "Macro_tarea.ID", "ID" },
{ "Registros.ID", "ID" }
};
Then, we define a delegate to obtain the PropertyInfo object of the property to which we intend to assign the alias of the previous dictionary
var mapper = new Func<Type, string, PropertyInfo>((type, columnName) =>
{
if (columnMaps.ContainsKey(columnName))
return type.GetProperty(columnMaps[columnName]);
else
return type.GetProperty(columnName);
});
Now, we define an object that implements the ITypeMap interface using CustomPropertyTypeMap implementation
ITypeMap MacroTareaMapper = new CustomPropertyTypeMap(typeof(Macro_tarea),
(type, columnName) => mapper(type, columnName));
ITypeMap RegistrosMapper = new CustomPropertyTypeMap(typeof(Registros),
(type, columnName) => mapper(type, columnName));
Then we register them
SqlMapper.SetTypeMap(typeof(Macro_tarea), MacroTareaMapper);
SqlMapper.SetTypeMap(typeof(Registros), RegistrosMapper);
Simpler solution with Dapper.FluentMap
It is implemented as follows:
We create a class that inherits from EntityMap<T> and using the Map method we define which column corresponds to each property. For example,
internal class Macro_tareaMap : EntityMap<Macro_tarea>
{
internal Macro_tareaMap()
{
//Mi propiedad ID esta asociada a la columna Macro_tarea.ID
Map(x => x.ID).ToColumn("Macro_tarea.ID");
}
}
Then just register it
FluentMapper.Initialize((config) =>
{
config.AddMap(new Macro_tareaMap());
});
Hope it helps another people!
Source: https://medium.com/dapper-net/custom-columns-mapping-1cd45dfd51d6

How do I simplify the access of a has-many relationship with the entity framework?

Here is what I want to do:
var user = db.User.First(conditions);
user.Book.First();
Here is currently how I have to do that.
var user = db.User.Include("Book").First(conditionsForUser);
user.Book.First();
The reason why I want to simplify this, is because I don't want to have to specify what is included every time I want to access a relationship. Seems very cumbersome.
e.g.: I would like to just be able to do the following, given I have previously retrieved a user:
user.Book.First()
user.Blog.First()
user.SomeOtherHasManyRelationship.Where(conditions)
Here is what I have so far:
public object RelationshipFor(string relationship)
{
using (var db = User.DbContext())
{
var relationshipType = TypeRepresentedBy(relationship); // unused for now, not sure if I need the type of the relationship
var myTable = ((ICollection)db.Send(RelationshipName)); // RelationshipName is "User" in this instance.
var meWithRelationship = myTable.Where(i => i.Send(IdColumn) == Id).Include(relationship); // currently, myTable doesn't know about 'Where' for some reason.
return meWithRelationship.Send(relationship);
}
}
And then how that would be used would be the following:
user.RelationshipFor("Book") // returns a list of books
I have some other logic in my code which abstracts that further which would allow me to do user.Book.First().
Hopefully I can get permission to open source a lot of this, as I'm modelling a lot of the api after ActiveRecord-style crud.
Note, that I'm using I set of extensions I made to help dealing with dynamicness less painful: https://github.com/NullVoxPopuli/csharp-extensions
UPDATE 1:
public object RelationshipFor(string relationship)
{
using (var db = User.DbContext())
{
var myTable = (DbSet<DatabaseModels.User>)db.Send(RelationshipName);
var myInclude = myTable.Include(i => i.Send(relationship));
var meWithRelationship = myInclude.First(i => (long)i.Send(IdColumn) == Id);
return meWithRelationship.Send(relationship);
}
}
For now, I've hard coded the cast of the user in an attempt to just get something working.
My error now is:
Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'.
This is not a trivial problem, and there's no "one size fits all" approach. What you actually seem to be after is lazy loading, which was not included in EF7 for many reasons.
I don't know what the code you show is supposed to do, but one option would be to introduce a repository pattern, where you specify the "entities to include" at the collection level:
public class UserRepository
{
private readonly IQueryable<User> _dataSet;
public UserRepository(IQueryable<User> userDataSet)
{
_dataSet = userDataSet;
}
public IQueryable<User> Include()
{
return _dataSet.Include(u => u.Book)
.Include(u => u.Blog);
}
}
And you can move lots of the logic to a generic base class, leaving you with just the Include() method. You can for example work with strings as you show (or enums, or ...), to only select related entities to include:
public class GenericRepository
{
// ...
public IQueryable<User> Include(string includeGroup = null)
{
return IncludeGroup(includeGroup);
}
protected virtual IncludeGroup(string includeGroup)
{
return _dataSet;
}
}
And then in UserRepository:
protected override IQueryable<User> IncludeGroup(string includeGroup)
{
switch (includeGroup.ToUpperInvariant())
{
case "BOOK":
return _dataSet.Include(u => u.Book)
.Include(u => u.Book.Author);
case "BLOG":
return _dataSet.Include(u => u.Blog);
default:
return base.Include(includeGroup);
}
}
And then use it like this:
var userRepo = new UserRepository(db.User);
var userWithBooks = userRepo.Include("Book");
var firstUser = userWithBooks.FirstOrDefault(u => u.Name == "Foo");
var firstUserFirstBook = firstUser.Book.FirstOrDefault();
One alternative would be to always include all navigation properties (recursively), but that would be a horrible approach in terms of query efficiency, as every query will be one massive join to all related tables, whether that is necessary or not.

Related Tables LINQ statement / EF 6 - One to Many, Most recent record

I would like to return some data from 2 related tables. I have a one to many relationship. One WebLead can have many Pricing records. I would like to return the data for the WebLead and the data for most recent record inserted into the Pricing table.
I am new to LINQ and EF. Here is what I have so far but this is only returning the WebLeads table data...What am I missing? Do I need to add a FirstOrDefault for the Pricing table?
var priceRefi = db.WebLeads.Include(p => p.Pricings)
.Where(l => l.LoanAgent.Equals(LoanAgent) && l.LeadStatus.Equals("Priced");
then to populate the view model:
PricedRefiLeads = priceRefi.ToList(),
UPDATE: I am sorry I left so much out. I updated my query to the following (LoanAgent is just a string parameter)
var priceRefi = from lead in db.WebLeads
where lead.LoanAgent == LoanAgent && lead.LeadStatus == "Priced"
select new LeadWithLastPricing()
{
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
I then want to take the results of that query and return it as a list to my view model:
var viewModel = new PipelineViewModel
{
////
PricedRefiLeads = priceRefi.ToList(),
}
I am seeing the following error on the priceRefi.ToList():
Cannot implicitly convert type
'System.Collections.Generic.List(LoanModule.ViewModels.LeadWithLastPricing)'
to 'System.Collections.Generic.List(LoanModule.Models.WebLead)
I am new to MVC. As I read this error, I understand that I must be missing something in my PipelineViewModel but I am not sure what that is.
In PipelineViewModel, I do have:
public List<WebLead> PricedRefiLeads { get; set; }
What am I missing? Forgive me if I left information out, I am struggling to wrap my head around this.
I am using a number of assumptions, for information not specifically mentioned in your question:
LoanAgent is a (local) string variable representing the agent you want to filter on.
Pricing has a field named PricingDate that is of type DateTime.
Then you can do it like this:
// I am assuming a Pricing has a DateTime field named "PricingDate"
var priceRefi =
from lead in WebLeads
where lead.LoanAgent == LoanAgent && lead.LeadStatus == "Priced"
select new {
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
Note that this returns an anonymous object as the projection result. If you want to pass this result on, you should create a class:
public class LeadWithLastPricing
{
public Lead Lead { get; set; }
public Pricing LastPricing { get; set; }
}
And do the select part like this:
// ...
select new LeadWithLastPricing() {
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
For your second error, change this:
public List<WebLead> PricedRefiLeads { get; set; }
To
public List<LeadWithLastPricing> PricedRefiLeads { get; set; }
And use it like:
var viewModel = new PipelineViewModel
{
PricedRefiLeads = priceRefi.ToList(),
}
You can try:
var query =
from l in db.WebLeads
let maxDate = l.Pricings.Max(p => p.InsertDate)
where l.LoanAgentID == someLoanAgentID
&& l.LeadStatus == "Priced"
select new { Lead = l, Pricing = l.Pricings.FirstOrDefault(x => x.InsertDate == maxDate) };
This will give objects with two properties: Lead and Pricing which are WebLead and Pricings objects. Please forgive syntax errors this is streight out of notepad.
Edit: I suppose I should tell you how to use the result object:
foreach (var MyLead in query)
{
string customerName = MyLead.Lead.CustomerName; // i.e. some property of WebLead
DateTime InsertDate = MyLead.Pricing.InsertDate; // i.e. some property of Pricings
}
You can project directly into a WebLead instead of creating a new class, by doing the following:
var priceRefi = db.WebLeads
.Include(p => p.Pricings)
.Where(l => l.LoanAgent == LoanAgent)
.Where(l => l.LeadStatus == "Priced")
.Select(lead=>new WebLead {
lead.LoanAgent,
lead.LeadStatus,
...,
Pricings=lead.Pricings.OrderByDescending(x=>x.‌​PricingDate).Take(1)});

Reusable Calculations For LINQ Projections In Entity Framework (Code First)

My domain model has a lot of complex financial data that is the result of fairly complex calculations on multiple properties of various entities. I generally include these as [NotMapped] properties on the appropriate domain model (I know, I know - there's plenty of debate around putting business logic in your entities - being pragmatic, it just works well with AutoMapper and lets me define reusable DataAnnotations - a discussion of whether this is good or not is not my question).
This works fine as long as I want to materialize the entire entity (and any other dependent entities, either via .Include() LINQ calls or via additional queries after materialization) and then map these properties to the view model after the query. The problem comes in when trying to optimize problematic queries by projecting to a view model instead of materializing the entire entity.
Consider the following domain models (obviously simplified):
public class Customer
{
public virtual ICollection<Holding> Holdings { get; private set; }
[NotMapped]
public decimal AccountValue
{
get { return Holdings.Sum(x => x.Value); }
}
}
public class Holding
{
public virtual Stock Stock { get; set; }
public int Quantity { get; set; }
[NotMapped]
public decimal Value
{
get { return Quantity * Stock.Price; }
}
}
public class Stock
{
public string Symbol { get; set; }
public decimal Price { get; set; }
}
And the following view model:
public class CustomerViewModel
{
public decimal AccountValue { get; set; }
}
If I attempt to project directly like this:
List<CustomerViewModel> customers = MyContext.Customers
.Select(x => new CustomerViewModel()
{
AccountValue = x.AccountValue
})
.ToList();
I end up with the following NotSupportedException: Additional information: The specified type member 'AccountValue' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Which is expected. I get it - Entity Framework can't convert the property getters into a valid LINQ expression. However, if I project using the exact same code but within the projection, it works fine:
List<CustomerViewModel> customers = MyContext.Customers
.Select(x => new CustomerViewModel()
{
AccountValue = x.Holdings.Sum(y => y.Quantity * y.Stock.Price)
})
.ToList();
So we can conclude that the actual logic is convertible to a SQL query (I.e., there's nothing exotic like reading from disk, accessing external variables, etc.).
So here's the question: is there any way at all to make logic that should be convertible to SQL reusable within LINQ to entity projections?
Consider that this calculation may be used within many different view models. Copying it to the projection in each action is cumbersome and error prone. What if the calculation changes to include a multiplier? We'd have to manually locate and change it everywhere it's used.
One thing I have tried is encapsulating the logic within an IQueryable extension:
public static IQueryable<CustomerViewModel> WithAccountValue(
this IQueryable<Customer> query)
{
return query.Select(x => new CustomerViewModel()
{
AccountValue = x.Holdings.Sum(y => y.Quantity * y.Stock.Price)
});
}
Which can be used like this:
List<CustomerViewModel> customers = MyContext.Customers
.WithAccountValue()
.ToList();
That works well enough in a simple contrived case like this, but it's not composable. Because the result of the extension is an IQueryable<CustomerViewModel> and not a IQueryable<Customer> you can't chain them together. If I had two such properties in one view model, one of them in another view model, and then the other in a third view model, I would have no way of using the same extension for all three view models - which would defeat the whole purpose. With this approach, it's all or nothing. Every view model has to have the exact same set of calculated properties (which is rarely the case).
Sorry for the long-winded question. I prefer to provide as much detail as possible to make sure folks understand the question and potentially help others down the road. I just feel like I'm missing something here that would make all of this snap into focus.
I did a lot of research on this the last several days because it's been a bit of a pain point in constructing efficient Entity Framework queries. I've found several different approaches that all essentially boil down to the same underlying concept. The key is to take the calculated property (or method), convert it into an Expression that the query provider knows how to translate into SQL, and then feed that into the EF query provider.
I found the following libraries/code that attempted to solve this problem:
LINQ Expression Projection
http://www.codeproject.com/Articles/402594/Black-Art-LINQ-expressions-reuse and http://linqexprprojection.codeplex.com/
This library allows you to write your reusable logic directly as an Expression and then provides the conversion to get that Expression into your LINQ query (since the query can't directly use an Expression). The funny thing is that it'll be translated back to an Expression by the query provider. The declaration of your reusable logic looks like this:
private static Expression<Func<Project, double>> projectAverageEffectiveAreaSelector =
proj => proj.Subprojects.Where(sp => sp.Area < 1000).Average(sp => sp.Area);
And you use it like this:
var proj1AndAea =
ctx.Projects
.AsExpressionProjectable()
.Where(p => p.ID == 1)
.Select(p => new
{
AEA = Utilities.projectAverageEffectiveAreaSelector.Project<double>()
});
Notice the .AsExpressionProjectable() extension to set up projection support. Then you use the .Project<T>() extension on one of your Expression definitions to get the Expression into the query.
LINQ Translations
http://damieng.com/blog/2009/06/24/client-side-properties-and-any-remote-linq-provider and https://github.com/damieng/Linq.Translations
This approach is pretty similar to the LINQ Expression Projection concept except it's a little more flexible and has several points for extension. The trade off is that it's also a little more complex to use. Essentially you still define your reusable logic as an Expression and then rely on the library to convert that into something the query can use. See the blog post for more details.
DelegateDecompiler
http://lostechies.com/jimmybogard/2014/05/07/projecting-computed-properties-with-linq-and-automapper/ and https://github.com/hazzik/DelegateDecompiler
I found DelegateDecompiler via the blog post on Jimmy Bogard's blog. It has been a lifesaver. It works well, is well architected, and requires a lot less ceremony. It does not require you to define your reusable calculations as an Expression. Instead, it constructs the necessary Expression by using Mono.Reflection to decompile your code on the fly. It knows which properties, methods, etc. need to be decompiled by having you decorate them with ComputedAttribute or by using the .Computed() extension within the query:
class Employee
{
[Computed]
public string FullName
{
get { return FirstName + " " + LastName; }
}
public string LastName { get; set; }
public string FirstName { get; set; }
}
This can also be easily extended, which is a nice touch. For example, I set it up to look for the NotMapped data annotation instead of having to explicitly use the ComputedAttribute.
Once you've set up your entity, you just trigger decompilation by using the .Decompile() extension:
var employees = ctx.Employees
.Select(x => new
{
FullName = x.FullName
})
.Decompile()
.ToList();
You can encapsulate logic by creating a class that contains the original Entity and the additional calculated property. You then create helper methods that project to the class.
For example, if we were trying to calculate the tax for an Employee and a Contractor entity, we could do this:
//This is our container for our original entity and the calculated field
public class PersonAndTax<T>
{
public T Entity { get; set; }
public double Tax { get; set; }
}
public class PersonAndTaxHelper
{
// This is our middle translation class
// Each Entity will use a different way to calculate income
private class PersonAndIncome<T>
{
public T Entity { get; set; }
public int Income { get; set; }
}
Income calculating methods
public static IQueryable<PersonAndTax<Employee>> GetEmployeeAndTax(IQueryable<Employee> employees)
{
var query = from x in employees
select new PersonAndIncome<Employee>
{
Entity = x,
Income = x.YearlySalary
};
return CalcualateTax(query);
}
public static IQueryable<PersonAndTax<Contractor>> GetContratorAndTax(IQueryable<Contractor> contractors)
{
var query = from x in contractors
select new PersonAndIncome<Contractor>
{
Entity = x,
Income = x.Contracts.Sum(y => y.Total)
};
return CalcualateTax(query);
}
Tax calculation is defined in one place
private static IQueryable<PersonAndTax<T>> CalcualateTax<T>(IQueryable<PersonAndIncome<T>> personAndIncomeQuery)
{
var query = from x in personAndIncomeQuery
select new PersonAndTax<T>
{
Entity = x.Entity,
Tax = x.Income * 0.3
};
return query;
}
}
Our view model projections using the Tax property
var contractorViewModel = from x in PersonAndTaxHelper.GetContratorAndTax(context.Contractors)
select new
{
x.Entity.Name,
x.Entity.BusinessName
x.Tax,
};
var employeeViewModel = from x in PersonAndTaxHelper.GetEmployeeAndTax(context.Employees)
select new
{
x.Entity.Name,
x.Entity.YearsOfService
x.Tax,
};

How to architect my service/repository code with Linq2Sql!

I have a problem in architecting my application.
I have the following structure with only important aspects shown.
namespace Domain
{
public class Invoice
{
//properties
}
public class InvoiceRepository
{
public Linq2SqlContext context = new Linq2SqlContext();
public IQueryable<Invoice> GetInvoices()
{
var query = from inv in _dbctx.Invoices orderby inv.invInvoiceDate descending select GetInvoice(inv) ;
return query;
}
}
public class InvoiceService()
{
public InvoiceRepository _repository = new InvoiceRepositroy();
public IQueryable<Invoice> GetInvoices()
{
return _repository.GetInvoices();
}
}
}
namespace MyApp
{
public class UI
{
public InvoiceService _service = new InvoiceService();
public void FilterInvoices()
{
var query =
(
from i in _service.GetInvoices()
from s in _service.GetStatuses()
where i.ProjectID == _projectid &&
s.ID == i.Status
select new
{
InvoiceID = i.ID,
DocumentTotal = i.TotalDue.ToString(),
Created = i.Created,
WeekEnding = i.WeekEnding,
Status = s.staStatus
}
).Skip(_pageIndex * _pageSize).Take(_pageSize);
}
}
{
So I want to return IQueryable from my service so I can
filter from client code. But the problem I'm coming up with
is the FilterInvoices method errors with "No supported
translation to sql" because of the GetInvoice method
which is iused to return an Invoice entity (this is
a layer on top op the LInq2 sql layer) and not an Linq2sql Invoice entity.
So how do I return a IQueryable from my service with this structure??
Also how do I sort and return a IQureyable in repository GetInvoices.
Hope this makes sense.
Malcolm
linq2sql thinks GetInvoice (within GetInvoices) is a stored procedure. One way around it
var query = from inv in _dbctx.Invoices orderby inv.invInvoiceDate descending select inv ;
though that would pass back the objects generated by your datacontext. If you wanted to populated custom objects you could iterated over the collection creating your custom Invoice objects and populating them.
foreach(var inv in query) { somelist.Add(new MyCustomInvoince() { id = inv.id ... }
EDIT: The above will return a list. Use the following to return IQueryable
return from item in query
select GetInvoice(item);
The difference is at this stage your are using Linq2Objects, and that provider will know how to call GetInvoice
You cannot query with LTS (Linq to SQL) something built 'on top' of the LTS layer.
The reason is that the LTS entities layer is a mapping of the content of you database, and the query you perform is 'just' translated into SQL.
I personnaly use another approach to keep an independance between my layers...
I create interfaces that match my LTS entites, and I use the Cast<>() method to have my repository return the interface instead of the concrete implementation.
It works perfectly.
You need to extend the base entity (no pb as it is a partial class) :
partial class Employee : IEmployee
And you need this property in your repository :
public IQueryable<IEmployee> Query
{
get
{
return this._context.Employees.Cast<IEmployee>();
}
}
Based on this, you could code a generic repository, but it's another story (more complicated)
This is just off the top of my head, but you could try:
from i in _service.GetInvoices().AsEnumerable()

Categories

Resources