Using primary keys in linqtoSQL - c#

I am trying to write some generic linqtoSQL which performs operation on entities based on their primary keys. Some entities I am working with have composite primary keys.
Fundamentally I want to be able to do things such as:
if( PrimaryKey(foo) == PrimaryKey(bar) ) ...
or,
from oldEntity in oldTableOrCollection
join newEntity in newTableOrCollection
on PrimaryKeyOf(oldEntity) equals PrimaryKeyOf(newEntity)
select new {oldEntity, newEntity}
with only the requirement that the entities have primary keys.
Here's what I've found so far:
It's possible to do things such as
var query = from row in oneTableOfRows
join otherRow in anotherTableOfRows on
new { row.Column1, row.Column2 }
equals
new { otherRow.Column1, otherRow.Column2}
select ...;
in which linqtosql will use the anonymous types for comparison (provided the property names match).
I can then abstract the method for selecting the columns on which to join to allow generic row types:
void DoQuery<RowType,KeyType>(Table<RowType> oneTableOfRows,
Table<RowType> anotherTableOfRows,
Func<RowType, KeyType> keySelector)
{
var query = from row in oneTableOfRow
join otherRow in anotherTableOfRows on
keySelector(row)
equals
keySelector(otherRow)
select ...;
}
...
Func<rowType, keyType> myKeySelector = row => new { row.Column1, row.Column2 };
DoQuery(table, otherTable, myKeySelector);
What I'm trying to move to now, is where the keySelector will select the primary key of any RowType. I am using a custom template based on http://www.codeplex.com/l2st4 to generate my entities (which itself is fairly similar to the default in VS).
I'm hoping to ideally give each generated RowType the ability to select its primary key as gathered from the dbml. So far the ouptut looks like the following:
public interface IPrimaryKeyEntity<PrimaryKeyType>
{
PrimaryKeyType PrimaryKey { get; }
}
//Here, Product is a table with composite primary key based on its ProductID and it's ProductPriceVersionID columns
//I've tried using both class and struct as the primary key type for entities
public class ProductPrimaryKey
{
public Guid ProductID;
public Guid ProductPriceVersionID;
}
public partial class Product : IPrimaryKeyEntity<ProductPrimaryKey>,
INotifyPropertyChanging, INotifyPropertyChanged
{
#region IPrimaryKeyEntity Implementation
public ProductPrimaryKey PrimaryKey
{
get
{ return new ProductPrimaryKey()
{
ProductID = this.ProductID,
ProductPriceVersionID = this.ProductPriceVersionID
};
}
}
#endregion
...
//Rest is mostly standard LinqToSql entity generation
}
Going back to the original aim, I can now compile the following for all my primary key entities:
from oldEntity in oldTableOrCollection
join newEntity in newTableOrCollection
on oldEntity.PrimaryKey equals newEntity.PrimaryKey
select new {oldEntity, newEntity}
However, runtime, I get the infamous [PrimaryKey] "has no supported translation to SQL" exception.
I understand that to translate to SQL it is necessary to use Expression's however I am pretty unfamiliar with Linq.Expressions and have had no progress yet trying to apply it to my situation...
If anyone can improve on what I've got so far or has a better method in general I'd be grateful to know....
Cheers

