Can I do an UPDATE on a JOIN query with OrmLite on ServiceStack? - ormlite-servicestack

I want to do an update for a specific field on a table based on the results from a query that includes a join. Using OrmLite with ServiceStack.
My Classes are as follows:
public class Document
{
public int Id { get; set; }
public string BCL_Code { get; set; }
public bool IsActive { get; set; }
public int DocumentTypeId { get; set; }
}
public class DocumentType
{
public int Id { get; set; }
public string TypeName { get; set; }
}
Trying to do the Update on the Join with the following code:
var q = db.From<Document>()
.Join<Document, DocumentType>(
(doc, type) => doc.DocumentTypeId == type.Id)
.Where(d => d.BCL_Code == "FR49")
.And<DocumentType>(t => t.TypeName == "Enrollment Process");
db.UpdateOnly(new Document { IsActive = false }, onlyFields: q);
I know I can update specific fields, and I know how to do joins, but when I try to include a join in the query, and then do an UpdateOnly, I get an error message on the db.UpdateOnly line:
The multi-part identifier "DocumentType.TypeName" could not be bound.
Is it possible to do an Update on a Join Query?
If so, what is the proper way to do it?

There's no Typed APIs for Update From Table in OrmLite yet, you can add a feature request for it.
In the meantime you can use Custom SQL, e.g:
db.ExecuteSql(#"UPDATE Document SET IsActive = #isActive
FROM Document d
INNER JOIN DocumentType t ON (d.DocumentTypeId = t.Id)
WHERE d.BCL_Code = 'FR49'
AND t.TypeName = 'Enrollment Process'",
new { isActive = false });

Related

.Net Core 6.0 Web API - How to implement postgresql database(eg: Product Table -> Description column) localization for English and French?

