How to create a custom property in a Linq-to-SQL entity class? - c#

I have two tables Studies and Series. Series are FK'd back to Studies so one Study contains a variable number of Series.
Each Series item has a Deleted column indicating it has been logically deleted from the database.
I am trying to implement a Deleted property in the Study class that returns true only if all the contained Series are deleted.
I am using O/R Designer generated classes, so I added the following to the user modifiable partial class for the Study type:
public bool Deleted
{
get
{
var nonDeletedSeries = from s in Series
where !s.Deleted
select s;
return nonDeletedSeries.Count() == 0;
}
set
{
foreach (var series in Series)
{
series.Deleted = value;
}
}
}
This gives an exception "The member 'PiccoloDatabase.Study.Deleted' has no supported translation to SQL." when this simple query is executed that invokes get:
IQueryable<Study> dataQuery = dbCtxt.Studies;
dataQuery = dataQuery.Where((s) => !s.Deleted);
foreach (var study in dataQuery)
{
...
}
Based on this http://www.foliotek.com/devblog/using-custom-properties-inside-linq-to-sql-queries/, I tried the following approach:
static Expression<Func<Study, bool>> DeletedExpr = t => false;
public bool Deleted
{
get
{
var nameFunc = DeletedExpr.Compile();
return nameFunc(this);
}
set
{ ... same as before
}
}
I get the same exception when a query is run that there is no supported translation to SQL. (
The logic of the lambda expression is irrelevant yet - just trying to get past the exception.)
Am I missing some fundamental property or something to allow translation to SQL? I've read most of the posts on SO about this exception, but nothing seems to fit my case exactly.

I believe the point of LINQ-to-SQL is that your entities are mapped for you and must have correlations in the database. It appears that you are trying to mix the LINQ-to-Objects and LINQ-to-SQL.
If the Series table has a Deleted field in the database, and the Study table does not but you would like to translate logical Study.Deleted into SQL, then extension would be a way to go.
public static class StudyExtensions
{
public static IQueryable<study> AllDeleted(this IQueryable<study> studies)
{
return studies.Where(study => !study.series.Any(series => !series.deleted));
}
}
class Program
{
public static void Main()
{
DBDataContext db = new DBDataContext();
db.Log = Console.Out;
var deletedStudies =
from study in db.studies.AllDeleted()
select study;
foreach (var study in deletedStudies)
{
Console.WriteLine(study.name);
}
}
}
This maps your "deleted study" expression into SQL:
SELECT t0.study_id, t0.name
FROM study AS t0
WHERE NOT EXISTS(
SELECT NULL AS EMPTY
FROM series AS t1
WHERE (NOT (t1.deleted = 1)) AND (t1.fk_study_id = t0.study_id)
)
Alternatively you could build actual expressions and inject them into your query, but that is an overkill.
If however, neither Series nor Study has the Deleted field in the database, but only in memory, then you need to first convert your query to IEnumerable and only then access the Deleted property. However doing so would transfer records into memory before applying the predicate and could potentially be expensive. I.e.
var deletedStudies =
from study in db.studies.ToList()
where study.Deleted
select study;
foreach (var study in deletedStudies)
{
Console.WriteLine(study.name);
}

When you make your query, you will want to use the statically defined Expression, not the property.
Effectively, instead of:
dataQuery = dataQuery.Where((s) => !s.Deleted);
Whenever you are making a Linq to SQL query, you will instead want to use:
dataQuery = dataQuery.Where(DeletedExpr);
Note that this will require that you can see DeletedExpr from dataQuery, so you will either need to move it out of your class, or expose it (i.e. make it public, in which case you would access it via the class definition: Series.DeletedExpr).
Also, an Expression is limited in that it cannot have a function body. So, DeletedExpr might look something like:
public static Expression<Func<Study, bool>> DeletedExpr = s => s.Series.Any(se => se.Deleted);
The property is added simply for convenience, so that you can also use it as a part of your code objects without needing to duplicate the code, i.e.
var s = new Study();
if (s.Deleted)
...

Related

Execute custom SQL before running FromSqlRaw in Entity Framework Core 6 or above in SQL Server

