Run Custom Script after every Code-First Migration - entity-framework

I need to create a multi-column index like so:
CREATE NONCLUSTERED INDEX [IX_UserName_Inc_LastOnline_Lat_Long] ON [dbo].[User]
(
UserName ASC
)
Include (LastOnline, Latitude, Longitude)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO
I've found some ways to do this, but nothing that stands out as a way to get exactly the above. Is there a way to include custom SQL as part of migrations without manually doing add-migration and adding the code there?

I decided to run this as part of my Seed() method:
protected override void Seed(AppContext context) {
Database.SetInitializer(new MigrateDatabaseToLatestVersion<AppContext, Configuration>());
if (!WebSecurity.Initialized) {
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "User", "UserId", "Username", autoCreateTables: true);
}
context.Database.ExecuteSqlCommand(
"IF NOT EXISTS(SELECT * FROM sys.indexes WHERE name = 'IX_UserName_Inc_LastOnline_Lat_Long') " +
"BEGIN " +
" CREATE NONCLUSTERED INDEX [IX_UserName_Inc_LastOnline_Lat_Long] ON [dbo].[User] " +
" (UserName ASC) " +
" Include (LastOnline, LocationLat, LocationLong) " +
" WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) " +
"END"
);
base.Seed(context);
}

Related

T-Sql update and avoid conflict

