I am a novice when it comes to entity framework.I usually use ado.net because is faster than any ORM and the repetitive code can be easily generated.
Now I have decided to give EF6 a go again and gain some experience in EF
Scenario.
Need to migrate data for many clients.(30 databases)
Each client will have their own staging database
Each database will have different tables
All databases will have/share the same "Views" schema.
Basically we decided that regardless of the clients tables ,they must all share the same Views.
So when we read the data we dont care because the views columnNames will be the same for them all.
Ado.net Implementation
Is very simple.I my dal i have methods like "GetCustomers","GetAccounts" etc... and all I need to do is change the connectionString
and I can read the views from any database.Does not get simpler than this.
EF implementation
Please correct me if I am wrong.
In order for EF to work I would have to generate code for 30 databases (databaseFirst).
Is there a way I can use just a connection string to connect to the right database and based on that connection string read data from the views?
How would you do it using EF6 by just changing the connection string?
Can you give a noddy example how to do it?
Any suggestions
here it is, just a bit more of 10 minutes... (EF 5 on .net 4.5)
using System;
using System.Linq;
using System.Data.Entity;
using System.Collections.Generic;
using System.Data.Entity.ModelConfiguration;
using System.Data.Objects.SqlClient;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Data.SqlClient;
namespace testef {
public class EntityZ {
public Int32 Id { get; set; }
public String P1 { get; set; }
}
public class EntityZConfiguration : EntityTypeConfiguration<EntityZ> {
public EntityZConfiguration()
: base() {
ToTable("v", "dbo"); // the view
HasKey(x => x.Id); //required
//the mapping of properties
Property(x => x.Id).HasColumnName("id");
Property(x => x.P1).HasColumnName("value");
}
}
public class TestEFContext : DbContext {
public DbSet<EntityZ> Entities { get; set; }
public TestEFContext(String cs)
: base(cs) {
//Database.SetInitializer<TestEFContext>(new DropCreateDatabaseAlways<TestEFContext>());
Database.SetInitializer<TestEFContext>(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new EntityZConfiguration());
}
}
class Program {
static void Main(string[] args) {
//creating the db
using (SqlConnection conn = new SqlConnection("Data Source=ALIASTVALK;Initial Catalog=master;Integrated Security=True; MultipleActiveResultSets=True")) {
conn.Open();
using (SqlCommand com = conn.CreateCommand()) {
com.CommandText = "declare #v int = 0 select #v = 1 from sys.databases where name = 'TestEF' if #v = 1 drop database TestEF";
com.ExecuteNonQuery();
com.CommandText = "create database TestEF";
com.ExecuteNonQuery();
com.CommandText = "use TestEF";
com.ExecuteNonQuery();
com.CommandText = "create table t (i int not null, t nvarchar(max))";
com.ExecuteNonQuery();
com.CommandText = "insert into t (i, t) values (1, 'hello world')";
com.ExecuteNonQuery();
com.CommandText = "create view v as select i as id, t as value from t";
com.ExecuteNonQuery();
}
}
String cs = #"Data Source=ALIASTVALK;Initial Catalog=TestEF;Integrated Security=True; MultipleActiveResultSets=True";
using (TestEFContext ctx = new TestEFContext(cs)) {
foreach (EntityZ z in ctx.Entities) {
Console.WriteLine("{0}: {1}", z.Id, z.P1);
}
}
}
}
}
Related
I'm trying to map a custom MySQL function to a method on my dbcontext as shown in this article. As suggested in the article I've stubbed out a method with the same signature of my user-defined function.
public int CalcOffset(DateTime input_date, int id)
=> throw new InvalidOperationException();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDbFunction(typeof(DbContext).GetMethod(nameof(CalcOffset), new[] { typeof(DateTime), typeof(int) }),
b =>
{
b.HasName("fn_test");
b.HasParameter("input_date");
b.HasParameter("id");
});
}
The MySql function I'm attempting to map to:
CREATE DEFINER=`myuser`#`localhost` FUNCTION `fn_test`(input_date datetime, project_id int) RETURNS int(11)
BEGIN
RETURN 1;
END
finally I call the function via the context
var offset = _context.CalcOffset(DateTime.Now, id);
When I run the code above I'm getting the error message thrown by the function I stubbed out. However per the article the method should be mapped the database function, not the actual method:
The body of the CLR method is not important. The method will not be
invoked client-side, unless EF Core can't translate its arguments. If
the arguments can be translated, EF Core only cares about the method
signature
Versions:
ASP.NET Core 3.1
Pomelo Entity Framework Core 3.2
MySql 5.7
UDFs work fine in my tests. Here is a fully working console program:
using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Pomelo.EntityFrameworkCore.MySql.Storage;
namespace IssueConsoleTemplate
{
public class IceCream
{
public int IceCreamId { get; set; }
public string Name { get; set; }
}
public class Context : DbContext
{
public virtual DbSet<IceCream> IceCreams { get; set; }
public int IncrementInteger(int value)
=> throw new InvalidOperationException();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connectionString = "server=127.0.0.1;port=3306;user=root;password=;database=So66857783";
optionsBuilder.UseMySql(
connectionString,
options => options.CharSetBehavior(CharSetBehavior.NeverAppend)
.ServerVersion(ServerVersion.AutoDetect(connectionString)))
.UseLoggerFactory(
LoggerFactory.Create(
configure => configure
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<IceCream>(
entity =>
{
modelBuilder.HasDbFunction(
typeof(Context).GetMethod(nameof(IncrementInteger)),
b => b.HasName("udf_increment")
.HasParameter("value"));
entity.HasData(
new IceCream {IceCreamId = 1, Name = "Vanilla"},
new IceCream {IceCreamId = 2, Name = "Chocolate"},
new IceCream {IceCreamId = 3, Name = "Matcha"}
);
});
}
}
internal static class Program
{
private static void Main(string[] args)
{
using var context = new Context();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
SetupDatabase(context);
var iceCreams = context.IceCreams
.OrderBy(i => i.IceCreamId)
.Select(
i => new
{
IceCreamId = i.IceCreamId,
IcrementedValue = context.IncrementInteger(i.IceCreamId)
})
.ToList();
Trace.Assert(iceCreams.Count == 3);
Trace.Assert(iceCreams[0].IceCreamId == 1);
Trace.Assert(iceCreams[0].IcrementedValue == 2);
}
private static void SetupDatabase(DbContext context)
{
context.Database.OpenConnection();
var connection = context.Database.GetDbConnection();
using var command = connection.CreateCommand();
command.CommandText = #"CREATE FUNCTION `udf_increment`(value int) RETURNS int
DETERMINISTIC
BEGIN
RETURN value + 1;
END";
command.ExecuteNonQuery();
}
}
}
It generates the following SQL query:
SELECT `i`.`IceCreamId`, `udf_increment`(`i`.`IceCreamId`) AS `IcrementedValue`
FROM `IceCreams` AS `i`
ORDER BY `i`.`IceCreamId`
However, just calling the function without an entity context, as you do in your OP, will not work because you would just execute your function body, instead of letting EF Core translating an expression tree to SQL:
// This does not work, because Context.IncrementInteger(int) is being directly called
// here, instead of being part of an expression tree, that is then translated to SQL
// by EF Core.
var answer = context.IncrementInteger(41);
Trace.Assert(answer == 42);
Of course you can always execute some good old SQL yourself, if you need to:
// Executing the UDF using god old SQL.
// Use parameters for input values in the real world.
context.Database.OpenConnection();
using var command = context.Database.GetDbConnection().CreateCommand();
command.CommandText = #"SELECT `udf_increment`(41)";
var answerToEverything = (int)command.ExecuteScalar();
Trace.Assert(answerToEverything == 42);
Expected/desired behavior: "Members" table is created in "mySchema"
Actual behavior: "Members" table is created in "public" (the default schema)
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace Test
{
class MyContext : DbContext
{
readonly string connectionString;
readonly string schema;
readonly bool isParameterless;
public MyContext()
{
connectionString = #"Server = 127.0.0.1; Port = 5432; Database = empty; User Id = postgres; Password = 123;";
isParameterless = true;
}
public MyContext(string connectionString, string schema)
{
this.connectionString = connectionString;
this.schema = schema;
}
public DbSet<Member> Members { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
if (!isParameterless)
modelBuilder.HasDefaultSchema(schema);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(connectionString);
}
}
class Member
{
public string id { get; set; }
}
class Programs
{
static void Main(string[] args)
{
var context = new MyContext(#"Server = 127.0.0.1; Port = 5432; Database = test; User Id = postgres; Password = 123;", schema: "mySchema");
context.Database.EnsureDeleted();
Console.WriteLine(context.Model.GetDefaultSchema()); // "mySchema"
Console.WriteLine(context.Model.FindEntityType(typeof(Member)).GetSchema()); // "mySchema"
context.Database.Migrate();
Console.WriteLine(context.Model.GetDefaultSchema()); // "mySchema"
Console.WriteLine(context.Model.FindEntityType(typeof(Member)).GetSchema()); // "mySchema"
context.Database.ExecuteSqlRaw(#"CREATE TABLE public.""Members""()"); // Npgsql.PostgresException: '42P07: relation "Members" already exists'
context.Database.ExecuteSqlRaw(#"CREATE TABLE ""mySchema"".""Members""()"); // Npgsql.PostgresException: '3F000: schema "mySchema" does not exist'
}
}
}
Migrations were generated for this code using the following command:
dotnet ef migrations add init -v
Further technical detail
EF Core version: 3.1.0
Database provider: Npgsql.EntityFrameworkCore.PostgreSQL (3.1.0)
Target framework: NET Core 3.0
VS Project.zip
You haven't posted a full program so it's not possible to be sure.
However, my guess is that you've created a context via the parameterless constructor before doing so with the other constructor. The EF Core model building, which includes running OnModelCreating, runs only once when you first use the context; it isn't redone for every single context you instantiate. As a result, you can't have multiple contexts with different default schemas.
I try Advantage Database server as data provider for MVC application. Data source is free Visual FoxPro's tables. I define DbContext, data provider and simple LINQ as shown below
provider:
invariantName="Advantage.Data.Provider" type="Advantage.Data.Provider.AdsProviderServices, EntityFramework.Advantage.v12, Version=12.0.0.0, Culture=neutral"
connectionStrings: tested both of them
name="Context1_ads" connectionString="Data Source=D:\PathToData\; ServerType=LOCAL; TableType=CDX;" providerName="Advantage.Data.Provider"
name="Context2_ads" connectionString="Data Source=\\AdsSrv:6262\Data; TableType=CDX;" providerName="Advantage.Data.Provider"
[Table("STATE", Schema = "::this")] // is this needed?
public class State
{
public string Guid { get; set; }
public string Pseudonim { get; set; }
public string Statename { get; set; }
//...
}
//Mapping
public class StateMap : EntityTypeConfiguration<State>
{
public StateMap()
{
this.ToTable("State");
this.HasKey(t => t.Guid);
//...
}
}
// Query
ctx.States.Where(t => t.Pseudonim=="Nim").SingleOrDefault();
And get:
Error 7200: AQE Error: State = HY000; NativeError = 5041; [SAP][Advantage SQL Engine][ASA] Error 5041: The requested object was not found. dbo - link object is not supported on free connection. Table name: State AdsCommand query execution failed.
Genetated query:
SELECT
"Limit1"."Guid" AS "Guid"
FROM ( SELECT TOP 2
"Extent1"."Guid" AS "Guid"
FROM "dbo"."State" "Extent1"
WHERE (("Extent1"."Pseudonim" = :p__linq__0) OR (("Extent1"."Pseudonim" IS NULL) AND (:p__linq__0 IS NULL)))
) "Limit1"
Is possible use LINQ to dbf with Advantage provider?
Add default schema for DbContext:
modelBuilder.HasDefaultSchema("");
I need to use a transaction in Entity Framewok (version 5) in a controller of an MVC4 project.
This because I've to save data in different table within the same transaction and avoid data inconsistency..
using System;
using System.Collections.Generic;
using System.Linq; using System.Web.Mvc;
using System.IO;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Data;
using System.Data.Objects;
private DBcontextName context = new DBcontextName ();
context.Connection.Open();
When i try to use transaction, the object Connection is not recognized by context
DbContext does not contain a definition for 'Connection' and no extension method 'Connection' accepting a first argument of type...
I don't understand what it's wrong,
can you help me please?
namespace NameSpaceName {
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Linq;
public partial class DBcontextName : DbContext
{
public DBcontextName ()
: base("name=DBcontextName ")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet......{ get; set; }
public DbSet......{ get; set; }
}
}
Thanks
Try like this:
using (TransactionScope scope = new TransactionScope())
{
using (DBcontextName context = new DBcontextName()
{
SqlConnection connection = (SqlConnection)((EntityConnection)context.ObjectContext.Connection).StoreConnection;
using (SqlCommand command = storeConnection.CreateCommand())
{
command.Connection = connection ;
connection.Open();
command.CommandText = yourStoredProcedureName;
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(yourSqlParameters);
using (DbDataReader reader = command.ExecuteReader())
{
// Do stuff
}
}
}
scope.Complete();
}
You only need to do this if you are calling a stored procedure though (for speed with multiple records, you could have a proc taking a table-valued parameter to save a list of records). If you just want to use entity framework, you can do this:
using (TransactionScope scope = new TransactionScope())
{
using (DBcontextName context = new DBcontextName()
{
// Get objects, delete objects, add objects, etc.
// Or add new objects
context.SaveChanges();
}
scope.Complete();
}
I use POCO in Entity Framework. Is any direct or indirect way in the latest EF version to get Table name at the runtime to avoid hardcode values?
I need it inside my custom database initializer to run code like this:
context.Database.ExecuteSqlCommand(
string.Format("DBCC CHECKIDENT ({0}, RESEED, {1})", tableName, newSeed))
Thanks
I'm working from the assumption that your context looks something like mine, with each of the table names getting generated from the class names when you add a DbSet to your context. If that's the case, you can achieve your goal with reflection, though it's a little ugly:
public class MyContext : DbContext
{
public MyContext() : base("MyDatabase")
{
}
public DbSet<Video> Video { get; set; }
public DbSet<VideoRating> Rating { get; set; }
public DbSet<User> User { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
public class Initializer : IDatabaseInitializer<DashVaultContext>
{
public void InitializeDatabase(MyContext context)
{
if (!context.Database.Exists())
{
context.Database.Create();
PropertyInfo[] propertyInfos = typeof(MyContext).GetProperties(BindingFlags.DeclaredOnly |
BindingFlags.Public | BindingFlags.Instance);
var newSeed = 1000; // Or whatever is appropriate
foreach (PropertyInfo propertyInfo in propertyInfos)
{
var tableName = propertyInfo.PropertyType.GetGenericArguments()[0].Name;
context.Database.ExecuteSqlCommand(
string.Format("DBCC CHECKIDENT ({0}, RESEED, {1})", tableName, newSeed));
}
}
}
}
}
UPDATE: I removed the pluralization hack and just turned off pluralization in the generated table names (see the OnModelCreating override).
POCO means you can use "plain-old" CLR objects (POCO), such as existing domain objects, with your data model. These POCO data classes (also known as persistence-ignorant objects), which are mapped to entities that are defined in a data model and by definition it shouldn't be directly related to database implementation details. However, you can use constant class and Fluent mapping to facilitate your requirement in a better way
Your constant class implementation
public static class Constant
{
public const string CreditCustomer = "dbo.CreditCustomer";
}
Your mappings goes like this
builder.Entity<Customer>()
.HasKey(c => c.ID)
.MapSingleType(c => new {
cid = c.ID,
nme = c.Name
}
)
.ToTable(Constant.Table.CreditCustomer);
In your dbInitializer
context.Database.ExecuteSqlCommand(
string.Format("DBCC CHECKIDENT ({0}, RESEED, {1})", Constant.Table.CreditCustomer, newSeed))
Looking at how "active" this discussion is, it seems to me this functionality is just not provided in the current version of EF. I hope this features will be available in one of future version of EF.
Will this code be useful at all?
var query = from meta in context.MetadataWorkspace.GetItems(DataSpace.SSpace)
.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)
let properties = meta is EntityType ? (meta as EntityType).Properties : null
select new
{
TableName = (meta as EntityType).Name,
Fields = from p in properties
select new
{
FielName = p.Name,
DbType = p.TypeUsage.EdmType.Name
}
};