I only need it to work for SQL Server. This is an example. The question is about a general approach.
There is a nice extension method from https://entityframework-extensions.net called WhereBulkContains. It is, sort of, great, except that the code of the methods in this library is obfuscated and they do not produce valid SQL when .ToQueryString() is called on IQueryable<T> with these extension methods applied.
Subsequently, I can't use such methods in production code as I am not "allowed" to trust such code due to business reasons. Sure, I can write tons of tests to ensure that WhereBulkContains works as expected, except that there are some complicated cases where the performance of WhereBulkContains is well below stellar, whereas properly written SQL works in a blink of an eye. And (read above), since the code of this library is obfuscated, there is no way to figure out what's wrong there without spending a large amount of time. We would've bought the license (as this is not a freeware) if the library weren't obfuscated. All together that basically kills the library for our purposes.
This is where it gets interesting. I can easily create and populate a temporary table, e.g. (I have a table called EFAgents with an int PK called AgentId in the database):
private string GetTmpAgentSql(IEnumerable<int> agentIds) => #$"
drop table if exists #tmp_Agents;
create table #tmp_Agents (AgentId int not null, primary key clustered (AgentId asc));
{(agentIds
.Chunk(1_000)
.Select(e => $#"
insert into #tmp_Agents (AgentId)
values
({e.JoinStrings("), (")});
")
.JoinStrings(""))}
select 0 as Result
";
private const string AgentSql = #"
select a.* from EFAgents a inner join #tmp_Agents t on a.AgentID = t.AgentId";
where GetContext returns EF Core database context and JoinStrings comes from Unity.Interception.Utilities and then use it as follows:
private async Task<List<EFAgent>> GetAgents(List<int> agentIds)
{
var tmpSql = GetTmpAgentSql(agentIds);
using var ctx = GetContext();
// This creates a temporary table and populates it with the ids.
// This is a proprietary port of EF SqlQuery code, but I can post the whole thing if necessary.
var _ = await ctx.GetDatabase().SqlQuery<int>(tmpSql).FirstOrDefaultAsync();
// There is a DbSet<EFAgent> called Agents.
var query = ctx.Agents
.FromSqlRaw(AgentSql)
.Join(ctx.Agents, t => t.AgentId, a => a.AgentId, (t, a) => a);
var sql = query.ToQueryString() + Environment.NewLine;
// This should provide a valid SQL; https://entityframework-extensions.net does NOT!
// WriteLine - writes to console or as requested. This is irrelevant to the question.
WriteLine(sql);
var result = await query.ToListAsync();
return result;
}
So, basically, I can do what I need in two steps:
using var ctx = GetContext();
// 1. Create a temp table and populate it - call GetTmpAgentSql.
...
// 2. Build the join starting from `FromSqlRaw` as in example above.
This is doable, half-manual, and it is going to work.
The question is how to do that in one step, e.g., call:
.WhereMyBulkContains(aListOfIdConstraints, whateverElseIsneeded, ...)
and that's all.
I am fine if I need to pass more than one parameter in each case in order to specify the constraints.
To clarify the reasons why do I need to go into all these troubles. We have to interact with a third party database. We don't have any control of the schema and data there. The database is large and poorly designed. That resulted in some ugly EFC LINQ queries. To remedy that, some of that ugliness was encapsulated into a method, which takes IQueryable<T> (and some more parameters) and returns IQueryable<T>. Under the hood this method calls WhereBulkContains. I need to replace this WhereBulkContains by, call it, WhereMyBulkContains, which would be able to provide correct ToQueryString representation (for debugging purposes) and be performant. The latter means that SQL should not contain in clause with hundreds (and even sometimes thousands) of elements. Using inner join with a [temp] table with a PK and having an index on the FK field seem to do the trick if I do that in pure SQL. But, ... I need to do that in C# and effectively in between two LINQ method calls. Refactoring everything is also not an option because that method is used in many places.
Thanks a lot!
I think you really want to use a Table Valued Parameter.
Creating an SqlParameter from an enumeration is a little fiddly, but not too difficult to get right;
CREATE TYPE [IntValue] AS TABLE (
Id int NULL
)
private IEnumerable<SqlDataRecord> FromValues(IEnumerable<int> values)
{
var meta = new SqlMetaData(
"Id",
SqlDbType.Int
);
foreach(var value in values)
{
var record = new SqlDataRecord(
meta
);
record.SetInt32(0, value);
yield return record;
}
}
public SqlParameter ToIntTVP(IEnumerable<int> values){
return new SqlParameter()
{
TypeName = "IntValue",
SqlDbType = SqlDbType.Structured,
Value = FromValues(values)
};
}
Personally I would define a query type in EF Core to represent the TVP. Then you can use raw sql to return an IQueryable.
public class IntValue
{
public int Id { get; set; }
}
modelBuilder.Entity<IntValue>(e =>
{
e.HasNoKey();
e.ToView("IntValue");
});
IQueryable<IntValue> ToIntQueryable(DbContext ctx, IEnumerable<int> values)
{
return ctx.Set<IntValue>()
.FromSqlInterpolated($"select * from {ToIntTVP(values)}");
}
Now you can compose the rest of your query using Linq.
var ids = ToIntQueryable(ctx, agentIds);
var query = ctx.Agents
.Where(a => ids.Any(i => i.Id == a.Id));
I would propose to use linq2db.EntityFrameworkCore (note that I'm one of the creators). It has built-in temporary tables support.
We can create simple and reusable function which filters records of any type:
public static class HelperMethods
{
private class KeyHolder<T>
{
[PrimaryKey]
public T Key { get; set; } = default!;
}
public static async Task<List<TEntity>> GetRecordsByIds<TEntity, TKey>(this IQueryable<TEntity> query, IEnumerable<TKey> ids, Expression<Func<TEntity, TKey>> keyFunc)
{
var ctx = LinqToDBForEFTools.GetCurrentContext(query) ??
throw new InvalidOperationException("Query should be EF Core query");
// based on DbContext options, extension retrieves connection information
using var db = ctx.CreateLinqToDbConnection();
// create temporary table and BulkCopy records into that table
using var tempTable = await db.CreateTempTableAsync(ids.Select(id => new KeyHolder<TKey> { Key = id }), tableName: "temporaryIds");
var resultQuery = query.Join(tempTable, keyFunc, t => t.Key, (q, t) => q);
// we use ToListAsyncLinqToDB to avoid collission with EF Core async methods.
return await resultQuery.ToListAsyncLinqToDB();
}
}
Then we can rewrite your function GetAgents to the following:
private async Task<List<EFAgent>> GetAgents(List<int> agentIds)
{
using var ctx = GetContext();
var result = await ctx.Agents.GetRecordsByIds(agentIds, a => a.AgentId);
return result;
}

Get List of Objects Added to Entity Framework 6 Include List

Background
I have created object graphs in Entity Framework where any given object A will have a table Ac that tracks changes for it. These objects may also connect to each other, such as A being 1-many to B. Here is an example graph:
A -> Ac
/ \
Bc <- B \
/ \
Cc <- C D -> Dc
I want to be able to load an object and specific connected objects at a point in time by using the change tables to pull those records and apply them. Ideally, I'd like to be able to either use or mimic the .Include function from Entity Framework.
The Issue
Pulling out which objects are already included in an IQueryable is not as easy as I guessed it would be. Looking at an IQueryable<T> with a child object of T Include()-ed, I can see that these relationships are stored in some sort of Span object within an Arguments property - but these are both internal classes and trying to retrieve this information has a lot of steps.
Here is what I have so far:
public static void LoadVersion<T>( this IQueryable<T> query, DateTime targetDateTime )
{
//grab the value of the "Arguments" property on query.Expression
//this has to be done through reflection because "Arguments" is not accessible otherwise
PropertyInfo argumentsPropertyInfo = query.Expression.GetType().GetProperties().FirstOrDefault( x => x.Name == "Arguments" );
dynamic argumentsPropertyValue = argumentsPropertyInfo.GetValue( query.Expression );
for (int i = 0; i < argumentsPropertyValue.Count; i++)
{
//This gets me a System.Data.Entity.Core.Objects.Span, but that class is internal
//In the watch, I can see span -> SpanList[0].Navigations[0] gives me the name of the class in the .Include()
// This is the value I need
dynamic span = argumentsPropertyValue[i].Value;
//So if I try to pull it out using the same reflection trick as before, I get
// a dynamic {System.Reflection.PropertyInfo[0]} (not a list, as you would normally expect),
// and accessing those values & methods makes the debugger exit without an exception
dynamic spanPropertyInfo = argumentsPropertyValue[i].Value.GetType().GetProperties();
//this makes the debugger exit without an exception
dynamic spanPropertyValue = spanPropertyInfo[0].GetValue(span);
//this also makes the debugger exit without an exception (with the above line commented out, of course)
dynamic spanPropertyValue2 = spanPropertyInfo.GetValue( span );
}
}
Based on how difficult it is for me to find what is Included in a Query, I can't help but think that I am doing this entirely the wrong way. Digging through some of the Entity Framework 6.1.3 source code hasn't shed much light on this.
Edit
I've been playing around with the code provided by Alex Derck, but I realized I still need a few pieces to make this work the way I want.
Here is the version of VisitMethodCall I implemented:
protected override Expression VisitMethodCall( MethodCallExpression node )
{
if (node.Method.Name != "Include" && node.Method.Name != "IncludeSpan") return base.VisitMethodCall(node);
try
{
string includedObjectName = (string) node.Arguments.First().GetPrivatePropertyValue( "Value" );
if (includedObjectName != null)
{
_includes.Add(includedObjectName);
}
}
catch (Exception e ){ }
return base.VisitMethodCall( node );
}
I'm able to construct a query with includes and get the names of the objects I included using the IncludeVisitor, but the main goal to use these was to be able to find the related tables and add them to the include.
So when I have the equivalent of this:
var query = ctx.Persons.Include(p => p.Parents).Include(p => p.Children);
// includes[0] = "Parents"
// includes[1] = "Children"
var includes = IncludeVisitor.GetIncludes(query.Expression);
I am successfully grabbing the includes, and I can then find the related tables (Parents -> ParentsChanges, Children -> ChildrenChanges), but I'm not 100% sure how to add these back to the include.
The main problem here is when it's a nested statement:
context.A.Include(x => x.B).Include(x => x.C).Include(x => x.B.Select(y => y.D))
I can successfully traverse that whole graph and get the names of A, B, C, and D, but I need to be able to add a statement like this back to the include:
[...].Include(x => x.B.Select(y => y.D.Select(z => z.DChanges)))
I can find DChanges just fine, but I don't know how to build that include back up because I don't know how many steps are between DChanges and the original item (A).
After looking a bit in the source code of Entity Framework I noticed the includes are not part of the Expression, but rather part of the IQueryable. If you think about it, it's pretty obvious it should be that way. Expressions can't actually execute code themselves, they are translated by a provider (which is also part of the IQueryable), and not all providers should know how to translate an Include method. In the source code you can see the IQueryable.Include method calls the following small method:
public ObjectQuery<T> Include(string path)
{
Check.NotEmpty(path, "path");
return new ObjectQuery<T>(QueryState.Include(this, path));
}
The query (casted to an ObjectQuery) simply gets returned and only it's internal QueryState gets changed, nothing at all happens to the expression. In the debugger you can see the EntitySets that will be included if you look in the IQueryable, but I haven't been able to put them into a list (_cachedPlan is always null when I try to access it through reflection).
I think after seeing this, the thing you're trying to do is not possible, so I would keep a static list of strings in my dbContext and implement a custom Include extension method:
public partial class TestDB
{
public static ICollection<Expression> Includes { get; set; } = new List<Expression>();
public TestDB() : base()
{
Includes = new List<Expression>();
}
...
}
public static class EntityExtensions
{
public static IQueryable<T> CustomInclude<T, TProperty>(this IQueryable<T> query,
Expression<Func<T,TProperty>> include) where T : class
{
TestDB.Includes.Add(include);
return query.Include(include);
}
}
You could also 'override' the normal Include method from System.Data.Entity.
I say 'override', because technically it's not really possible to override an extension method, but you can just create an extension method called Include with the same parameters yourself, and if you don't include System.Data.Entity where you use it, there's no ambiguity between your own method and the one from System.Data.Entity:
public static class EntityExtensions
{
public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> query,
Expression<Func<T,TProperty>> include) where T : class
{
TestDB.Includes.Add(include);
var method = typeof(QueryableExtensions)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(m => m.Name == "Include")
.First(m => m.GetParameters().All(p => p.ParameterType.IsGenericType));
var generic = method.MakeGenericMethod(typeof(T), typeof(TProperty));
return (IQueryable<T>)generic.Invoke(query, new object[] { query, include });
}
}
I write here another answer to your question (but not the solution you need).
You can retrieve the objects added to entity framework 6 include list from an already retrieved entity in the same way the entity proxy does.
The property to retrieve if a property should be lazy loaded on access (so not already loaded and not in include list) is Relationship.IsLoaded. You can find the list of relationships in YourEntityWithProxy._entityWrapper.Relationships.
_entityWrapper and other properties are private so you need to use reflection to read them.
With the help of Alex, I was able to get what I wanted.
First, to get the name of the includes, I used a small variation of one of the earlier versions of the answer Alex posted:
internal static class IncludeVisitorExtensions
{
public static object GetPrivatePropertyValue( this object obj, string propName )
{
PropertyInfo propertyInfo = obj.GetType().GetProperty( propName, BindingFlags.Public
| BindingFlags.NonPublic | BindingFlags.Instance );
return propertyInfo.GetValue( obj, null );
}
public static object GetPrivateFieldValue( this object obj, string fieldName )
{
FieldInfo fieldInfo = obj.GetType().GetField( fieldName, BindingFlags.Public
| BindingFlags.NonPublic | BindingFlags.Instance );
return fieldInfo?.GetValue( obj );
}
}
internal class IncludeVisitor : ExpressionVisitor
{
private static readonly IncludeVisitor Visitor;
private static List<string> _includes;
private IncludeVisitor() { }
static IncludeVisitor()
{
Visitor = new IncludeVisitor();
}
public static ICollection<string> GetIncludes( Expression expr )
{
_includes = new List<string>();
Visitor.Visit( expr );
return _includes;
}
protected override Expression VisitMethodCall( MethodCallExpression node )
{
if (node.Method.Name != "Include" && node.Method.Name != "IncludeSpan")
return base.VisitMethodCall( node );
//"Include" == .Where() is present in the query
//"IncludeSpan" == no .Where() in the query
try
{
if (node.Method.Name == "Include")
{
string includedObjectName = (string) node.Arguments.First().GetPrivatePropertyValue("Value");
if (includedObjectName != null)
{
_includes.Add(includedObjectName);
}
}
else if (node.Method.Name == "IncludeSpan")
{
var spanList =
node.Arguments.First().GetPrivatePropertyValue("Value").GetPrivatePropertyValue("SpanList");
var navigations = ((IEnumerable<object>) spanList).Select(s => s.GetPrivateFieldValue("Navigations"));
foreach (var nav in navigations)
_includes.Add(string.Join(".", (IEnumerable<string>) nav));
}
}
catch (Exception e) { }
return base.VisitMethodCall( node );
}
}
One little detail I found when testing his code is the difference in how the included tables are found in the expression based on the presence of a .Where() in the IQueryable<>. Thankfully, this can be checked based on the method name, and while the code is a bit ugly, it does dance around the vastly different structures to return the correct name of the table.
Now I have the name of the table, pluralized, as a string. This is because the name is from the DbContext, so I can reflect over the properties and get the Type of the table:
List<PropertyInfo> contextProperties = typeof( TContext ).GetProperties().ToList();
PropertyInfo prop = contextProperties.First( x => x.Name == s );
With the Type, I can accurately find the table I need via Navigation Properties, then I can build a string to send into my new .Include:
ICollection<string> includes = IncludeVisitor.GetIncludes( query.Expression );
foreach (string include in includes)
{
//sometimes the returned include string will be two tables joined with a '.'; these need to be split and each one checked independently
List<string> split = include.Split( '.' ).ToList();
foreach (string s in split)
{
//using .First here because we expect the property to exist
PropertyInfo prop = contextProperties.First( x => x.Name == s );
//the property will be of type DbSet<ObjectType>, so grab the first generic argument (in this case, the object type)
Type dbSetPropertyType = prop.PropertyType.GetGenericArguments().First();
//Get the type we're looking to add in the .Include
var targetTable = GetTargetTableBasedOnTypeViaNavigation(dbSetPropertyType);
//get the name of the property based on the type of the table we just looked up
PropertyInfo contextProperty = contextProperties.SingleOrDefault(x => x.PropertyType.IsGenericType && x.PropertyType.GetGenericArguments().First().Name == targetTable.Name );
string includeString = "";
//build the string and add it to the query
includeString += include + "." + contextPropertyForChangeTracker.Name;
query = query.Include(includeString);
}
}
This worked on my initial test data sets, though I'm not sure how well it will handle more complex graphs.

How do I tell linq2db how to translate a given expression, ie Split(char) into SQL when it does not know how to do so?

I am using linq2db and while it works well enough for most CRUD operations I have encountered many expressions that it just cannot translate into SQL.
It has gotten to the point where unless I know in advance exactly what kinds of expressions will be involved and have successfully invoked them before, I am worried that any benefit derived from linq2db will be outweighed by the cost of trying to find and then remove (or move away from the server side) the offending expressions.
If I knew how to tell linq2db how to parse an Expression<Func<T,out T>> or whatnot into SQL whenever on an ad-hoc, as-it-is-needed basis, then I would be much more confident and I could do many things using this tool.
Take, for instance, String.Split(char separator), the method that takes a string and a char to return a string[] of each substring between the separator.
Suppose my table Equipment has a nullable varchar field Usages that contains lists of different equipment usages separated by commas.
I need to implement IList<string> GetUsages(string tenantCode, string needle = null) that will give provide a list of usages for a given tenant code and optional search string.
My query would then be something like:
var listOfListOfStringUsages =
from et in MyConnection.GetTable<EquipmentTenant>()
join e in MyConnection.GetTable<Equipment>() on et.EquipmentId = e.EquipmentId
where (et.TenantCode == tenantCode)
where (e.Usages != null)
select e.Usages.Split(','); // cannot convert to sql here
var flattenedListOfStringUsages =
listOfListOfStringUsages.SelectMany(strsToAdd => strsToAdd)
.Select(str => str.Trim())
.Distinct();
var list = flattenedListOfStringUsages.ToList();
However, it would actually bomb out at runtime on the line indicated by comment.
I totally get that linq2db's creators cannot possibly be expected to ship with every combination of string method and major database package.
At the same time I feel as though could totally tell it how to handle this if I could just see an example of doing just that (someone implementing a custom expression).
So my question is: how do I instruct linq2db on how to parse an Expression that it cannot parse out of the box?
A few years ago I wrote something like this:
public class StoredFunctionAccessorAttribute : LinqToDB.Sql.FunctionAttribute
{
public StoredFunctionAccessorAttribute()
{
base.ServerSideOnly = true;
}
// don't call these properties, they are made private because user of the attribute must not change them
// call base.* if you ever need to access them
private new bool ServerSideOnly { get; set; }
private new int[] ArgIndices { get; set; }
private new string Name { get; set; }
private new bool PreferServerSide { get; set; }
public override ISqlExpression GetExpression(System.Reflection.MemberInfo member, params ISqlExpression[] args)
{
if (args == null)
throw new ArgumentNullException("args");
if (args.Length == 0)
{
throw new ArgumentException(
"The args array must have at least one member (that is a stored function name).");
}
if (!(args[0] is SqlValue))
throw new ArgumentException("First element of the 'args' argument must be of SqlValue type.");
return new SqlFunction(
member.GetMemberType(),
((SqlValue)args[0]).Value.ToString(),
args.Skip(1).ToArray());
}
}
public static class Sql
{
private const string _serverSideOnlyErrorMsg = "The 'StoredFunction' is server side only function.";
[StoredFunctionAccessor]
public static TResult StoredFunction<TResult>(string functionName)
{
throw new InvalidOperationException(_serverSideOnlyErrorMsg);
}
[StoredFunctionAccessor]
public static TResult StoredFunction<TParameter, TResult>(string functionName, TParameter parameter)
{
throw new InvalidOperationException(_serverSideOnlyErrorMsg);
}
}
...
[Test]
public void Test()
{
using (var db = new TestDb())
{
var q = db.Customers.Select(c => Sql.StoredFunction<string, int>("Len", c.Name));
var l = q.ToList();
}
}
(and of course you can write your wrappers around Sql.StoredFunction() methods to get rid of specifying function name as a string every time)
Generated sql (for the test in the code above):
SELECT
Len([t1].[Name]) as [c1]
FROM
[dbo].[Customer] [t1]
PS. We use linq2db extensively in our projects and completely satisfied with it. But yes, there is a learning curve (as with almost everything serious we learn) and one needs to spend some time learning and playing with the library in order to feel comfortable with it and see all the benefits it can give.
Try listOfListOfStringUsages.AsEnumerable(). It will enforce executing SelectMany on client side.
UPDATE:
I used the following code to reproduce the issue:
var q =
from t in db.Table
where t.StringField != null
select t.StringField.Split(' ');
var q1 = q
//.AsEnumerable()
.SelectMany(s => s)
.Select(s => s.Trim())
.Distinct()
.ToList();
It's not working. But if I uncomment .AsEnumerable(), it works just fine.

GridView ObjectDataSource LINQ Paging and Sorting using multiple table query

I am trying to create a pageing and sorting object data source that before execution returns all results, then sorts on these results before filtering and then using the take and skip methods with the aim of retrieving just a subset of results from the database (saving on database traffic). this is based on the following article:
http://www.singingeels.com/Blogs/Nullable/2008/03/26/Dynamic_LINQ_OrderBy_using_String_Names.aspx
Now I have managed to get this working even creating lambda expressions to reflect the sort expression returned from the grid even finding out the data type to sort for DateTime and Decimal.
public static string GetReturnType<TInput>(string value)
{
var param = Expression.Parameter(typeof(TInput), "o");
Expression a = Expression.Property(param, "DisplayPriceType");
Expression b = Expression.Property(a, "Name");
Expression converted = Expression.Convert(Expression.Property(param, value), typeof(object));
Expression<Func<TInput, object>> mySortExpression = Expression.Lambda<Func<TInput, object>>(converted, param);
UnaryExpression member = (UnaryExpression)mySortExpression.Body;
return member.Operand.Type.FullName;
}
Now the problem I have is that many of the Queries return joined tables and I would like to sort on fields from the other tables.
So when executing a query you can create a function that will assign the properties from other tables to properties created in the partial class.
public static Account InitAccount(Account account)
{
account.CurrencyName = account.Currency.Name;
account.PriceTypeName = account.DisplayPriceType.Name;
return account;
}
So my question is, is there a way to assign the value from the joined table to the property of the current table partial class? i have tried using.
from a in dc.Accounts
where a.CompanyID == companyID
&& a.Archived == null
select new {
PriceTypeName = a.DisplayPriceType.Name})
but this seems to mess up my SortExpression.
Any help on this would be much appreciated, I do understand that this is complex stuff.
This is a functional programming thing. Mutating Account by doing an assignment is out. New-ing up a new instance of the shape you want is in.
Step 1: declare a class that has the shape of the result you want:
public class QueryResult
{
public int CompanyID {get;set;}
public string CurrencyName {get;set;}
public string PriceTypeName {get;set;}
}
Step 2: project into that class in your query
from ...
where ...
select new QueryResult()
{
CompanyID = a.CompanyID,
CurrencyName = a.Currency.Name,
PriceTypeName = a.PriceType.Name
};
Step 3: Profit! (order by that)
The query generator will use the details of your QueryResult type to generate a select clause with that shape.