I'm trying to migrate a Tomcat app from using Postgres 9.5 to SQL Server 2016 and I've got a problem statement I can't seem to duplicate.
It's basically an upsert but one of the complications is the request supplies arguments to do the update, but when there is conflict I need to use some of the existing values from conflicting rows to insert/update.
The primary keys in the table can sometimes cause a conflict, which requires updating rows and deleting the old ones.
The table schema in MS SQL looks like:
CREATE TABLE [dbo].[signup](
[site_key] [varchar](32) NOT NULL,
[list_id] [bigint] NOT NULL,
[email_address] [varchar](256) NOT NULL,
[customer_id] [bigint] NULL,
[attribute1] [varchar](64) NULL,
[date1] [datetime] NOT NULL,
[date2] [datetime] NULL,
CONSTRAINT [pk_signup] PRIMARY KEY CLUSTERED
(
[site_key] ASC,
[list_id] ASC,
[email_address] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
The old Postgres SQL looked like this:
WITH updated_rows AS (
INSERT INTO signup
(site_key, list_id, email_address, customer_id, attribute1, date1, date2)
SELECT site_key, list_id, :emailAddress, customer_id, attribute1, date1, date2
FROM signup WHERE customer_id = :customerId and email_address <> :emailAddress
ON CONFLICT (site_key, list_id, email_address) DO UPDATE SET customer_id = excluded.customer_id
RETURNING site_key, customer_id, email_address, list_id
)
DELETE FROM signup AS signup_delete USING updated_rows
WHERE
signup_delete.site_key = updated_rows.site_key
AND signup_delete.customer_id = updated_rows.customer_id
AND signup_delete.list_id = updated_rows.list_id
AND signup_delete.email_address <> :emailAddress;
Two arguments are supplied, customer id and email address, shown here as Spring NamedParameterJdbcTemplate values :customerId and :emailAddress
It's trying to change the email address of the customer id to be the supplied one, but sometimes the supplied email address already exists in the primary key constraint.
In which case it needs to change the existing customer id to be supplied one, and remove the rows with that don't match the new email address.
I also need to try and maintain isolation so that nothing can change the data whilst I'm updating.
I'm trying to do it with a MERGE statement but I can't seem to get it to work, it's complaining I cant use values that aren't in the clause scope, but I think I've probably got other issues here too.
This is what I had so far. It doesn't even address the deleting part - only the upserting, but I can't even get this part to work. I was planning to use the OUTPUT from this as input to something to delete the rows similar to the postgres version.
WITH source AS (
SELECT cs.[site_key] as existing_site_key,
cs.list_id as existing_list_id,
cs.email_address as existing_email,
cs.customer_id as existing_customer_id,
cs.attribute1 as existing_attribute1,
cs.date1 as existing_date1,
cs.date2 as existing_date2,
cs2.email_address as conflicting_email,
cs2.customer_id AS conflicting_customer_id
FROM [dbo].[signup] cs
LEFT JOIN [dbo].[signup] cs2 ON cs2.email_address = :emailAddress
AND cs.site_key = cs2.site_key
AND cs.list_id = cs2.list_id
WHERE cs.customer_id = :customerId
)
MERGE signup WITH (HOLDLOCK) AS target
USING source
ON ( source.conflicting_customer_id is not null )
WHEN MATCHED AND source.existing_site_key = target.site_key AND source.existing_list_id = target.list_id AND source.conflicting_email = target.email_address THEN UPDATE
SET customer_id = :customerId
WHEN NOT MATCHED BY target AND source.existing_site_key = target.site_key AND source.existing_list_id = target.list_id AND source.conflicting_customer_id = :customerId THEN INSERT
(site_key, list_id, email_address, customer_id, attribute1, date1, date2) VALUES
(source.existing_site_key, source.existing_list_id, :emailAddress, source.customer_id, source.existing_attribute1, source.existing_date1, source.existing_date2)
Thanks,
mikee

EF Core FromSql Returning Odd Error Message

This code throws an exception when rawVoters.Count() is called:
string sql = #"select * from Voters as v
inner join Homes as h on v.HomeID = h.ID
inner join Locations as l on h.LocationID = l.ID
inner join Streets as s on l.StreetID = s.ID
inner join Cities as c on s.CityID = c.ID
inner join VoterAgencies as va on v.CountyID = va.CountyID and v.VoterID = va.VoterID
where (va.AgencyID = #agencyID)
and (c.Name like '%' + #city + '%')
and (v.FirstName like '%' + #firstName + '%')
and (v.LastName like '%' + #lastName + '%')
and (s.Name like '%' + #street + '%')
and ((#voterID = 0) or (v.VoterID = #voterID))";
List<SqlParameter> parameters = new List<SqlParameter>();
parameters.Add( new SqlParameter( "#agencyID", agencyID ) );
parameters.Add( new SqlParameter( "#city", model.City ) );
parameters.Add( new SqlParameter( "#firstName", model.FirstName ) );
parameters.Add( new SqlParameter( "#lastName", model.LastName ) );
parameters.Add( new SqlParameter( "#street", model.Street ) );
parameters.Add( new SqlParameter( "#voterID", model.VoterID ) );
IQueryable<Voter> rawVoters = _context.Voters
.AsNoTracking()
.FromSql( sql, parameters.ToArray() );
int numVoters = 0;
try
{
numVoters = rawVoters.Count();
}
catch( Exception e )
{
int i = 9;
i++;
}
The error message is:
"The column 'ID' was specified multiple times for 'v'."
I thought this might be because EF Core doesn't like the "as x" phrasing, so I substituted the table names for each of the identifiers...and got the same error message.
I'm curious as to what's going on here.
The problem was that the T-SQL was returning all fields (select *). Under EF Core, the returned fields must match the fields specified for the entity being returned, in this case Voter.
An inner join, like the one I was using, by default returns far more than just the Voter fields.
Changing the SQL to be select v.* (where v is the alias for Voters) solved the problem.
Hence you're just getting the Count,you can specify your column name as shown below.Then no issues :)
string sql = #"select v.ID from Voters as v

Needing help to improve some TSQL "not exists" query performance

I'm having an performance issue running a query on a table containing 750 000 entries. It takes between 15 to 20 seconds to execute, blocking access to the database during that time and creating lots of error logs (and angry customers, of course).
Here is the query:
DECLARE #FROM_ID AS UNIQUEIDENTIFIER = 'XXX'
DECLARE #TO_ID AS UNIQUEIDENTIFIER = 'YYY'
update tbl_share
set user_id = #TO_ID
where user_id = #FROM_ID
and not exists (
select *
from tbl_share ts
where ts.file_id = file_id
and ts.user_id = #TO_ID
and ts.corr_id = corr_id
and ts.local_group_id = local_group_id
and ts.global_group_id = global_group_id
)
I'm kind of stuck right now since my TSQL knowledge is limited.
I'm wondering if:
I should create a temporary table
I should select something else than "*"
I haven't lot of opportunities to run the tests since it's a production database and there are permanently 10-20 customers connected on day time.
Thanks for your help!
How about restructuring your code logic?
DECLARE #FROM_ID AS UNIQUEIDENTIFIER = 'XXX'
DECLARE #TO_ID AS UNIQUEIDENTIFIER = 'YYY'
IF NOT EXISTS (select *
from tbl_share ts
where ts.user_id = #TO_ID)
BEGIN
update tbl_share
set user_id = #TO_ID
where user_id = #FROM_ID
END
So, you are doing your check beforehand and do only updating the database in the case it is needed.
HTH
Let's start with optimizing the select.
Check query plans.
If that is the PK then is it fragmented?
select *
from tbl_share
where user_id = #FROM_ID
and not exists (
select *
from tbl_share ts
where ts.file_id = file_id
and ts.user_id = #TO_ID
and ts.corr_id = corr_id
and ts.local_group_id = local_group_id
and ts.global_group_id = global_group_id
)
select tUpdate.*
from tbl_share as tUpdate
left outer join tbl_share as tExists
on tUpdate.user_id = #FROM_ID
and tExists.user_id = #TO_ID
and tExists.file_id = tUpdate.file_id
and tExists.corr_id = tUpdate.corr_id
and tExists.local_group_id = tUpdate.local_group_id
and tExists.global_group_id = tUpdate.global_group_id
where tExists.user_id is null

Parametrized query inside mysqli class

I am using Mertol Kasanan's class for running parametrized queries -
http://liveplanet.googlecode.com/svn-history/r132/trunk/db/DB.php
I am very satisfied with the script except for some issues that I don't seem to put my finger on.
As it states in the brief tutorial in the class's description the method for running the query is:
$result = $db->query('SELECT * FROM `users` WHERE id = ? AND user_type = ? LIMIT ?',$id,$user_type,$limit);
Can anybody figure out how to run a query without defining any parameter as it seems that
$result = $db->query('SELECT * FROM `users` WHERE id = 'y' ");
neither
$result = $db->query('SELECT * FROM `users` WHERE id = 'y' ", '');
do not do the trick as it returns a binding error;
A workaround would be
$result = $db->query('SELECT * FROM `users` WHERE 1 = ? AND id = 'y' ", 1);
Is there a neater way to run my query?
I don't need parameters as the query gets it's values from a safe source inside a class.
Edit:
Let's say I have this:
if($HC == 'C'){
$sql = "SELECT * FROM `photo_c` WHERE `user` = ?i AND `pic` != ?s AND cat != 'D' GROUP BY pic LIMIT ?";
$query = $this->dbs->query($sql,$this->user,$this->user_head,4);
$results = $this->dbs->numRows($query);
if($results < 3){
$sql = "SELECT * FROM `photo` WHERE `user` = ?i AND `pic` != ?s ORDER BY id ASC LIMIT ?";
$query = $this->dbs->query($sql, $this->user,$this->user_head,4);
}
}else{
$sql = "SELECT * FROM `photo_c` WHERE `user` = ?i AND `pic` != ?s AND cat = ?s ORDER BY RAND() LIMIT ?";
$query = $this->dbs->query($sql,$this->user,$this->user_head,$HC,4);
$results = $this->dbs->numRows($query);
}
Now, in order to get the data from the right query I can either define $data->getAll under each query - but that would mean repeating my code or I could try extracting the data from the last defined $query result - which I do not know how to do.
I know that there may be a better way of doing this but I am trying to improve my coding style as I think the safemysql class would need some improvements even if that would mean a bit more documentation.
I could try using $db->getAll instead of $db->query but, as far as I know, I cannot use numRows on GetAll.
As a matter of fact, this class is totally unusable. And the problem you mentioned is a least one.
It seems that someone who wrote it, never used this class in a real life project.
So, if you want a class which works and works way better, go for SafeMysql, as it will do exactly what you want:
$data = $db->getAll("SELECT * FROM `users` WHERE status = 'y'");
(note that you've got your data already, without any further code)
Nevertheless, you have to understand that the following statement of yours
I don't need parameters as the query gets it's values from a safe source inside a class.
is wrong.
It's OK to use a hard-coded value as you wrote it, but if you were intended to use a "safe" variable - it ought to be added via placeholder. Otherwise your query remains error-prone and unsafe.
So, it have to be
$id = 1; // "safe" variable
$data = $db->getRow("SELECT * FROM `users` WHERE id = ?i", $id);
To answer edited question. Not sure if it's what you need, but here is the code. It wu
if($HC == 'C')
{
$sql = "SELECT * FROM `photo_c` WHERE `user` = ?i AND `pic` != ?s AND cat != 'D' GROUP BY pic LIMIT ?";
$data = $this->dbs->getAll($sql,$this->user,$this->user_head,4);
if (count($data) < 3) {
$sql = "SELECT * FROM `photo` WHERE `user` = ?i AND `pic` != ?s ORDER BY id ASC LIMIT ?";
$data = $this->dbs->query($sql, $this->user,$this->user_head,4);
}
} else {
$sql = "SELECT * FROM `photo_c` WHERE `user` = ?i AND `pic` != ?s AND cat = ?s ORDER BY RAND() LIMIT ?";
$data = $this->dbs->query($sql,$this->user,$this->user_head,$HC,4);
}

How to add logic to my SQL query to include/exclude search parameters?

Using SQL Server 2008R2, I have here is a simple query:
SELECT *
FROM ItemData
WHERE FREETEXT(Title, '"' + #OriginalSearchTerm + '"')
AND ( WebsiteID=#WebsiteID AND GeoCity = #GeoCity AND GeoState = #GeoState )
ORDER BY ItemListID DESC
This is all fine when there is a valid value for #GeoCity and #GeoState. However there will be scenarios where #GeoCity = -1 and/or #GeoState = -1.
I would rather not write entire separate queries for these cases, although this would work just fine.
How can I optimize the current query to do just this?
Thanks.
-- This might be more efficient as OR statement executes block for each OR statement.
SELECT *
FROM ItemData
WHERE FREETEXT(Title, '"' + #OriginalSearchTerm + '"')
AND ( WebsiteID=#WebsiteID AND
(GeoCity = ISNULL(#GeoCity,-1)) AND
(GeoState = ISNULL(GeoState,-1))
ORDER BY ItemListID DESC
I'm not sure what you want the query to do when #GeoCity = -1 and/or #GeoState = -1. Assuming you want to exclude the GeoCity = #GeoCity and/or GeoState = #GeoState conditions in that case, here's the query:
SELECT *
FROM ItemData
WHERE FREETEXT(Title, '"' + #OriginalSearchTerm + '"')
AND ( WebsiteID=#WebsiteID AND
(#GeoCity = -1 OR GeoCity = #GeoCity) AND
(#GeoState = -1 OR GeoState = #GeoState) )
ORDER BY ItemListID DESC