Trying to add Support For Index Usage on EFCore Spanner Provider - entity-framework-core

I'm writing a driver for EF Core for Spanner - In basic level it works and I can write Read and Write Queries that get's translated to Spanner SQL , executed and return results etc..
Now I'm trying to add Support For Read Query with Secondary Index.
Ultimately I'm trying to generate this SQL Query:
SELECT * FROM PostTags#{ FORCE_INDEX = PostTagsByTagId } WHERE TagId = 1
From This Linq:
var postTag = ctx.PostTags.WithIndex("PostTagsByTagId").Where(x => x.TagId == 1).FirstOrDefault();
I've added extension method as follow:
public static class SpannerIndexSupport
{
public static IQueryable<TSource> WithIndex<TSource>(this IQueryable<TSource> query, string indexName)
{
var methodDefinition = typeof(SpannerIndexSupport).GetTypeInfo().GetMethods().Single(m => m.Name == "WithIndex");
var method = methodDefinition.MakeGenericMethod(typeof(TSource));
var args = new[] { query.Expression, Expression.Constant(indexName) };
var expression = Expression.Call(null, method, args);
return query.Provider.CreateQuery<TSource>(expression);
}
}
And tried to write IAsyncQueryProvider to support it but couldn't find a way to make it work.
Any ideas Anyone?