How to return query results from method that uses LINQ to SQL

Here is the code I'm working with, I'm still a bit new to LINQ, so this is a work in progress. Specifically, I'd like to get my results from this query (about 7 columns of strings, ints, and datetime), and return them to the method that called the method containing this LINQ to SQL query. A simple code example would be super helpful.
using (ormDataContext context = new ormDataContext(connStr))
{
var electionInfo = from t1 in context.elections
join t2 in context.election_status
on t1.statusID equals t2.statusID
select new { t1, t2 };
}
(In this case, my query is returning all the contents of 2 tables, election and election_status.)
Specifically, I'd like to get my
results from this query (about 7
columns of strings, ints, and
datetime), and return them
Hi, the problem you've got with your query is that you're creating an anonymous type. You cannot return an anonymous type from a method, so this is where you're going to have trouble.
What you will need to do is to create a "wrapper" type that can take an election and an election_status and then return those.
Here's a little sample of what I'm talking about; as you can see I declare a Tuple class. The method that you will wrap your query in returns an IEnumerable.
I hope this helps :-)
class Tuple
{
Election election;
Election_status election_status;
public Tuple(Election election, Election_status election_status)
{
this.election = election;
this.election_status = election_status;
}
}
public IEnumerable<Tuple> getElections()
{
IEnumerable<Tuple> result = null;
using (ormDataContext context = new ormDataContext(connStr))
{
result = from t1 in context.elections
join t2 in context.election_status
on t1.statusID equals t2.statusID
select new Tuple(t1, t2);
}
}
UPDATE
Following from NagaMensch's comments, a better way to achieve the desired result would be to use the built in LINQ to SQL associations.
If you go to your entity diagram and click on toolbox, you will see 3 options. Class, Association and Inheritance. We want to use Association.
Click on Association and click on the ElectionStatus entity, hold the mouse button down and it will allow you to draw a line to the Election entity.
Once you've drawn the line it will ask you which properties are involved in the association. You want to select the StatusId column from the Election entity, and the StatusId column from the ElectionStatus entity.
Now that you've completed your mapping you will be able to simplify your query greatly because the join will not be necessary. You can just access the election status via a brand new property that LINQ to SQL will have added to the Election entity.
Your code can now look like this:
//context has to be moved outside the function
static ExampleDataContext context = new ExampleDataContext();
//Here we can return an IEnumerable of Election now, instead of using the Tuple class
public static IEnumerable<Election> getElections()
{
return from election in context.Elections
select election;
}
static void Main(string[] args)
{
//get the elections
var elections = getElections();
//lets go through the elections
foreach (var election in elections)
{
//here we can access election status via the ElectionStatus property
Console.WriteLine("Election name: {0}; Election status: {1}", election.ElectionName, election.ElectionStatus.StatusDescription);
}
}
You can also find a "how to" on LINQ to SQL associations here.
Note: It's worth mentioning that if you have an FK relationship set up between your tables in the database; LINQ to SQL will automatically pick the relationship up and map the association for you (therefore creating the properties).
You'll need to create classes that have the same structure as the anonymous types. Then, instead of "new { t1, t2 }", you use "new MyClass(t1, t2)".
Once you have a named class, you can pass it all over the place as you were hoping.
The problem is, that you are creating a anonymous type, hence there is no way to declare a method with this return type. You have to create a new type that will hold your query result and return this type.
But I suggest not to return the result in a new type but return just a colection of election objects and access the election_status objects through the relation properties assuming you included them in your model. The data load options cause the query to include the related election status objects in the query result.
public IList<election> GetElections()
{
using (ormDataContext context = new ormDataContext(connStr))
{
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<election>(e => e.election_status);
context.DeferredLoadingEnabled = false;
context.LoadOptions = dlo;
return context.elections.ToList();
}
}
Now you can do the following.
IList<election> elections = GetElections();
// Access the election status.
Console.WriteLin(elections[0].election_status);
I general LINQ to SQL could just retrieve the related entities on demand - that is called deferred loading.
ormDataContext context = new ormDataContext(connStr));
IList<election> elections = context.elections.ToList();
// This will trigger a query that loads the election
// status of the first election object.
Console.WriteLine(elections[0].election_status);
But this requires you not to close the data context until you finished using the retrieved objects, hence cannot be used with a using statement encapsulated in a method.
IEnumerable<object> getRecordsList()
{
using (var dbObj = new OrderEntryDbEntities())
{
return (from orderRec in dbObj.structSTIOrderUpdateTbls
select orderRec).ToList<object>();
}
}
maybe it can help :)
using (ormDataContext context = new ormDataContext(connStr))
{
var electionInfo = from t1 in context.elections
join t2 in context.election_status
on t1.statusID equals t2.statusID
select new {
t1.column1,
t1.column2,
t1.column3,
t1.column4,
t2.column5,
t2.column6,
t2.column7
};
}
foreach (var ei in electionInfo){
//write what do u want
}
return electionInfo.ToList();
You cannot return an anonymous type from a method. It is only available within the scope in which it is created. You'll have to create a class instead.

Categories

Resources