EF 6 Migration: How to execute sql SELECT? - entity-framework

In our project we have necessity of adding some predefined data to DB. I think the best way and concept is using for that EF Migrations (not Seed method).
But we have a big troubles with adding related data to DB:
For Example:
Suppose we have 2 tables:
Users:
Id (PK auto increment)
Name
RoleId
Roles:
Id (PK auto increment)
Name
Let's suppose that we need to add User(Name = 'John', RoleId = (Id of role that name is 'Admin')).
How can we do it? It would be great if we find a solution that allows us to execute pure SQL SELECT script which not uses Entities of Code First because they can be modified or removed.
For DELETE, INSERT, UPDATE can be used Sql(...) method but what about SELECT?

You cannot have a context into the migration.
Logically first are ran the migrations to Update the DB Schema, then you can have a context to work with the data via it. If your DB does not match the model, or even the table is still not there, you cannot use it in EF.
I had to look into the EF code (and also because was curious). Practically the Sql() method in the DbMigration class in several levels below just adds the SQL string into a list of queries that should be executed into the transaction and moves on. It does not executes it when it is called. So in short EF just fills in a list of codes lines that should be executed in the end at once. And it seems correct if you try to walk in all paths of what you can do with the C# code in the migration code.
The question is quite good actually, unfortunately still I didn't found any better solution rather than using pure ADO.
Another option is to generate more custom SQL queries, and use T-SQL more widely.
For your case as you want to insert the user and set the groupId looking by the name, it can be used with inner select:
INSERT INTO Users (Name, GroupId)
VALUES ('John', RoleId = (SELECT Id FROM Roles WHERE Name = 'Admin')).
For my issue, I had to a bit do more sophisticated execution - the following does the same as the AddOrUpdate method of the DbSet, using the IF statement:
IF EXISTS (SELECT * FROM Table1 WHERE Column1='SomeValue')
UPDATE Table1 SET (...) WHERE Column1='SomeValue'
ELSE
INSERT INTO Table1 VALUES (...)
I found it here: http://blogs.msdn.com/b/miah/archive/2008/02/17/sql-if-exists-update-else-insert.aspx

I'm using good old LINQ for this:
public override void Up()
{
using (var dc = new DbContext("your connection string or name"))
{
var ids = dc.Database.SqlQuery<int>("SELECT id FROM sometable WHERE somefield={0}", 42).ToArray();
...
}
}
Using LINQ is better, even for usual migrations, because, there is a bug in DbMigration.Sql method, it ignores arguments: How to pass parameters to DbMigration.Sql() Method

Related

Can I add properties to a Mapping Table in postgres using Jakarta Persistence without creating a seperate entity?

I have the following many to many relationship...
#ManyToMany
#JoinTable(
name="user_brand_access",
schema = "auth",
joinColumns= #JoinColumn(name="person_id"),
inverseJoinColumns= #JoinColumn(name="brand_id")
)
private Set<Brand> brands;
This works fine as long as the table just has those 2 properties and an autogenerated id column. However, they would now like to add a column for the userID of the person adding the record. Now when I try to add a record I get...
2023-01-25 16:56:53 org.postgresql.util.PSQLException: ERROR: record "new" has no field "created_by"
2023-01-25 16:56:53 Where: SQL statement "INSERT INTO logging.history (tabname, schemaname, operation, new_val,created_by)
2023-01-25 16:56:53 VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP, row_to_json(NEW),COALESCE(NEW.created_by,NEW.updated_by,'my-user'))"
I am assuming this is because I am not defining the created by. I could probably solve this by creating a separate "PersonBrand" entity but as I go down that rabbit hole the model gets fairly confusing. Is there a way I can add this value to the relationshp without having to create a whole other entity?
I am assuming this is because I am not defining the created by. I could probably solve this by creating a separate PersonBrand entity but as I go down that rabbit hole the model gets fairly confusing.
This is absolutely the right and correct and normal and recommended thing to do and I don't see why it would be confusing.
Indeed, our usual recommendation for many years has been to avoid the use of #ManyToMany for precisely the reason you have discovered for yourself: that database tables with two FK columns almost inevitably get extra columns added to them at some point, and then you have to go and change your Java code.