I am developing a Web API using Core 6.0 with localization. Localization should be supported for both static (e.g., basic strings like greeting) and dynamic content (e.g., Values of the Product Instance).
I have implemented the localization for static content using JsonStringLocalizerFactory as discussed in this article - https://christian-schou.dk/how-to-add-localization-in-asp-net-core-web-api/.
public class LocalizerController : ControllerBase
{
private readonly IStringLocalizer<LocalizerController> _stringLocalizer;
public LocalizerController(IStringLocalizer<LocalizerController> stringLocalizer)
{
_stringLocalizer = stringLocalizer;
}
[HttpGet]
public IActionResult Get()
{
var message = _stringLocalizer["hi"].ToString();
return Ok(message);
}
[HttpGet("{name}")]
public IActionResult Get(string name)
{
var message = string.Format(_stringLocalizer["welcome"], name);
return Ok(message);
}
[HttpGet("all")]
public IActionResult GetAll()
{
var message = _stringLocalizer.GetAllStrings();
return Ok(message);
}
}
Next, I would like to implement localization for dynamic content (e.g., Details of the Product which will be sent to the WEB API and stored in the postgresql database table).
A possible approach is to duplicate the postgresql database table for each language (English and French). Could there be a better approach to avoid duplicate data and additional manual work?
You can create language table for each multi-language entity.
Langugage model;
public class Language
{
public int Id { get; set; }
public string Name { get; set; }
public string IsoCode { get; set; }
}
Static language list;
public class Constant
{
public static List<Language> Languages { get; set; } = new()
{
new Language
{
Id = 1,
Name = "English(United States)",
IsoCode = "en-US"
},
new Language
{
Id = 2,
Name = "Turkish",
IsoCode = "tr-TR"
}
};
}
Entities;
public class Product
{
public int Id { get; set; }
public decimal Price { get; set; }
public virtual ICollection<ProductLang> ProductLangs { get; set; }
}
public class ProductLang
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
[ForeignKey("Products")]
public Guid ProductId { get; set; }
public virtual Product Product { get; set; }
public int LanguageId { get; set; }
}
You can change the LanguageId property name. If you want to store languages in database, you can create a Languages table and create a relationship with that table from entity language tables. This can reduce duplication.
After include the language table to the entity, you can write an extension method to easily get the requested language data.
public static string GetLang<TEntity>(this IEnumerable<TEntity> langs, Expression<Func<TEntity, string>> propertyExpression, int defaultLangId)
{
var languageIdPropName = nameof(ProductLang.LanguageId);
var requestedLangId = GetCurrentOrDefaultLanguageId(defaultLangId);
if (langs.IsNullOrEmpty())
return string.Empty;
var propName = GetPropertyName(propertyExpression);
TEntity requestedLang;
if (requestedLangId != defaultLangId)
requestedLang = langs.FirstOrDefault(lang => (int)lang.GetType()
.GetProperty(languageIdPropName)
.GetValue(lang) == requestedLangId)
?? langs.FirstOrDefault(lang => (int)lang.GetType()
.GetProperty(languageIdPropName)
.GetValue(lang) == defaultLangId);
else requestedLang = langs.FirstOrDefault(lang => (int)lang.GetType().GetProperty(languageIdPropName).GetValue(lang) == defaultLangId);
requestedLang ??= langs.FirstOrDefault();
return requestedLang.GetType().GetProperty(propName).GetValue(requestedLang, null)?.ToString();
static int GetCurrentOrDefaultLanguageId(int defaultLanguageId)
{
var culture = CultureInfo.CurrentCulture;
var currentLanguage = Constant.Languages.FirstOrDefault(i => i.IsoCode == culture.Name);
if (currentLanguage != null)
return currentLanguage.Id;
else
return defaultLanguageId;
}
static string GetPropertyName<T, TPropertyType>(Expression<Func<T, TPropertyType>> expression)
{
if (expression.Body is MemberExpression tempExpression)
{
return tempExpression.Member.Name;
}
else
{
var op = ((UnaryExpression)expression.Body).Operand;
return ((MemberExpression)op).Member.Name;
}
}
}
This extension method checks for 3 conditions;
If there is data in the requsted language, it returns this data,
If there is no data in the requsted language, it checks if there is data in the default language. If the data is available in the default language, it will return the data,
Returns the first available language data if there is no data in the default language
Usage;
var defaultLangId = 1;
Product someProduct = await _dbContext.Set<Product>().Include(i => i.ProductLangs).FirstOrDefaultAsync();
var productName = someProduct.ProductLangs.GetLang(i => i.Name, defaultLangId);
It is up to you to modify this extension method according to your own situation. I gave you an example scenario where languages are kept in a static list.

Azure Table Storage: Use existing properties as PartitionKey and RowKey

I have a model where existing fields already fits quite nicely for RowKey and PartitionKey but I don't know how to assign them and not have duplicate data. Consider this example:
public class ClassA : ITableEntity
{
// My existing fields
public string Id { get; set; }
public string Receiver { get; set; }
public DateTimeOffset? Received { get; set; }
// ITableEntity fields
public string PartitionKey { get => Receiver; set => Receiver = value; }
public string RowKey { get => Id; set => Id = value; }
public DateTimeOffset? Timestamp { get => Received; set => Received = value; }
public ETag ETag { get; set; }
}
This works but we get duplicate data as expected. Then I tried to add [IgnoreDataMember] to Id, Receiver and Received but then I cannot query using TableClient.QueryAsync(x => x.Id == "...").
Is there a way I can map the ITableEntity to my exisiting fields so I:
Avoid having duplicate data
Can query using my exisiting fields
I'm using Azure.Data.Tables Version 12.4.0
With Table Storage, it is not possible to designate any attributes as PartitionKey and RowKey attribute. The attributes must be named PartitionKey and RowKey.
You can query on any existing fields but the query will not be treated the same as querying on PartitionKey/RowKey fields and will result in full table scan.

RavenDB array search returns random results

