Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
<Compile Include="NpgsqlServices.cs" />
<Compile Include="NpgsqlProviderManifest.cs" />
<Compile Include="NpgsqlTextFunctions.cs" />
<Compile Include="NpgsqlTypeFunctions.cs" />
<Compile Include="NpgsqlWeightLabel.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Spatial\PostgisDataReader.cs" />
Expand Down
8 changes: 4 additions & 4 deletions src/EntityFramework6.Npgsql/NpgsqlProviderManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,15 +351,15 @@ public override string EscapeLikeArgument([NotNull] string argument)
public override bool SupportsInExpression() => true;

public override ReadOnlyCollection<EdmFunction> GetStoreFunctions()
=> typeof(NpgsqlTextFunctions).GetTypeInfo()
.GetMethods(BindingFlags.Public | BindingFlags.Static)
=> new[] { typeof(NpgsqlTextFunctions).GetTypeInfo(), typeof(NpgsqlTypeFunctions) }
.SelectMany(x => x.GetMethods(BindingFlags.Public | BindingFlags.Static))
.Select(x => new { Method = x, DbFunction = x.GetCustomAttribute<DbFunctionAttribute>() })
.Where(x => x.DbFunction != null)
.Select(x => CreateFullTextEdmFunction(x.Method, x.DbFunction))
.Select(x => CreateComposableEdmFunction(x.Method, x.DbFunction))
.ToList()
.AsReadOnly();

static EdmFunction CreateFullTextEdmFunction([NotNull] MethodInfo method, [NotNull] DbFunctionAttribute dbFunctionInfo)
static EdmFunction CreateComposableEdmFunction([NotNull] MethodInfo method, [NotNull] DbFunctionAttribute dbFunctionInfo)
{
if (method == null)
throw new ArgumentNullException(nameof(method));
Expand Down
20 changes: 20 additions & 0 deletions src/EntityFramework6.Npgsql/NpgsqlTypeFunctions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Data.Entity;

namespace Npgsql
{
/// <summary>
/// Use this class in LINQ queries to emit type manipulation SQL fragments.
/// </summary>
public static class NpgsqlTypeFunctions
{
/// <summary>
/// Emits an explicit cast for unknown types sent as strings to their correct postgresql type.
/// </summary>
[DbFunction("Npgsql", "cast")]
public static string Cast(string unknownTypeValue, string postgresTypeName)
{
throw new NotSupportedException();
}
}
}
20 changes: 19 additions & 1 deletion src/EntityFramework6.Npgsql/SqlGenerators/SqlBaseGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,14 @@ PendingProjectsNode VisitInputWithBinding(DbExpression expression, string bindin
for (var i = 0; i < rowType.Properties.Count && i < projection.Arguments.Count; ++i)
{
var prop = rowType.Properties[i];
input.Projection.Arguments.Add(new ColumnExpression(projection.Arguments[i].Accept(this), prop.Name, prop.TypeUsage));
var argument = projection.Arguments[i].Accept(this);
var constantArgument = projection.Arguments[i] as DbConstantExpression;
if (constantArgument != null && constantArgument.Value is string)
{
argument = new CastExpression(argument, "varchar");
}

input.Projection.Arguments.Add(new ColumnExpression(argument, prop.Name, prop.TypeUsage));
}

if (enterScope) LeaveExpression(child);
Expand Down Expand Up @@ -1150,6 +1157,17 @@ VisitedExpression VisitFunction(EdmFunction function, IList<DbExpression> args,
{
return VisitMatchRegex(function, args, resultType);
}
else if (functionName == "cast")
{
if (args.Count != 2)
throw new ArgumentException("Invalid number of arguments. Expected 2.", "args");

var typeNameExpression = args[1] as DbConstantExpression;
if (typeNameExpression == null)
throw new NotSupportedException("cast type name argument must be a constant expression.");

return new CastExpression(args[0].Accept(this), typeNameExpression.Value.ToString());
}
}

var customFuncCall = new FunctionExpression(
Expand Down
69 changes: 69 additions & 0 deletions test/EntityFramework6.Npgsql.Tests/EntityFrameworkBasicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -650,5 +650,74 @@ public void TestScalarValuedStoredFunctions_with_null_StoreFunctionName()
Assert.That(echo, Is.EqualTo(1337));
}
}

[Test]
public void TestCastFunction()
{
using (var context = new BloggingContext(ConnectionString))
{
context.Database.Log = Console.Out.WriteLine;

var varbitVal = "10011";

var blog = new Blog
{
Name = "_",
Posts = new List<Post>
{
new Post
{
Content = "Some post content",
Rating = 1,
Title = "Some post Title",
VarbitColumn = varbitVal
}
}
};
context.Blogs.Add(blog);
context.SaveChanges();

Assert.IsTrue(
context.Posts.Select(
p => NpgsqlTypeFunctions.Cast(p.VarbitColumn, "varbit") == varbitVal).First());

Assert.IsTrue(
context.Posts.Select(
p => NpgsqlTypeFunctions.Cast(p.VarbitColumn, "varbit") == "10011").First());
}
}

[Test]
public void Test_issue_27_select_ef_generated_literals_from_inner_select()
{
using (var context = new BloggingContext(ConnectionString))
{
context.Database.Log = Console.Out.WriteLine;

var blog = new Blog { Name = "Hello" };
context.Users.Add(new Administrator { Blogs = new List<Blog> { blog } });
context.Users.Add(new Editor());
context.SaveChanges();

var administrator = context.Users
.Where(x => x is Administrator) // Removing this changes the query to using a UNION which doesn't fail.
.Select(
x => new
{
// causes entity framework to emit a literal discriminator
Computed = x is Administrator
? "I administrate"
: x is Editor
? "I edit"
: "Unknown",
// causes an inner select to be emitted thus showing the issue
HasBlog = x.Blogs.Any()
})
.First();

Assert.That(administrator.Computed, Is.EqualTo("I administrate"));
Assert.That(administrator.HasBlog, Is.True);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ public class NoColumnsEntity
public int Id { get; set; }
}

[Table("Users")]
public abstract class User
{
public int Id { get; set; }

public IList<Blog> Blogs { get; set; }
}

[Table("Editors")]
public class Editor : User { }

[Table("Administrators")]
public class Administrator : User { }

public class BloggingContext : DbContext
{
public BloggingContext(string connection)
Expand All @@ -114,6 +128,9 @@ public BloggingContext(string connection)
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<NoColumnsEntity> NoColumnsEntities { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Editor> Editors { get; set; }
public DbSet<Administrator> Administrators { get; set; }

[DbFunction("BloggingContext", "ClrStoredAddFunction")]
public static int StoredAddFunction(int val1, int val2)
Expand All @@ -135,6 +152,9 @@ private static DbCompiledModel CreateModel(NpgsqlConnection connection)
dbModelBuilder.Entity<Blog>();
dbModelBuilder.Entity<Post>();
dbModelBuilder.Entity<NoColumnsEntity>();
dbModelBuilder.Entity<User>();
dbModelBuilder.Entity<Editor>();
dbModelBuilder.Entity<Administrator>();

// Import function
var dbModel = dbModelBuilder.Build(connection);
Expand Down