duplicate entries error from the database - grails

I have something like the following domain-structure (it's a legacy database!):
class Contract implements Serializable {
int type
...
hasMany = [attachments: Attachment]
static mapping = {
attachments cascade: "all-delete-orphan"
}
}
class Attachment implements Serializable {
static belongsTo = [contract: Contract]
int attNo
String name
static mapping = {
id generator: 'assigned', composite: ['contract', 'attNo']
}
}
The contract has - depending on it's type - a few default attachments which should be deleted and recreated if the type changes.
For this I created a service which is called just before saving the edited contract. (The service doesn't have the #Transactional annotation, to have it running in the same transaction than the save/update of the contract.)
class AttachmentService {
def removeAttRecords(int contractId) {
Contract contract = Contract.get(contractId)
def records = contract.getAttachments()
for (int i = 0; i < records.size(); i++) {
Attachment record = records[i]
contract.removeFromAttachments(record)
}
}
def insertAttRecord(int contractId, String name) {
def contract = Contract.get(contractId)
def records = contract.attachments
int maxNo = 0
for (attachment record: records) {
if (record.attNo > maxNo)
maxNo = record.attNo
}
Attachment att = new Attachment()
att.contract = contract
att.attNo = maxNo +1
att.name = name
att.validate()
att.save(insert: true, flush: true, failOnError: true)
}
def createDefaultRecords(int newType, int contractId) {
if (newType == 1) {
removeAttRecords(contractId)
} else if (newType == 2) {
removeAttRecords(contractId)
insertAttRecord(contractId, "Attachment XYZ")
insertAttRecord(contractId, "Attachment ABC")
} else if (newType == 3) {
removeAttRecords(contractId)
insertAttRecord(contractId, "Attachment DEF")
}
}
}
My problem is now, that I get a duplicate entries error from the database in the insertAttRecord->att.save call, if I call the createDefaultRecords method in my service. Because of the deletion and the recreation.
I currently have no idea, how I could work around it.

You're using id generator: 'assigned' for the Attachment class. That means you have to set its id (the primary key) yourself. It looks like since you're not assigning it the new entries will all have the same id, hence the duplicate entries failure.

I worked around my problem with the following code:
class AttachmentService {
def removeAttRecords(int contractId) {
Attachment.executeUpdate("delete from Attachment att where att.contract.id = ?", [contractId])
}
def insertAttRecord(int contractId, int nextMaxNo, String name) {
Attachment att = new Attachment()
att.contract = Contract.get(contractId)
att.attNo = nextMaxNo
att.name = name
att.validate()
att.save(insert: true, flush: true, failOnError: true)
}
def createDefaultRecords(int newType, int contractId) {
def result = Attachment.executeQuery("select max(att.attNo) from Attachment att where att.contract.id = ?", contractId)
if (result[0] == null) result[0] = 0;
int currMaxNo = result[0]
if (newType == 1) {
removeAttRecords(contractId)
} else if (newType == 2) {
removeAttRecords(contractId)
insertAttRecord(contractId, currMaxNo +1, "Attachment XYZ")
insertAttRecord(contractId, currMaxNo +2, "Attachment ABC")
} else if (newType == 3) {
removeAttRecords(contractId)
insertAttRecord(contractId, currMaxNo +1, "Attachment DEF")
}
}
}
After discussing with the db-designer, it would be ok to handle that in this way.
(but I'd still like to know, why it wasn't working, even with a session-flush... :-)

Related

Null Reference exception was unhandled by user code and item is null

This working but all of a sudden it stopped working and was throwing null reference exception, when i checked in my basket record was inserted but the item is still null: the error is around BasketItem item = basket.BasketItems.FirstOrDefault(i => i.product_Id == product_Id);
using my_eCommerce.Contracts.Repositories;
using my_eCommerce.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace my_eCommerce.Services
{
public class BasketService
{
IRepositoryBase<Basket> baskets;
public const string BasketSessionName = "eCommerceBasket";
public BasketService(IRepositoryBase<Basket> baskets)
{
this.baskets = baskets;
}
private Basket CreateNewBasket(HttpContextBase httpContext)
{
//create a new basket
//
//ffirst ceate a new cookie
HttpCookie cookie = new HttpCookie(BasketSessionName);
// Now create a new Basket and set the creation date
Basket basket = new Basket();
//basket.BasketId = Guid.NewGuid(); //basket.BasketId = Guid.NewGuid() is supposed to come after date;
basket.Basket_Id = Guid.NewGuid();
basket.date = DateTime.Now;
baskets.Insert(basket);
baskets.Commit();
//Add basketId to a cookie
cookie.Value = basket.Basket_Id.ToString();
cookie.Expires = DateTime.Now.AddDays(1);
httpContext.Response.Cookies.Add(cookie);
return basket;
}
public bool AddToBasket(HttpContextBase httpContext, int product_Id, int quantity)
{
bool succcess = true;
Basket basket = GetBasket(httpContext);
//BasketItem item = basket.BasketItems.FirstOrDefault(i => i.product_Id == product_Id);
BasketItem item = basket.BasketItems.FirstOrDefault(i => i.product_Id == product_Id);
if (item == null)
{
item = new BasketItem()
{
Basket_Id = basket.Basket_Id,
product_Id = product_Id,
Quantity = quantity
};
basket.BasketItems.Add(item);
}
else
{
item.Quantity = item.Quantity + quantity;
}
baskets.Commit();
return succcess;
}
public Basket GetBasket(HttpContextBase httpContext)
{
HttpCookie cookie = httpContext.Request.Cookies.Get(BasketSessionName);
Basket basket;
Guid basket_Id;
if (cookie != null)
{
if (Guid.TryParse(cookie.Value, out basket_Id))
{
basket = baskets.GetById(basket_Id);
}
else
{
basket = CreateNewBasket(httpContext);
}
}
else
{
basket = CreateNewBasket(httpContext);
}
return basket;
}
}
}
Your basket or basket.BasketItems objects could be null in some situations. You should put Null check for this object the way you have done for the item object. Try to rewrite the code in the following way:
...
BasketItem item = null;
Basket basket = GetBasket(httpContext);
if(basket != null && basket.BasketItems != null)
{
item = basket.BasketItems.FirstOrDefault(i => i.product_Id == product_Id);
}
if (item == null)
{
...
Use Null-conditional operators ?.
You can use it like below and it will not throw exception but it will return
null if basket is null.
null if basket.BasketItems is null.
Or FirstOrDefault() value from basket.BasketItems
BasketItem item = basket?.BasketItems?.FirstOrDefault(i => i.product_Id == product_Id);

linq to entity does not recognize the method

I need to call a method FormatCourseTitle() from linq query but receive error message "Linq to Entity does not recognize the method FormatCourseTitle..." How to solve this problem?
public ActionResult Index()
{
var searchResults = (from a in db.Courses
join b in db.Summary on
new { subject = a.subject, catalog = a.catalog, coursetitle = FormatCourseTitle(a.coursetitle) } equals
new { subject = b.Subject, catalog = b.Catalogno, coursetitle = b.CourseTitle } into ab
from b in ab.DefaultIfEmpty()
where a.Active == true
select new JoinModel
{
Courses = a,
Summary2020 = b
} ).ToList();
return View(searResults);
}
public class JoinModel
{
[Key]
public int Id { get; set; }
public Courselist Courses { get; set; }
public Contentsummary Summary { get; set; }
}
public string FormatCourseTitle(string courseTitle)
{
//find if last three characters are " or"
string[] words = courseTitle.Trim().ToLower().Split(' ');
int j = words.Count();
string tempStr = string.Empty;
if (words[j] == "or")
{
tempStr = courseTitle.Substring(0, courseTitle.Length - 3);
}
else
{
tempStr = courseTitle;
}
return tempStr;
}
You should use extension method as
public static class StringHelper
{
public static string FormatCourseTitle(this string courseTitle)
{
//find if last three characters are " or"
string[] words = courseTitle.Trim().ToLower().Split(' ');
int j = words.Count();
string tempStr = string.Empty;
if (words[j] == "or")
{
tempStr = courseTitle.Substring(0, courseTitle.Length - 3);
}
else
{
tempStr = courseTitle;
}
return tempStr;
}
}
Change query to
var searchResults = (from a in db.Courses
join b in db.Summary on
new { subject = a.subject, catalog = a.catalog, coursetitle = a.coursetitle.FormatCourseTitle() } equals
new { subject = b.Subject, catalog = b.Catalogno, coursetitle = b.CourseTitle } into ab
from b in ab.DefaultIfEmpty()
where a.Active == true
select new JoinModel
{
Courses = a,
Summary2020 = b
} ).ToList();
I find a way to add condition in linq query to solve this problem.
var searchResults = (from a in db.Courses
join b in db.Summary on
new { subject = a.subject, catalog = a.catalog, coursetitle = a.coursetitle.Trim().EndsWith(" or")?a.coursetitle.Substring(0,a.coursetitle.Length-3):a.coursetitle } equals
new { subject = b.Subject, catalog = b.Catalogno, coursetitle = b.CourseTitle } into ab
from b in ab.DefaultIfEmpty()
where a.Active == true
select new JoinModel
{
Courses = a,
Summary2020 = b
} ).ToList();

ASP.NET MVC5 Generic Translation method for database entities

I have two methods for a database entity which gets the translation for selected language if it has one, or the default language translation.
public string GetName(int? LanguageId, int? DefaultLanguageId)
{
string retval = "";
var textInSelectedLanguage = this.CategoryTexts.Where(w => w.LanguageId == LanguageId).SingleOrDefault();
if (textInSelectedLanguage == null)
{
retval = this.CategoryTexts.Where(w => w.LanguageId == DefaultLanguageId).SingleOrDefault().Name;
}
else
{
retval = textInSelectedLanguage.Name;
}
return retval;
}
public string GetDescription(int? LanguageId, int? DefaultLanguageId)
{
string retval = "";
var textInSelectedLanguage = this.CategoryTexts.Where(w => w.LanguageId == LanguageId).SingleOrDefault();
if (textInSelectedLanguage == null)
{
retval = this.CategoryTexts.Where(w => w.LanguageId == DefaultLanguageId).SingleOrDefault().Description;
}
else
{
retval = textInSelectedLanguage.Description;
}
return retval;
}
As you can see they are very similar. If I have more properties to translate, this won't be a good implementation. The behavior is similar for the other translations.
How can reduce this code to one method?
I tried to use reflection but I didn't had any results.
Later...
I reduced my code to one method which return me the an entity instance with all the properties in the selected language or default language:
public CategoryText GetTranslation(int? DesiredLanguageId, int? DefaultLanguageId)
{
CategoryText retval = null;
var textInSelectedLanguage = this.CategoryTexts.Where(w => w.LanguageId == DesiredLanguageId).SingleOrDefault();
if (textInSelectedLanguage == null)
{
retval = this.CategoryTexts.Where(w => w.LanguageId == DefaultLanguageId).SingleOrDefault();
}
else
{
retval = textInSelectedLanguage;
}
return retval;
}
I think this method can be easily made generic by trying to find a way to replace my CategoryTexts Dbset with any other DbSet database entity. How can I do this?
Assuming you have an appropriate base class you could write it:-
public T GetTranslation<T>(DbSet<T> set, int? DesiredLanguageId, int? DefaultLanguageId)
where T:SomeBaseClassThatHasPropertyLanguageId
{
return
set.SingleOrDefault(w => w.LanguageId == DesiredLanguageId) ??
set.SingleOrDefault(w => w.LanguageId == DefaultLanguageId);
}
I somehow solved this.
I have an Internationalization class which with the following method:
public static T GetDbEntityTranslation<T>(ITranslatable Entity)
{
return (T)Entity.GetTranslation<T>(GetDefaultLanguage().Id, GetChosenLanguage().Id);
}
The ITranslatable interface:
public interface ITranslatable
{
T GetTranslation<T>(int? DesiredLanguageId, int? DefaultLanguageId);
}
My category class:
public partial class Category : ITranslatable
{
private LSEntities db = new LSEntities();
public T GetTranslation<T>(int? DesiredLanguageId, int? DefaultLanguageId)
{
CategoryText retval = null;
retval = db.CategoryTexts.Where(w => w.LanguageId == DesiredLanguageId && w.CategoryId == this.Id).SingleOrDefault()
?? this.CategoryTexts.Where(w => w.LanguageId == DefaultLanguageId && w.CategoryId == this.Id).SingleOrDefault()
?? this.CategoryTexts.Where(w => w.CategoryId == this.Id).FirstOrDefault();
if (retval == null)
throw new Exception("No translation found for this object");
return (T)(object)retval;
}
}
The problem was that I need to get my translation based on my category ID which is stored in Category class. My translations are searched in CategoryTexts property.

How can I update related tables?

My project involves creating a new hotel room and 2 tables in my database will update. My tables are called RoomType and RoomFacility.
I can successfully update RoomType, but when I try to update RoomFacility and use RoomTypeID to make a new room facility, it fails. I always get 1 for my RoomFacilityID.
How can I update data for both tables, roomType and RoomFacility?
This is the code for my service to update my database
public void UpdateFacilityInRooms(List<int> FacilityIDs, int RoomTypeID)
{
List<HotelRoomFacility> hotelRoomFacilities =
_HotelRoomFacilityRopository.AsQueryable()
.Where(f => f.RoomTypeID == RoomTypeID).ToList();
foreach (int newRoomFacility in FacilityIDs)
{
if (hotelRoomFacilities.Where(h => h.RoomFacilityID == newRoomFacility).Count() == 0)
{
HotelRoomFacility facility = new HotelRoomFacility
{
RoomFacilityID = newRoomFacility,
RoomTypeID = RoomTypeID
};
_HotelRoomFacilityRopository.Add(facility);
}
}
_HotelRoomFacilityRopository.CommitChanges();
}
public RoomType NewRoom(int HotelID,int? RoomTypeID,
string RoomTypeName, string RoomTypeDescription)
{
RoomType room = new RoomType();
room.HotelID = HotelID;
room.RoomTypeID = RoomTypeID ?? 0;
room.RoomtypeName = RoomTypeName;
room.RoomTypeDescripton = RoomTypeDescription;
_RoomTypeRepository.Add(room);
_RoomTypeRepository.CommitChanges();
return room;
}
public RoomType UpdateRoom(int RoomTypeID, string RoomTypeName, string RoomTypeDescription, List<int> RoomFacilityIDs)
{
RoomType roomType = (from rt in _RoomTypeRepository.AsQueryable().Include(r => r.HotelRoomFacilities)
where rt.RoomTypeID == RoomTypeID
select rt).FirstOrDefault();
if (roomType == null)
return null;
roomType.RoomTypeName = RoomTypeName;
roomType.RoomTypeDescripton = RoomTypeDescription;
//Add New Room facilities
List<HotelRoomFacility> hotelRoomFacilities = _HotelRoomFacilityRopository.AsQueryable().Where(f => f.RoomTypeID == RoomTypeID).ToList();
foreach (int newRoomFacilityID in RoomFacilityIDs)
{
if (roomType.HotelRoomFacilities.Where(h => h.RoomFacilityID == newRoomFacilityID).Count() == 0)
{
roomType.HotelRoomFacilities.Add(new HotelRoomFacility
{
RoomFacilityID = newRoomFacilityID
});
}
}
foreach (HotelRoomFacility roomFacility in hotelRoomFacilities)
{
if (RoomFacilityIDs.Contains(roomFacility.RoomFacilityID) == false)
_HotelRoomFacilityRopository.Delete(roomFacility);
}
_RoomTypeRepository.Attach(roomType);
_RoomTypeRepository.CommitChanges();
return roomType;
}

Call namedQuery inside a criteria in controller

Is possible to call namedQuery on grails inside a controller? I know that I can call a namedQuery inside another namedQuery, but i dont want to do that. Any ideas? Thanks
User.groovy
static namedQueries = {
filterUsers{
eq("age", 21)
}
}
MyController.groovy
def r = User.createCriteria().list {
eq("id", 1)
filterUsers() //not possible
}
or..
MyController.groovy
//not possible too
//Cannot invoke method createCriteria() on null object
def r = User.filterUsers().createCriteria().list {
eq("id", 1)
}
Here's an example:
Domain:
class User {
int age
String userName
static namedQueries = {
filterUsers {
eq("age", 21)
}
}
static constraints = {
}
}
Controller:
class TestController {
def index = {
def users = User.filterUsers {
and {
like 'userName', 'Derek%'
}
}
render users as JSON
}
}
Also, you can find more about this here: Reference Documentation

Resources