I'm trying to perform a search on top of a dictionary using the Search method from RavenDB 4. Strangely, if the search term is the word in or it I get random results back. I'm absolutely sure that none of the records contains those words. It also happens when executing the equivalent lucene query on the studio. It works as expected when I enter a valid search term like the employee's name, number, etc.
I've managed to create this simple scenario based on the real one.
Here's the index:
public class Search : AbstractIndexCreationTask<Employee, Page>
{
public Search()
{
Map = employees => from employee in employees
select new
{
Id = employee.Id,
Details = employee.Details
};
Reduce = results => from result in results
group result by new
{
result.Id,
result.Details
}
into g
select new
{
g.Key.Id,
g.Key.Details
};
Index("Details", FieldIndexing.Search);
}
}
Employee class:
public class Employee
{
public string Id { get; set; }
public Dictionary<string, object> Details { get; set; }
}
Adding employees:
details = new Dictionary<string, object>();
details.Add("EmployeeNo", 25);
details.Add("FirstNames", "Yuri");
details.Add("Surname", "Cardoso");
details.Add("PositionCode", "XYZ");
details.Add("PositionTitle", "Developer");
employee = new Employee
{
Details = details
};
session.Store(employee);
session.SaveChanges();
Search method:
var searchTerm = "in";
var result = session
.Query<Page, Search>()
.Search(i => i.Details, $"EmployeeNo:({searchTerm})")
.Search(i => i.Details, $"FirstNames:({searchTerm})", options: SearchOptions.Or)
.Search(i => i.Details, $"Surname:({searchTerm})", options: SearchOptions.Or)
.Search(i => i.Details, $"PositionCode:({searchTerm})", options: SearchOptions.Or)
.Search(i => i.Details, $"PositionTitle:({searchTerm})", options: SearchOptions.Or)
.ToList();
Lucene query outputed:
from index 'Search' where search(Details, "EmployeeNo:(it)")
or search(Details, "FirstNames:(it)")
or search(Details, "Surname:(it)")
or search(Details, "PositionCode:(it)")
or search(Details, "PositionTitle:(it)")
Any idea why random results are returned when those specific words are enterered?
The issue is stop words. Certain terms are so common, that they are meaningless for searching using full text search.
is, it, they, are, etc.
They are erased by the query analyzer.
See the discussion here: https://ravendb.net/docs/article-page/4.2/Csharp/indexes/using-analyzers
You can use a whitespace analyzer, instead of the Standard Analyzer, since the former doesn't eliminate stop words.
After getting help from the RavenDB group guys, we've managed to find a solution for my scenario.
Employee:
public class Employee
{
public string Id { get; set; }
public string DepartmentId { get; set; }
public Dictionary<string, object> Details { get; set; }
}
Department:
public class Department
{
public string Id { get; set; }
public string Name { get; set; }
}
Page:
public class Page
{
public string Id { get; set; }
public string Department { get; set; }
public Dictionary<string, object> Details { get; set; }
}
Index (with dynamic fields):
public class Search : AbstractIndexCreationTask<Employee, Page>
{
public Search()
{
Map = employees => from employee in employees
let dept = LoadDocument<Department>(employee.DepartmentId)
select new
{
employee.Id,
Department = dept.Name,
_ = employee.Details.Select(x => CreateField(x.Key, x.Value))
};
Store(x => x.Department, FieldStorage.Yes);
Index(Constants.Documents.Indexing.Fields.AllFields, FieldIndexing.Search);
}
}
Query:
using (var session = DocumentStoreHolder.Store.OpenAsyncSession())
{
var searchTearm = "*yu* *dev*";
var result = await session
.Advanced
.AsyncDocumentQuery<Page, Search>()
.Search("Department", searchTearm)
.Search("EmployeeNo", searchTearm)
.Search("FirstNames", searchTearm)
.Search("Surname", searchTearm)
.Search("PositionCode", searchTearm)
.Search("PositionTitle", searchTearm)
.SelectFields<Page>()
.ToListAsync();
}
Everything seems to be working fine this way, no more random results.
Big thanks to Ayende and Egor.

A circular reference was detected while serializing entities with one to many relationship

