I am writing a MySQL INSERT ... ON DUPLICATE KEY UPDATE implementation via a raw SQL command in EF Core 2.0. I am very close to a working solution, but the only problem that I have is determining which PropertyInfos read via reflection are primary keys. In the CreateUpdates() method below, how can I filter primary keys out from columnProperties so they are not a part of the update SQL statement?
I have tried using EntityFramework.PrimaryKey
, but I cannot seem to get it to work with generics (TEntity).
I have included all of my relevant code, but the piece I am focused on in this question is the TODO in the last method, CreateUpdates().
private static void InsertOnDuplicateKeyUpdate<TEntity>(DbContext dbContext) where TEntity : class
{
var columnProperties = GetColumnPropertiesLessBaseEntityTimestamps<TEntity>();
var tableName = GetTableName<TEntity>();
var columns = string.Join(", ", columnProperties.Select(x => x.Name));
var values = CreateValues<TEntity>(columnProperties);
var updates = CreateUpdates(columnProperties);
var rawSqlString = "INSERT INTO " + tableName + " (" + columns + ") VALUES " + values +
" ON DUPLICATE KEY UPDATE " + updates;
dbContext.Set<TEntity>().FromSql(rawSqlString);
dbContext.SaveChanges();
}
private static string GetTableName<TEntity>()
{
return typeof(TEntity).Name.Pluralize().ToLower();
}
private static List<PropertyInfo> GetColumnPropertiesLessBaseEntityTimestamps<TEntity>()
{
return typeof(TEntity).GetProperties().Where(x =>
x.PropertyType.Namespace != "System.Collections.Generic" &&
!new List<string> {"CreatedDateUtc", "ModifiedDateUtc"}.Contains(x.Name)).ToList();
}
private static string CreateValues<TEntity>(IReadOnlyCollection<PropertyInfo> columnProperties)
{
return GetSeedRows<TEntity>().Select(row => CreateRowValues(columnProperties, row)).Aggregate("",
(current, rowValues) => current == "" ? rowValues : current + ", " + rowValues);
}
private static string CreateRowValues<TEntity>(IEnumerable<PropertyInfo> columnProperties, TEntity row)
{
return (from property in columnProperties
let value = row.GetType().GetProperty(property.Name).GetValue(row)
select WrapStringPropertyValueInSingleQuotes(property, value)).Aggregate("",
(current, value) => current == "" ? "(" + value : current + ", " + value) + ")";
}
private static object WrapStringPropertyValueInSingleQuotes(PropertyInfo property, object value)
{
if (property.PropertyType == typeof(string))
value = "'" + value + "'";
return value;
}
private static string CreateUpdates(IEnumerable<PropertyInfo> columnProperties)
{
//TODO: filter out primary keys from columnProperties
return columnProperties.Select(property => property.Name).Aggregate("", (current, column) => current == ""
? column + " = VALUES(" + column + ")"
: current + ", " + column + " = VALUES(" + column + ")");
}
In ef-core it has become much easier to retrieve meta data from the mapping model. You can get the PropertyInfos of the primary key by this line:
var keyPropertyInfos = dbContext.Model.FindEntityType(typeof(TEntity))
.FindPrimaryKey()
.Properties
.Select(p => p.PropertyInfo);
By the way, you can get all (mapped) properties by replacing FindPrimaryKey().Properties by GetProperties();
Related
I have a problem with a MySQL query, I'm making a controller where I need a filter on the front (React). The controller is made with params inside the route, but is not working when I try to test it with Postman, but if I make the same exact query on phpmyadmin works perfectly.
Controller:
let { tabla, nameUno, fechaIni, fechaFin, nameDos, nameTres, nameCuatro } =
req.params;
conn.query(
// SELECT * FROM tyt_finan WHERE tf_fecha_r BETWEEN '2022-05-31' AND '2022-06-01' AND tf_city IN ('Bogota') AND tf_estado = 'Pendiente' AND tf_campana = 'IN'
"SELECT * FROM " +
tabla +
" WHERE " +
nameUno +
" BETWEEN " +
fechaIni +
" AND " +
fechaFin +
" AND " +
nameDos +
" IN ('') AND " +
nameTres +
" = ? AND " +
nameCuatro +
" = ?",
[req.params.value1, req.params.value2, req.params.value3],
Routes
routes.get(
"/searchAll/:tabla/:nameUno/:fechaIni/:fechaFin/:nameDos/:value1/:nameTres/:value2/:nameCuatro/:value3",
defaultController.searchAll
);
And this is the GET petition that I'm sending on Postman:
http://localhost:9000/searchAll/tyt_finan/tf_fecha_r/'2022-05-31'/'2022-06-01'/tf_city/'Bogota','Barranquilla'/tf_estado/'Pendiente'/tf_campana/'IN'
Use parameters for the variables. It's also easier to use a template literal than concatenation to construct the SQL string with variables for the table and column names.
You also had more values in your parameters array than ? in the SQL. I've replaced IN ('') with = ? to use value1 there.
let {tabla, nameUno, fechaIni, fechaFin, nameDos, value1, nameTres, value2, nameCuatro, value3} = req.params;
conn.query(
`SELECT *
FROM ${tabla}
WHERE ${nameUno} BETWEEN ? AND ?
AND ${nameDos} = ?
AND ${nameTres} = ?
AND ${nameCuatro} = ?`, [fetchaIni, fechaFin, value1, value2, value3], (err, res) => {
if (!err) {
console.log(res);
}
});
Here is the structure:
And below is the code:
public IQueryable<PageTemplate> GetTemplate()
{
var PageTemplates = from oPT in _db.PageTemplates
join oHSFK in _db.HelperSites on oPT.SiteID equals oHSFK.SiteID into oHSChild
from oHS in oHSChild.DefaultIfEmpty()
join oHUFK in _db.HelperUsers on oPT.SiteID equals oHUFK.UserID into oHUChild
from oHU in oHUChild.DefaultIfEmpty()
where oPT.SiteID == ConfigDto.SiteDetails.SiteID || oPT.SiteID == null
select new
{
TemplateID = oPT.TemplateID,
TemplateName = oPT.TemplateName,
//SiteName = oHS.SiteName,
//UpdatedByName = oHU.UserFirstName + " " + oHU.UserLastName,
UpdatedDate = oPT.UpdatedDate
};
return null;
}
How do I return IQueryable<PageTemplate> which has related Entities already. I know the workaround of making a new class having all the required properties of PageTemplate, HelperSite & HelperUser classes. But, I am looking for a solution, if possible, to use existing Entity Framework classes.
I tried with active annotation of xtend, and I want to create a live annotation which can generate a String[] field to record the names of method parameters.
#Target(ElementType::TYPE)
#Active(typeof(ParameterRecorderProcessor))
annotation ParameterRecorder {
}
class ParameterRecorderProcessor extends AbstractClassProcessor {
override doTransform(MutableClassDeclaration annotatedClass, extension TransformationContext context) {
var iii = 0;
// add the public methods to the interface
for (method : annotatedClass.declaredMethods) {
if (method.visibility == Visibility::PUBLIC) {
iii = iii + 1
annotatedClass.addField(method.simpleName + "_" + iii) [
type = typeof(String[]).newTypeReference // String[] doesn't work
var s = ""
for (p : method.parameters) {
if(s.length > 0) s = s + ","
s = s + "\"" + p.simpleName + "\""
}
val ss = s
initializer = [
'''[«ss»]'''
]
]
}
}
}
}
You can see I use typeof(String[]).newTypeReference to define the type of new created field, but it doesn't work. The generated java code is looking like:
private Object index_1;
It uses Object and the initializer part has be empty.
How to fix it?
This looks like a bug to me. As a workaround, you may want to use typeof(String).newTypeReference.newArrayTypeReference or more concise string.newArrayTypeReference
I am using/reusing json.net to get facebook likes of a user and his/her friend's like. I am having this error : Object reference not set to an instance of an object
In the following code, I have parsed facebook json three times and I have this exception raised the third time I have used it....in the methoduserlikes function to be precise...
I think the problem is in [var i].........it works fine the first two times but exeception occurs third time.....
kindly point out what seems to be the problem.....
oAuth.AccessTokenGet(Request["code"]);
global_token = oAuth.Token;
if (oAuth.Token.Length > 0)
{
url = "https://graph.facebook.com/me/likes?access_token=" + oAuth.Token;
url_friends = "https://graph.facebook.com/me/friends?access_token=" + oAuth.Token;
string json = oAuth.WebRequest(oAuthFacebook.Method.GET, url, String.Empty);
string jsonfriends = oAuth.WebRequest(oAuthFacebook.Method.GET, url_friends, String.Empty);
Label1.Text = oAuth.Token.ToString();
JObject likes = JObject.Parse(json);
string id = "";//user id
string name = "";//user likes
JObject friends = JObject.Parse(jsonfriends);
string fid = "";
foreach (var i in likes["data"].Children()) //Loop through the returned friends
{
id = i["id"].ToString().Replace("\"", "");
name = i["name"].ToString().Replace("\"", "");
Label3.Text = Label3.Text + "<br/> " + name;
}
foreach (var i in friends["data"].Children())
{
fid = i["id"].ToString().Replace("\"", "");
methoduserlikes(fid);
}
}
}
}
public void methoduserlikes(string id)
{
Label4.Text = Label4.Text + "<br/> " + id;
string flike = "";
string like_id = "";
string flike_url = "https://graph.facebook.com/" + id + "/likes?access_token=" + global_token;
string jsonfriend_like = oAuth.WebRequest(oAuthFacebook.Method.GET, flike_url, String.Empty);
JObject friend_like = JObject.Parse(jsonfriend_like);
foreach (var i in friend_like["data"].Children())
{
like_id = i["id"].ToString().Replace("\"", "");
**flike = i["name"].ToString().Replace("\"","");** here the exception is raised
Label5.Text = Label5.Text + "<br/> " + flike;
}
}
}
I have an entity that has a collection of associated entities in an EntitySet. Ultimately, I'm trying to report on some changes that have been made to this entity. I will most likely use the GetModifiedMembers() method to do this, and I'm guessing I can just do the same with each entity in the EntitySet, but I'm not sure how to tell if there have been any deletions in that EntitySet.
What's the best way to do this?
You could use the dataContext.GetChangeSet() to track all the changed entities and filter the specific T entities. Please see if this is what you want:
public static void ShowModifiedEntitiesInfo<T>(DataContext context) where T : class
{
foreach (var entity in context.GetChangeSet().Updates.Where(del => del is T).Cast<T>())
{
ModifiedMemberInfo[] modifiedMembers = context.GetTable<T>().GetModifiedMembers(entity);
Console.WriteLine("Updated Entity: " + entity.ToString());
Console.WriteLine(" (Members Changed)");
foreach (var member in modifiedMembers)
{
Console.WriteLine(" - Member Name: " + member.Member.Name);
Console.WriteLine(" - Original Value: " + member.OriginalValue.ToString());
Console.WriteLine(" - Current Value: " + member.CurrentValue.ToString());
}
}
foreach (var entity in context.GetChangeSet().Inserts.Where(del => del is T).Cast<T>())
{
Console.WriteLine("Inserted Entity: " + entity.ToString());
}
foreach (var entity in context.GetChangeSet().Deletes.Where(del => del is T).Cast<T>())
{
Console.WriteLine("Deleted Entity: " + entity.ToString());
}
}
EDIT:
Is what you need something like this?
public static void ShowModifiedCustomerInfo(MyDataContext context, Customer customer)
{
ModifiedMemberInfo[] modifiedMembers = context.Customers.GetModifiedMembers(customer);
List<Order> updatedOrders = context.GetChangeSet().Updates.Where(e => e is Order).Cast<Order>().Intersect<Order>(customer.Orders).ToList<Order>();
List<Order> insertedOrders = context.GetChangeSet().Inserts.Where(e => e is Order).Cast<Order>().Intersect<Order>(customer.Orders).ToList<Order>();
List<Order> deletedOrders = context.GetChangeSet().Deletes.Where(e => e is Order).Cast<Order>().Intersect<Order>(customer.Orders).ToList<Order>();
if (modifiedMembers.Length > 0 || updatedOrders.Count > 0 || insertedOrders.Count > 0 || deletedOrders.Count > 0)
{
Console.WriteLine("Updated Customer: " + customer.ToString());
foreach (var member in modifiedMembers)
{
Console.WriteLine(" - Member Name: " + member.Member.Name);
Console.WriteLine(" - Original Value: " + member.OriginalValue.ToString());
Console.WriteLine(" - Current Value: " + member.CurrentValue.ToString());
}
foreach (var entity in updatedOrders)
{
Console.WriteLine(" Updated Order: " + entity.ToString());
}
foreach (var entity in insertedOrders)
{
Console.WriteLine(" Inserted Order: " + entity.ToString());
}
foreach (var entity in deletedOrders)
{
Console.WriteLine(" Deleted Order: " + entity.ToString());
}
}
}
The Customer is the entity and has an EntitySet<Order>. For what I understand, you what to know if the customer itself has changed and if there was any order from this customer that also changed.
CodeSmith's PLINQO captures all changes made when SubmitChanges is executed and packages it all up in an Audit object that can be accessed from the context LastAudit property. Reports on what changed and what type of update it was. There is a sample at http://plinqo.com/home.ashx?NoRedirect=1#Auditing_18