How to change entry values for DbContext.SaveChanges()

We are trying to implement a multi tenant architecture in our Web API based application. We are using RLS in SQL Server, and Subscription_Id is, what is given to each subscriber. We have set the default value for Subscription_Id in SQL Server, so while I am calling db.SaveChanges(), I just want to ignore the Subscription_Id going to the SQL Server from the API.
I tried setting the value of Subscription_Id in the SaveChanges() override method but got stuck here.
public override int SaveChanges()
{
var objectType = selectedEntity.CurrentValues.ToObject();
Guid value = new Guid("54E720FC-616B-44C6-8485-5F2185FD7B4C");
PropertyInfo propertyInfo =
objectType.GetType().GetProperty("Subscription_Id");
ChangeTracker.Entries().FirstOrDefault()
.CurrentValues.ToObject().GetType()
.GetProperty("Subscription_Id")
.SetValue(objectType, Convert.ChangeType(value, propertyInfo.PropertyType), null);
return base.SaveChanges();
}
My advice is that you shouldn't modify your SaveChanges() code for this.
A recommended way of using RLS is making the TenantId columns transparent to your EF model and your code, so you don't need to define Tenant ID or navigation properties in your entities. This way you don't need to change your SaveChanges() code, or to explicitly manage and set Subscription_Id values anywhere in your code other that when opening the DB connection.
What you need to do is manually setting a default value constraint in the Subscription_Id columns in your database, with a default value based on the current session Subscription_Id parameter. The value will be set when inserting the records, and implicitly used to filter any subsequent queries and commands at database level.
In case of a new column:
ALTER TABLE SomeEntityTable ADD Subscription_Id nvarchar(128)
DEFAULT CAST(SESSION_CONTEXT(N'UserId') AS nvarchar(128))
In case of an existing column:
ALTER TABLE SomeEntityTable
ADD DEFAULT CAST(SESSION_CONTEXT(N'UserId') AS nvarchar(128)
FOR Subscription_Id
If the column had a previous different DEFAULT value it would be good to also delete its associated obsolete DEFAULT constraint. More info about updating default values in existing columns can be found here.
These columns should not be included in your model. You should not have properties for them in your entity classes. If you are using Database First you should make sure you exclude/ignore these columns when updating your model from your database.
How to do this if you are using EF Code First: you can manually include AlterColumn (or CreateColumn) instructions in a code migration after you generate it with Add-Migration. Do it for every entity table:
public override void Up()
{
AlterColumn("dbo.SomeEntityTable", "Subscription_Id",
c => c.String(
nullable: false,
maxLength: 128,
defaultValueSql: "CAST(SESSION_CONTEXT(N'UserId') AS nvarchar(128))"));
}
(It would be good to add also a Down() method removing the column.)
Warning: Be careful when running this migration if you already have existing records in the tables with an empty Subscription_Id column value (or if you are adding a new Subscription_Id column to a table that already have records). The empty column will be filled with the value of the Subscription_Id in the connection that is executing the migration, which probably will be wrong (you probably don't want all the existing records to be associated to that specific subscription). In that case you may want to include explicit UPDATE instructions with the right Subscription_Id values in your Up() method, with the Sql() method. Something like this:
Sql("UPDATE SomeEntitiesTable SET Subscription_Id= '19bc9b0d-28dd-4510-bd5e-d6b6d445f511' WHERE Id IN (1, 2, 5)");
With Code First you should also remove the Subscription_Id properties from your model classes. If you can't, at least add explicit Ignore() instructions in your configuration code for the Subscription_Id columns, you don't want them in your EF mappings.
Note: I'm assuming here that you created a RLS policy in your DB that uses UserId parameter in SESSION_CONTEXT, and that your application code is setting that value when opening the DB connection, via a DbConnectionInterceptor or something similar.
This page contains more info.

mvc Entity Framwork, insert using select max() from table

I am developing an App using MV5 and Entity Frameowrk to connect to SQL Server Database
I need to do an Insert where the is getting ID like this.
Select max(id)+1 from table where field_id = #ID.
The idea is to do it in one transaction.
Is that Posible to do it in Entity Framework or I should use a Store Procedure?
EF compiles the C# expressions you provide it into SQL, and then executes the resulting SQL. So you can write such a query in LINQ, which will in-fact be computed during a single "call" to the database:
long id = MyDbContext.MyDbSet.Max(entity => entity.Id) + 1;
Do note, that if you are doing this so that you can assign a non existing ID to a new entry- there's no need. EF takes care of this for you, so long as the field is named Id and it is of type long. Just do not assign it any value.

Insert postgres row using npgsql

In my postgres database, the keys for my tables are serial data types that the postgres database autoincrements. Using C#, Npgsql, Entity Framework 6, WPF, linq and EF dbsets (any extension methods?), is there a way to implement a repository method such as:
public virtual void Add(TEntity entity, string addrow)
{NpgsqlCommand cmd = new NpgsqlCommand(addrow, DbConnect);
cmd.ExecuteNonQuery();}
The viewmodel would pass in the string argument something like:
_addRow = string.Format("insert into mytable ('descrip') values ( " + DescripProperty + ") returning 'mytable_id' into mytableID");
Is this the right way to insert a postgres row / record that has an autoincrement key? Or do I need to utilize a PL/pgSQL - SQL Procedural Language function to first lock the table and do the insert from that server side? Thank you in advance.
EDIT ---------------------------
For clarification, I pass the EF DbContext into the genericrepository constructor. This code is in genericrepository class:
private readonly DbSet<TEntity> _aquery;
public DbSet<TEntity> AQuery;
// CTOR - inject dbcontext/entities
public GenericRepository(MyContextClass context)
{
if (context == null)
throw new ArgumentNullException("context");
_theDbContext = context;
_aquery = _theDbContext.Set<TEntity>();
AQuery = _aquery;
}
So I can do AQuery.AsQueryable, etc.
Your SQL query is correct in the sense that the serial key will get autogenerated by PostgreSQL and then return the new ID - there's no need for pl/pgsql or any explicit locking - PostgreSQL's serial mechanism is atomic and will work. However, several comments on your code:
You're passing Entity Framework entirely and doing things with raw SQL. The whole point of an ORM is to generate this kind of statement for you so you don't have to, and EF6 is perfectly capable of doing so.
Concatenating a parameter into your SQL query is a bad idea and opens the door to SQL injection. You should use a parameter instead.
You don't seem to be actually using the returned ID, so there's little use for the returning clause (although you may have omitted the relevant code)

Create entity and return ID

Using Open JPA 2.0, and database is DB2 9.7. I would like to like to create an entity which includes ID which is Primary Key, and auto generated Identity column and after creating the entity I need to display the ID generated. For this after persisting the entity, I am calling entity.getId().
In the database, I see below query getting executed
select ID from final table
(INSERT INTO WEB.USER (NAME, LOCATION) VALUES (?, ?))
Is there any alternate / better way to create an entity and return the generated ID? I would like to avoid "Select ID from final table", as this is expected to have performance impact.
This is how you retrieve auto generated ID from DB2 and there is nothing you can do about it. Other databases often require extra SQL query, so it's actually even better with DB2, which returns ID immedaitely. I don't think there is a faster way to so this.
Check out OPENJPA-736 where this optimization was implemented.
I am using Database sequence (#SequenceGenerator) to avoid "select ID from final table". This worked fine and #TableGenerator would also have sorted this issue.