The problem is that you are calling a function here:
join ... on oldEntity.PrimaryKey equals newEntity.PrimaryKey
A property-get is a function call. Linq to SQL does not know this function so it cannot translate it.
The only way to make this work is to build an expression that corresponds to this SQL:
join otherRow in anotherTableOfRows on
new { row.Column1, row.Column2 }
equals
new { otherRow.Column1, otherRow.Column2}
There is no other way, I can assure you. You can do it like this: First, you declare a custom tuple class:
class CustomTuple2<T1,T2>
{
public T1 Item1 { get; set; }
public T2 Item2 { get; set; }
}
You do this for all possible member counts (for example 8 or 16).
Let's look at how a join gets translated:
IQueryable<T1> t1 = ...; //table 1
IQueryable<T2> t2 = ...; //table 2
var joined = t1.Join(t2, _t1 => _t1.Key, _t2 => _t2.Key);
The lambda parameters are expressions. You need to build these two expressions using expression trees. This is the magic!
For example, the first lambda:
var param = Expression.Parameter(typeof(T1), "_t1");
var key = Expression.Property(param, "Key");
var lambda1 = Expression.Lambda(param, key);
Do the same for the second lambda. (Remember, that this was pseudo-code. I don't exactly recall how everything was called.)
Finally, invoke the Join:
var joined = typeof(Queryable).GetMethod("Join").Invoke(t1, t2, lambda1, lambda2);
So that is how you build a join at runtime! Please note, that all of this was pseudo-code. It will take you some research to figure the APIs out and make it work, but you can surely do it.
Also, if you have a composite key, you will need the mentioned tuple class to project the key members into.

Related

It is possible to query a NotMapped property?

i'm working with EF6 code first, and i used this answer to map a List<stirng> in my entitie.
This is my class
[Key]
public string SubRubro { get; set; }
[Column]
private string SubrubrosAbarcados
{
get
{
return ListaEspecifica == null || !ListaEspecifica.Any() ? null : JsonConvert.SerializeObject(ListaEspecifica);
}
set
{
if (string.IsNullOrWhiteSpace(value))
ListaEspecifica.Clear();
else
ListaEspecifica = JsonConvert.DeserializeObject<List<string>>(value);
}
}
[NotMapped]
public List<string> ListaEspecifica { get; set; } = new List<string>();
It works perfectly to storage my list as Json, but now i need to perform a linq query, and i'm trying this
var c = db.CategoriaAccesorios.Where(c => c.ListaEspecifica.Contains("Buc")).First();
And it's throwing
System.NotSupportedException: The specified type member
'ListaEspecifica' is not supported in LINQ to Entities. Only
initializers, entity members, and entity navigation properties are
supported.
what is logical.
Is there any way to perform a query like this?
The problem here is that LINQ to Entities does not understand how to convert your query to the back-end (SQL) language. Because you're not materializing (i.e. converting to .NET) the results of the query until you filter it, LINQ tries to convert your query to SQL itself. Since it's not sure how to do that, you get a NotSupportedException.
If you materialize the query first (I.e. call a .ToList()) then filter, things will work fine. I suspect this isn't what you want, though. (I.e. db.CategoriaAccesorios.ToList().Where(c => c.ListaEspecifica.Contains("Buc")).First();)
As this answer explains, your issue is the EF to SQL Conversion. Obviously you want some way to workaround it, though.
Because you are JSON serializing, there are actually a couple options here, most particularly using a LIKE:
var c =
(from category
in db.CategoriaAccessorios
where SqlMethods.Like(c.SubrubrosAbarcados, "%\"Buc\"%")
select category).First()
If EF Core, allegedly Microsoft.EntityFrameworkCore.EF.Functions.Like should replace SqlMethods.Like.
If you have SQL Server 2016+, and force the SubrubrosAbarcados to be a JSON type, it should be possible to use a raw query to directly query the JSON column in particular.
If you're curious about said aspect, here's a sample of what it could look like in SQL Server 2016:
CREATE TABLE Test (JsonData NVARCHAR(MAX))
INSERT INTO Test (JsonData) VALUES ('["Test"]'), ('["Something"]')
SELECT * FROM Test CROSS APPLY OPENJSON(JsonData, '$') WITH (Value VARCHAR(100) '$') AS n WHERE n.Value = 'Test'
DROP TABLE Test
I was able to do something like this via CompiledExpression.
using Microsoft.Linq.Translations;
// (...) namespace, class, etc
private static readonly CompiledExpression<MyClass, List<string>> _myExpression = DefaultTranslationOf<MyClass>
.Property(x => x.MyProperty)
.Is(x => new List<string>());
[NotMapped]
public List<string> MyProperty
{
get { return _myExpression.Evaluate(this); }
}
I hope there are better / prettier solutions though ;)

How to do GroupBy on complex object with an IQueryable