How to solve one to many relational issue in asp.net?
I have Topic which contain many playlists.
My code:
public class Topic
{
public int Id { get; set; }
public String Name { get; set; }
public String Image { get; set; }
---> public virtual List<Playlist> Playlist { get; set; }
}
and
public class Playlist
{
public int Id { get; set; }
public String Title { get; set; }
public int TopicId { get; set; }
---> public virtual Topic Topic { get; set; }
}
My controller function
[Route("data/binding/search")]
public JsonResult Search()
{
var search = Request["term"];
var result= from m in _context.Topics where m.Name.Contains(search) select m;
return Json(result, JsonRequestBehavior.AllowGet);
}
When I debug my code I will see an infinite data because Topics will call playlist then playlist will call Topics , again the last called Topic will recall playlist and etc ... !
In general when I just use this relation to print my data in view I got no error and ASP.NET MVC 5 handle the problem .
The problem happens when I tried to print the data as Json I got
Is there any way to prevent an infinite data loop in JSON? I only need the first time of data without call of reference again and again
You are getting the error because your entity classes has circular property references.
To resolve the issue, you should do a projection in your LINQ query to get only the data needed (Topic entity data).
Here is how you project it to an anonymous object with Id, Name and Image properties.
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new
{
Id = x.Id,
Name = x.Name,
Image = x.Image
});
return Json(result, JsonRequestBehavior.AllowGet);
}
If you have a view model to represent the Topic entity data, you can use that in the projection part instead of the anonymous object
public class TopicVm
{
public int Id { set;get;}
public string Name { set;get;}
public string Image { set;get;}
}
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new TopicVm
{
Id = x.Id,
Name = x.Name,
Image = x.Image
});
return Json(result, JsonRequestBehavior.AllowGet);
}
If you want to include the Playlist property data as well, you can do that in your projection part.
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new
{
Id = x.Id,
Name = x.Name,
Image = x.Image,
Playlist = x.Playlist
.Select(p=>new
{
Id = p.Id,
Title = p.Title
})
});
return Json(result, JsonRequestBehavior.AllowGet);
}

Entity Framework : adding record with related data

I have a very simple Situation with 2 tables
public class Movie
{
[Key]
public Guid ID { get; set; }
public string Name { get; set; }
public byte[] Hash { get; set; }
public int GenreID{ get; set; }
[ForeignKey("GenreID")]
public virtual Genre genre{ get; set; }
}
and
public class Genre
{
public int ID { get; set; }
public string Name { get; set; }
}
Now, in an import sequence I want to create new movies and link the Genre with the existing entries in the Genre table or create new Genre entries if they don't exist.
Movie m = new Movie();
m.ID = Guid.NewGuid();
IndexerContext db = new IndexerContext();
var genre = db.Genre.Where(g => g.Name== genreValue).FirstOrDefault();
if(genre!= null)
{
m.GenreID= genre.GenreID;
}
else
{
genre= new Genre();
genre.Name = genreValue;
db.Genres.Add(genre);
var genreCreated= db.Genre.Where(g => g.Name== genreValue).FirstOrDefault();
m.GenreID= genreCreated.GenreID;
}
Now the problem is, it doesn't work. The last line fails because genreCreated is null.
Plus I think I must doing it wrong - it can't be that difficult in Entity Framework.
can anyone help me?
db.Genres.Add(genre);
This does not send insert statement to database - this instructs entity framework that new record should be inserted when saving changes. Genre will be saved (and created id available) after you call db.SaveChanges(); As for now, you do not have save call, so genreCreated is null.
In your situation - fix is simple, you do not need to select genreCreated from db. Just setting m.Genre to new value should do the job
Movie m = new Movie();
m.ID = Guid.NewGuid();
IndexerContext db = new IndexerContext();
var genre = db.Genre.Where(g => g.Name== genreValue).FirstOrDefault();
if(genre! = null)
{
m.GenreID = genre.GenreID;
}
else
{
genre = new Genre();
genre.Name = genreValue;
m.Genre = genre;
}
db.SaveChanges(); //m.GenreID will automatically be set to newly inserted genre
After the add statement you need to save it:
Try
genre= new Genre();
genre.Name = genreValue;
db.Genres.Add(genre);
db.SaveChanges();

Resources