In the official Spanner EFCore library (https://github.com/GoogleCloudPlatform/google-cloud-dotnet/tree/master/apis/Google.Cloud.EntityFrameworkCore.Spanner/Google.Cloud.EntityFrameworkCore.Spanner), I would start by overriding VisitTable(TableExpression tableExpression) in SpannerQuerySqlGenerator:
https://github.com/GoogleCloudPlatform/google-cloud-dotnet/tree/master/apis/Google.Cloud.EntityFrameworkCore.Spanner/Google.Cloud.EntityFrameworkCore.Spanner/Query/Sql/Internal/SpannerQuerySqlGenerator.cs
This will allow you to get a proof of concept going because you can directly influence the generated SQL text there.
Once that works, then you will want to make it proper.
I suppose there might be a few ways to make this work. The simplest might be to have some custom no-op method marker in the Linq expression tree and then register an IMethodCallTranslator to convert it either to a custom spanner specific Expression (whose Accept calls into SqlGenerator to generate the proper Sql) or possibly creating a SqlTranslatingExpressionVisitor to switch out the table expression to a custom one that allows the FORCE_INDEX.
Sorry I couldn't help more.

This is now supported in the official Entity Framework provider for Google Cloud Spanner. You can add this by adding a tag to the query like this:
var singersOrderedByFullName = context.Singers
// This will add the following comment to the generated query:
// `-- Use hint: force_index FullName`
// This comment will be picked up by the interceptor and an index
// hint will be added to the query that is executed.
.TagWith("Use hint: force_index FullName")
.OrderBy(s => s.FullName)
.AsAsyncEnumerable();
A full example can be found here: https://github.com/googleapis/dotnet-spanner-entity-framework/blob/main/Google.Cloud.EntityFrameworkCore.Spanner.Samples/Snippets/QueryHintSample.cs

Related

Entity Framework Z Plus Batch Update

I am using Entity Framework Z Plus Batch Update method. I am unable to proceed due to below issue. Actually Update method works good when i give static values like tagName="amir1". But I need to get the Tagdescription from a web service or from another collection based on the tagId, Update method is not accepting a extension method or any other method to accomplish my requirement.Its saying
"LINQ to Entities does not recognize the method 'System.String GetTagDescription(Int32)' method, and this method cannot be translated into a store expression.".
Hope my requirement is clear now. Please guide me if there is any other approach for my requirement.
Here is my code:
using (var context = new TrialsDBEntities())
{
context.tblTags.Where(x => (tagIdCollection.Contains(x.tagId))).
Update(m => new tblTag { tagDescription = m.tagId.GetTagDescription(), tagName = "amir1" });
}
public static string GetTagDescription(this int i)
{
return "test" + i;
///Replace above line with call to database or web service call
getting some text by giving i as input
}
Disclaimer: I'm the owner of the project Entity Framework Plus
Unfortunately, that's not possible to use BatchUpdate with a value that changes from a row to another.
Disclaimer: I'm the owner of the project Entity Framework Extensions
In this situation, we normally recommend using our paid library that's build for this kind of situation and offer high-performance saving operation.
Example
// Easiest way
context.BulkSaveChanges();
// Fastest way
context.BulkUpdate(tags);
EDIT: Answer comment
If I can't use updated row so why the signature of action is misleading and give me possibility to write like this: e => new Entity { Name = e.Name + "Edited" }
For most providers such as SQL Server, your expression is supported. You give a global expression so we can apply. Your expression doesn't doesn't change from a row to another, it's the same expression.
What is not supported is giving a specific expression from a row to another.
Write your code as follows:
using (var context = new TrialsDBEntities())
{
var tagsToBeUpdated = context.tblTags.Where(x => (tagIdCollection.Contains(x.tagId))).AsNoTracking().ToList();
//Only use this code block if your tagsToBeUpdated list is too large
Parallel.ForEach(tagsToBeUpdated, tagToBeUpdated =>
{
var tagDescription = GetTagDescription(tagToBeUpdated.tagId);
tagToBeUpdated.tagDescription = tagDescription;
context.Entry(tagToBeUpdated).State = EntityState.Modified;
});
//Only use this code block if your tagsToBeUpdated list is not too large
foreach(var tagToBeUpdated in tagsToBeUpdated)
{
var tagDescription = GetTagDescription(tagToBeUpdated.tagId);
tagToBeUpdated.tagDescription = tagDescription;
context.Entry(tagToBeUpdated).State = EntityState.Modified;
}
context.SaveChanges();
}
public static string GetTagDescription(int i)
{
return "test" + i;
///Replace above line with call to database or web service call
//getting some text by giving i as input
}

Casting issue while returning value in method in EF4.0

I'm trying to load employees using Entity Framework.
The method is supposed to return employee list.
It' s giving this error:
Cannot implicit convert....<Class names and methods>.... An Explicit conversion exists.
I think the problem is related to casting.
Please check below code.
public List<Employee> LoadEmployees()
{
try
{
EMployeeDB1Entities EE = new EMployeeDB1Entities();
var Employees = EE.Employees.Where(p => p.Name.StartsWith("T"));
return Employees;
}
catch
{
return null;
}
}
var Employees = EE.Employees.Where(p => p.Name.StartsWith("T")).ToList();
Update your code to:
return Employees.ToList();
Also do note that this is the ToList() method that actually triggers the database query.
EE.Employees.Where(....) doesn't query the database. The DB is queried when the result of the Where() is enumerated, which is what .ToList() does.
Thanks it works...one more issue, suppose if I want to bind above list
to grid then how can I bind ?
Assuming you're using WPF or Silverlight:
To bind the result of your query on a datagrid, you could expose a public property of type ObservableCollection.
This collection accepts an IEnumerable<T> object as constructor.
You can write:
var myCollection = new ObservableCollection<Employee>(this.LoadEmployees());
Then bind the ItemSource property of your datagrid to your collection.
If you have more problems using bindings, I recommend you to ask another question, because the subject is quite different.

Entity Framework 4: Selecting Single Record

I'm currently planning on switching my "manual query-writing" code to a nice SQL framework, so I can leave the queries or sql things to the framework, instead of writing the queries myself.
Now I'm wondering how I can get a single record from my table in Entity Framework 4?
I've primarily used SQL like SELECT * FROM {0} WHERE Id = {1}. That doesn't work in EF4, as far as I'm concerned.
Is there a way I can select a single ID-Based record from my Context?
Something like:
public Address GetAddress(int addressId)
{
var result = from Context.Addresses where Address.Id = addressId;
Address adr = result as Address;
return Address;
}
Thank you!
var address = Context.Addresses.First(a => a.Id == addressId);
You can use Single or First methods.
The difference between those methods is that Single expects a single row and throws an exception if it doesn't have a single row.
The usage is the same for both of them
(Based on VS 2015) If you create an .edmx (Add --> ADO.NET Entity Data Model).
Go through the steps to created the ".edmx" and use the following to run the stored procedure. emailAddress is the parameter you are passing to the stored procedure g_getLoginStatus. This will pull the first row into LoginStatus and status is a column in the database:
bool verasity = false;
DBNameEntities db = new DBNameEntities(); // Use name of your DBEntities
var LoginStatus = db.g_getLoginStatus(emailAddress).FirstOrDefault();
if ((LoginStatus != null) && (LoginStatus.status == 1))
{
verasity = true;
}

Execute StoredProcedure in CodeFirst 4.1

I understand stored procedures mapping is not supported by my understanding is that I should be able to call stored procedures.
I have quite a few complex stored procedures and with the designer I could create a complex type and I was all good.
Now in code first let's suppose I have the following stored procedure, just put together something silly to give an idea. I want to return a student with 1 address.
In code I have A Student and Address Entity. But no StudentAddressEntity as it's a link table.
I have tried the following but I get an error
Incorrect syntax near '."}
System.Data.Common.DbException {System.Data.SqlClient.SqlException}
ALTER Procedure [dbo].[GetStudentById]
#StudentID int
AS
SELECT *
FROM Student S
left join StudentAddress SA on S.Studentid = sa.studentid
left join Address A on SA.AddressID = A.AddressID
where S.StudentID = #StudentID
C# code:
using (var ctx = new SchoolContext())
{
var student = ctx.Database.SqlQuery<Student>("GetStudentById,#StudentID",
new SqlParameter("StudentID", id));
}
Any examples out there how to call sp and fill a complexType in code first, using out parameters etc.. Can I hook into ADO.NET?
Trying just an SP that returns all students with no parameters I get this error
System.SystemException = Cannot create a value for property
'StudentAddress' of type
'CodeFirstPrototype.Dal.Address'. Only
properties with primitive types are
supported.
Is it because I have in a way ignore the link table?
Any suggestions?
I believe that your exception actually is:
Incorrect syntax near ','.
because this is invalid statement: "GetStudentById,#StudentID". It should be without comma: "GetStudentById #StudentID".
The problem with stored procedures in EF is that they don't support loading navigation properties. EF will materialize only the main entity and navigation properties will not be loaded. This is solved for example by EFExtensions. EFExtensions are for ObjectContext API so you will have to check if it is also usable for DbContext API.
Using EFExtentions it will look something like
using (var context = new SchoolContext())
{
var command = context.CreateStoreCommand("GetStudentById", CommandType.StoredProcedure,
new SqlParameter("StudentID", id));
using (command.Connection.CreateConnectionScope())
using (var reader = command.ExecuteReader())
{
// use the reader to read the data
// my recommendation is to create a Materializer using EFExtensions see
// http://blogs.msdn.com/b/meek/archive/2008/03/26/ado-entity-framework-stored-procedure-customization.aspx
// ex
var student = Student.Materializer.Materialize(reader).SingleOrDefault();
return student;
}
}

Add index with entity framework code first (CTP5)

Is there a way to get EF CTP5 to create an index when it creates a schema?
Update: See here for how EF 6.1 handles this (as pointed out by juFo below).
You can take advantage of the new CTP5’s ExecuteSqlCommand method on Database class which allows raw SQL commands to be executed against the database.
The best place to invoke SqlCommand method for this purpose is inside a Seed method that has been overridden in a custom Initializer class. For example:
protected override void Seed(EntityMappingContext context)
{
context.Database.ExecuteSqlCommand("CREATE INDEX IX_NAME ON ...");
}
As some mentioned in the comments to Mortezas answer there is a CreateIndex/DropIndex method if you use migrations.
But if you are in "debug"/development mode and is changing the schema all the time and are recreating the database every time you can use the example mentioned in Morteza answer.
To make it a little easier, I have written a very simple extension method to make it strongly typed, as inspiration that I want to share with anyone who reads this question and maybe would like this approach aswell. Just change it to fit your needs and way of naming indexes.
You use it like this: context.Database.CreateUniqueIndex<User>(x => x.Name);
.
public static void CreateUniqueIndex<TModel>(this Database database, Expression<Func<TModel, object>> expression)
{
if (database == null)
throw new ArgumentNullException("database");
// Assumes singular table name matching the name of the Model type
var tableName = typeof(TModel).Name;
var columnName = GetLambdaExpressionName(expression.Body);
var indexName = string.Format("IX_{0}_{1}", tableName, columnName);
var createIndexSql = string.Format("CREATE UNIQUE INDEX {0} ON {1} ({2})", indexName, tableName, columnName);
database.ExecuteSqlCommand(createIndexSql);
}
public static string GetLambdaExpressionName(Expression expression)
{
MemberExpression memberExp = expression as MemberExpression;
if (memberExp == null)
{
// Check if it is an UnaryExpression and unwrap it
var unaryExp = expression as UnaryExpression;
if (unaryExp != null)
memberExp = unaryExp.Operand as MemberExpression;
}
if (memberExp == null)
throw new ArgumentException("Cannot get name from expression", "expression");
return memberExp.Member.Name;
}
Update: From version 6.1 and onwards there is an [Index] attribute available.
For more info, see http://msdn.microsoft.com/en-US/data/jj591583#Index
This feature should be available in the near-future via data annotations and the Fluent API. Microsoft have added it into their public backlog:
http://entityframework.codeplex.com/workitem/list/basic?keywords=DevDiv [Id=87553]
Until then, you'll need to use a seed method on a custom Initializer class to execute the SQL to create the unique index, and if you're using code-first migrations, create a new migration for adding the unique index, and use the CreateIndex and DropIndex methods in your Up and Down methods for the migration to create and drop the index.
Check my answer here Entity Framework Code First Fluent Api: Adding Indexes to columns this allows you to define multi column indexes by using attributes on properties.