I'm looking for a way to do a GroupBy on a complex object, instead of just one property. The trouble is that I want to do this on an IQueryable, because getting all the data from the table is a really bad idea in this case.
We're using Entity Framework 6.1.
The class looks like this:
public class Pin {
public Guid Id {get;set;}
public Guid PageId {get;set;} /* this is the foreign key to our Pages-table */
public PageClass Page {get;set;} /* this is a relation */
}
I need to report on the times a certain page has been "pinned", printing the name of the page as well.
Right now my code looks like this:
var pinnedPages = GetAll().GroupBy(x => x, comparer);
foreach (var pinnedPage in pinnedPages)
{
var numberOfTimesPinned = pinnedPage.Count();
var pin = pinnedPage.Key;
//write a line to the report
}
But if I group on PageId, the pinnedPage.Key returns a Guid, obviously, while I need the whole Page object for my reporting needs.
I have tried implementing a custom comparer as well, but this cannot be translated to SQL, obviously which is why this doesn't work either.
GetAll().GroupBy(x => x.pageId).Select(_ => new {key = _.Key, page = _.FirstOrDefault().Page, count = _.Count()});
This will group by on the pageId, however the select will create a new anonymous object which will contain the key (pageId) and select the first PageClass object
You don't need any grouping if you query the pages directly and use a navigation property that I assume exist (or else should be added):
var pinnedPages = context.Pages
.Select(p => new
{
Page = p
Pins = p.Pins.Count()
});
foreach (var pinnedPage in pinnedPages)
{
var numberOfTimesPinned = pinnedPage.Pins;
var pin = pinnedPage.Page;
//write a line to the report
}
I use context.Pages because the source of the statement should be IQueryable. GetAll returns IEnumerable (apparently, otherwise the GroupBy overload with a comparer wouldn't work).

Table-Valued Function as IQueryable in EF?

I have a Table-Valued function which I would like to use as an IQueryable in a LINQ statement. I have created the following in my DbContext class:
[DbFunction("dbo","MyTableValuedFunction")]
public virtual IQueryable<MyClass> MyFunction(string keyword)
{
var keywordsParam = new System.Data.Entity.Core.Objects.ObjectParameter("keywords", typeof(string))
{
Value = keyword
};
return (this as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext
.CreateQuery<MyClass>("dbo.MyTableValuedFunction(#keywords)", keywordsParam);
}
The result of dbo.MyTableValuedFunction(#keywords) match an existing class "MyClass". An example of how I'd like to use this function:
MyClass example = (from a in dbContext.MyClass
join b in dbContext.MyFunction("exampleKeyword")
on a.Id equals b.Id
join c in dbContext.MyOtherClass
on a.SomeId equals c.Id
select a);
...but enumerating this IEnumerable throws an exception:
'dbo.MyTableValuedFunction' cannot be resolved into a valid type or function.
I have tried reducing the function to just one column, and changing the type to <int>, but this doesn't work, so I am not sure what is happening here; it's like the TVF is not being correctly found/recognised/used?
I have also tried following https://weblogs.asp.net/Dixin/EntityFramework.Functions#Table-valued_function and on the off-chance there's some subtle difference, I create the function with:
[Function(FunctionType.TableValuedFunction, "MyTableValuedFunction", Schema = "dbo")]
...but I run into exactly the same problem.
First if you used this extension, you should write following code according to your code:
[DbFunction("dbo","MyTableValuedFunction")]
public virtual IQueryable<MyClass> MyFunction(string keyword)
{
var keywordsParam = new System.Data.Entity.Core.Objects.ObjectParameter("keywords", typeof(string))
{
Value = keyword
};
return (this as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext
.CreateQuery<MyClass>("[Your DbContext Name].MyFunction(#keywords)", keywordsParam);
}
According to the extension codes, the EF uses conventions to translate the C# syntax into sql syntax. for this reason, you should register your functions in dbContext first as the link mentioned.

How to get values from a one to many table column without foreign key included in EF model

I have two tables(T1 one to many T2):
T1 table
ID(Primary key)
Name
Startdate
Enddate
T2 table
ID(Primary key)
T1ID(This the foreign key only in the db, since im using 3.5 .net framework I cant include foreign key using Entity Framework)
StoreID(nvarchar)
How can I get all T2 storeIDs that belongs to a row in the T1 table?
I have the following code to get a specific row from T1:
public t1 GetT1ByID(string name)
{
return db.t1.FirstOrDefault(m => m.Name.ToLower() == name.ToLower());
}
and here is the codebehind:
Repository rep = new Repository();
string namn = SPContext.Current.Web.Title;
var allItems = rep.GetT1ByID(namn);
I tried following:
foreach (var items in allItems.t2)
{
storeID.Text += items.storeID;
}
But I get nothing since there is nothing inside allItems.t2.
Any kind of help is appreciated.
First, when you have a question about how to retrieve something via LINQ or Entity Framework, we need your classes, not your table schema. The two aren't necessarily the same, and especially in this case, the navigation property will not be represented in the table schema.
That said, if you don't have an explicit property to hold the foreign key on your entity, then you'll have to compare the whole object instead. Again, it's difficult to give you actual code you can use because your entities aren't included in your question, but roughly you'd need something like the following:
var t2 = db.t2s.Find(t2ID);
var t1s = db.t1s.Where(m => m.t2NavProperty == t2);

Fluent NHibernate References with constants

I have a table mapped in Fluent NHibernate. This table must join to another table on an ID, but must also filter the joined values on that table against a set of constant values. Consider the following SQL:
SELECT *
FROM
Table1
INNER JOIN
Table2 ON
Table1.Table2Id = Table2.Id
AND Table2.Category = 'A constant expression'
AND Table2.Language = 'A constant expression'
My fluent mapping for Table1 currently looks like this:
References(a => a.Table2).Nullable().Columns("Table2Id").ReadOnly();
How can I implement the constant expressions?
It sounds like you could use filters to do this.
First, you need to define the filter types
public class SpecificCategoryFilter : FilterDefinition
{
public SpecificCategoryFilter()
{
WithName("SpecificCategory").WithCondition("Category = 'A constant expression'");
}
}
public class SpecificLanguageFilter : FilterDefinition
{
public SpecificLanguageFilter()
{
WithName("SpecificLanguage").WithCondition("Language = 'A constant expression'");
}
}
EDITED: As per comments, there is no .ApplyFilter<TFilter>() on References(), so have updated with what I believe is the way to do it with filters
The filters need to be applied in the fluent mappings
public class Table2Map : ClassMap<Table2>
{
public Table2Map()
{
// Other mappings here...
ApplyFilter<SpecificCategoryFilter>();
ApplyFilter<SpecificLanguageFilter>();
}
}
Finally, when you open a session, you need to enable the filters
using (var session = sessionFactory.OpenSession())
{
session.EnableFilter("SpecificCategory");
session.EnableFilter("SpecificLanguage");
}
If you're using an implementation of ICurrentSessionContext and the filters should always apply then you can enable the filters in the session returned from the call to ICurrentSessionContext.CurrentSession().
Now, when querying Table1, in order to activate the filters for Table2, you need to indicate to NHibernate to join to the referenced Table2; you can do this using
Fetch(t => t.Table2).Eager
JoinQueryOver(t => t.Table2) (and similar join strategies)
Without indicating to NHibernate to make the join, the reference will be lazily-loaded by default and hence the filters will not be applied in the query. The downside is that Table2 will be eager fetched but I don't know of a way to have the filters applied otherwise. The following query
session.QueryOver<Table1>().Inner.JoinQueryOver(t => t.Table2).List();
results in SQL similar to
SELECT
this_.Id as Id0_1_,
this_.Table2Id as Table3_0_1_,
table2_.Id as Id1_0_,
table2_.Category as Category1_0_,
table2_.Language as Language1_0_
FROM
Table1 this_
inner join
Table2 table2_
on this_.Table2Id=table2_.Id
WHERE
table2_.Category = 'A constant expression'
and table2_.Language = 'A constant expression'
which is akin to the SQL that you have in your question.
I have noticed that your mapping specifies Nullable and no eager fetching (by default it will be lazy loaded). So if you did want to generate the sql you have shown in your comment, you would not be able to do it with a simple session.Get<Table1>(). Even if you changed the mapping so that it was like this :
References(a => a.Table2).Nullable().Columns("Table2Id").ReadOnly().Fetch.Join;
You would most likely end up with a left outer join in the outputted sql. The only way you would be able to force a fetch with inner join (without downloading any extra NHibernate addons) would be to use a session.QueryOver (you may also be able to do this with session.Query and the NHibernate linq extensions). If this is the case, then you may as well specify your set of constants inside the QueryOver query.
public class GetTableQuery
{
private readonly string _category;
private readonly string _language;
public GetTableQuery(string category, string language)
{
_category = category;
_language = language;
}
public IEnumerable<Table1> Execute(ISession session)
{
var returnList = session.QueryOver<Table1>()
.Inner.JoinQueryOver(t1 => t1.Table2)
.Where(t2 => t2.Category == _category && t2.Language == _language)
.List();
return returnList;
}
}
I think the ApplyFilter method shown by Russ does make the model retrieval much simpler, and its really good for code re-usability (if you have other tables with categories and languages), but since your table reference is a nullable reference, you have to use a query anyway. Maybe a combination of QueryOver with the filter would be the best (assuming you can get the later version of fluent NHibernate)
You might want have a look into Formula(string formula) where you could provide plain SQL. If it is a good idea to filter data on the mapping level is another question IMHO... As an example have a look here.

Categories