Update Value Object fileld in Mvc code First - asp.net-core

I have a value object named Validity and its attribute StartDate and EndDate.
I have Update this field.but getting some error.The error is:
InvalidOperationException: The entity type 'Validity' has a defining navigation and the supplied entity is currently not being tracked. To start tracking this entity call '.Reference().TargetEntry' on the owner entry.
How to solve this error?
My Code Is:
public async Task<Subscription> Update(string ID, bool Active, string Plan, string Periodicity, decimal Price, string LocationTargets, string paymentStatus,
DateTime startDate, DateTime endDate, string OperationType)
{
#region PaymentStatus
int EnumPaymentStatus = 0;
string[] values = Enum.GetNames(typeof(PaymentStatusEnum.PaymentStatus));
if (values[0] == paymentStatus)
{
EnumPaymentStatus = (int)PaymentStatus.Due;
}
else if (values[1] == paymentStatus)
{
EnumPaymentStatus = (int)PaymentStatus.Open;
}
else if (values[2] == paymentStatus)
{
EnumPaymentStatus = (int)PaymentStatus.UnPaid;
}
else if (values[3] == paymentStatus)
{
EnumPaymentStatus = (int)PaymentStatus.Paid;
}
#endregion
#region Periodicity
int EnumPeriodicityValue = 0;
string[] values1 = Enum.GetNames(typeof(PeriodicityEnum.EnumPeriodicity));
if (values1[0] == Periodicity)
{
EnumPeriodicityValue = (int)EnumPeriodicity.Monthly;
}
else if (values1[1] == Periodicity)
{
EnumPeriodicityValue = (int)EnumPeriodicity.Trimestral;
}
else if (values1[2] == Periodicity)
{
EnumPeriodicityValue = (int)EnumPeriodicity.Semestral;
}
else if (values1[3] == Periodicity)
{
EnumPeriodicityValue = (int)EnumPeriodicity.Annual;
}
#endregion
#region Operation Type
int OperationTypeValue = 0;
string[] values2 = Enum.GetNames(typeof(OperationTypeEnum.EnumOperationType));
if (values2[0] == OperationType)
{
OperationTypeValue = (int)EnumOperationType.NewSubscription;
}
else if (values2[1] == OperationType)
{
OperationTypeValue = (int)EnumOperationType.SubscriptionRenewal;
}
else if (values2[2] == OperationType)
{
OperationTypeValue = (int)EnumOperationType.SubscriptionCancellation;
}
else if (values2[3] == OperationType)
{
OperationTypeValue = (int)EnumOperationType.SubscriptionChange;
}
#endregion
#region Update Data in Subscription Table
var subscription = new Subscription()
{
ID = ID,
Active = Active,
Plan = Plan,
Periodicity = Convert.ToString(EnumPeriodicityValue),
Price = Price,
LocationTargets = LocationTargets,
PaymentStatus = Convert.ToString(EnumPaymentStatus),
OperationType = Convert.ToString(OperationTypeValue),
UpdatedAt = DateTime.UtcNow
};
subscription.Validity = new Validity(startDate, endDate);
//Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<Subscription> s = await _db.AddAsync(subscription);
_db.Entry(subscription).Property(x => x.Active).IsModified = true;
_db.Entry(subscription).Property(x => x.Plan).IsModified = true;
_db.Entry(subscription).Property(x => x.Periodicity).IsModified = true;
_db.Entry(subscription).Property(x => x.Price).IsModified = true;
_db.Entry(subscription).Property(x => x.LocationTargets).IsModified = true;
_db.Entry(subscription).Property(x => x.PaymentStatus).IsModified = true;
_db.Entry(subscription.Validity).Property(x => x.StartDate).IsModified = true;
_db.Entry(subscription.Validity).Property(x => x.EndDate).IsModified = true;
_db.Entry(subscription).Property(x => x.OperationType).IsModified = true;
_db.Entry(subscription).Property(x => x.UpdatedAt).IsModified = true;
#endregion
#region Insert Data in SubscriptionHistory Table
string a = Convert.ToString(GuidComb.Generate());
var subscriptionhistory = new SubscriptionHistory()
{
ID = a,
Active = Active,
Plan = Plan,
Periodicity = Convert.ToString(EnumPeriodicityValue),
Price = Price,
LocationTargets = LocationTargets,
PaymentStatus = Convert.ToString(EnumPaymentStatus),
OperationType = Convert.ToString(OperationTypeValue)
};
subscriptionhistory.Validity = new Validity(startDate, endDate);
Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<SubscriptionHistory> y = await _db.AddAsync(subscriptionhistory);
#endregion
await _db.SaveChangesAsync();
return subscription;
}
In Subscription Class:
public class Subscription: BaseEntity
{
public Subscription()
{
this.ID = Convert.ToString(System.Guid.NewGuid());
this.CreatedAt = DateTime.UtcNow;
this.IsCancel = false;
}
private List<Validity> validities = new List<Validity>();
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[MaxLength(128)]
public string ID { get; set; }
public bool Active { get; set; }
[MaxLength(150)]
public string Plan { get; set; }
[MaxLength(101)]
public string Periodicity { get; set; }
public decimal Price { get; set; }
[MaxLength(250)]
public string LocationTargets { get; set; }
[MaxLength(101)]
public string PaymentStatus { get; set; }
public Validity Validity { get; set; }
[MaxLength(101)]
public string OperationType { get; set; }
public bool IsCancel { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime DeletedAt { get; set; }
public bool Deleted { get; set; }
//private List<Validity> validities = new List<Validity>();
public void Assignvalidity(Validity validitys)
{
this.validities.Add(validitys);
}
}
In Validity Class:
public class Validity : ValueObject
{
public DateTime StartDate { get; private set; }
public DateTime EndDate { get; private set; }
private Validity() { }
public Validity(DateTime startdate, DateTime enddate)
{
StartDate = startdate;
EndDate = enddate;
}
protected override IEnumerable<object> GetAtomicValues()
{
yield return StartDate;
yield return EndDate;
}
}

Add this line:
Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry s = _db.Update(subscription);
After this line:
subscription.Validity = new Validity(startDate, endDate);

In order to update an entity it must be tracked by the entity, in your below code you are creating new object :
var subscription = new Subscription()
{
ID = ID,
Active = Active,
Plan = Plan,
Periodicity = Convert.ToString(EnumPeriodicityValue),
Price = Price,
LocationTargets = LocationTargets,
PaymentStatus = Convert.ToString(EnumPaymentStatus),
OperationType = Convert.ToString(OperationTypeValue),
UpdatedAt = DateTime.UtcNow
};
First you need to get that entity from the db, then update related fields and save to db;
[UPDATE]
The validity is a related entity to Subscription, so you need an include query to fetch the object from the db, then you can update and save.
var subscription = _db.Set<Subscription>().Single(x=>x.ID == ID).Include(x=>x.Validity);
subscription.Validity.StartDate = startDate;
subscription.Vaklidity.EndDate = endDate;
_db.SaveChanges();
[UPDATE]
add foreignkey attributes then create a new migration then update the db:
public class Validity : ValueObject
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string SubscriptionID { get; set; }
public Subscription Subscription { get; set; }
// ...
}
public class Subscription: BaseEntity
{
public string ValidityID { get; set; }
public Validity Validity { get; set; }
// ...
}
After updating the db, get the related Validity entity and update it:
var validity = _db.Set<Validity>().Find(x => x.SubscriptionID == ID);
validity.StartDate = startDate;
validity.EndDate = endDate;
_db.SaveChanges();

Related

Retrieve values from SQL database - EF

I'm trying to figure out how to pull values from a SQL database and display this in a razor view.
I have the following class using Entity Framework (I believe)
public class EventLog
{
[Key]
public int Id { get; set; }
public int EventId { get; set; }
public int MaxDelegates { get; set; }
public string Code { get; set; }
public DateTime End { get; set; }
public string Title { get; set; }
}
And I want to map title to DBTitle in the following model:
public class CourseDetailVM : CourseDetailSummaryVM
{
public EventLog DBTitle { get; set; }
}
I then want to see this in the following view:
#using TSW.Web.Helpers
#model TSW.Web.ViewModels.CourseDetailVM
#{
Layout = "~/Views/_Master.cshtml";
}
#Model.DBTitle.Title;
I have the following controller already in place (sorry for the length I plan to reduce this down):
public class CourseDetailController : BaseRenderController<CourseDetailPageDT>
{
private readonly ISitePageFactory _pageFactory = null;
private readonly IEventService _eventService = null;
public CourseDetailController(IEventService eventService, ISitePageFactory pageFactory)
{
_pageFactory = pageFactory;
_eventService = eventService;
}
public async Task<ActionResult> CourseDetail()
{
var homepage = _pageFactory.GetCurrentHomepage();
var model = Mapper.Map<CourseDetailVM>(CurrentContent);
model.Email = homepage.ContactEmail;
model.PhoneNumber = homepage.HeaderPhoneNumber;
model.InnerPageHeader.ShowHeading = true;
model.InnerPageHeader.Title = model.PageTitle;
if (model.Categories.Count == 1)
{
var categoryTagId = model.Categories.First().Id;
var contentTypeAlias = DocumentTypeHelper.GetDocumentTypeAlias<CourseListingPageDT>();
var courseCategoryPage = Umbraco.TypedContentAtXPath($"//{contentTypeAlias}")
.FirstOrDefault(x => x.GetPropertyValue<int>(Constants.DocumentTypes.CourseListingPage.Category) == categoryTagId);
if (courseCategoryPage != null)
{
model.InnerPageHeader.BackLink = Mapper.Map<LinkItem>(courseCategoryPage.Id);
}
}
try
{
model.Events = await _eventService.GetEventsForCourse(CurrentContent.AdministrateId);
}
catch (Exception ex)
{
model.Events = new StaticPagedList<Event>(Enumerable.Empty<Event>(), 1, 1, 0);
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
if (CurrentContent.Graphic != 0)
{
model.InnerPageHeader.Graphic = Mapper.Map<CtaItem>(CurrentContent.Graphic);
}
return View(model);
}
}
I've tried every suggestion I can google to add the mapping in the controlling but can't get my head around this simple function of pulling the value from a SQL database into the razor view.
Could anyone help me out?

automatically expand related entity with OData controller

I have these classes:
public class Items
{
[Key]
public Guid Id { get; set; }
public string ItemCode { get; set; }
public decimal SalesPriceExcl { get; set; }
public decimal SalesPriceIncl { get; set; }
public virtual ICollection<ItemPrice> SalesPrices { get; set; }
public Items()
{
SalesPrices = new HashSet<App4Sales_ItemPrice>();
}
}
public class ItemPrice
{
[Key, Column(Order = 0), ForeignKey("Items")]
public Guid Id { get; set; }
public virtual Items Items { get; set; }
[Key, Column(Order=1)]
public Guid PriceList { get; set; }
public decimal PriceExcl { get; set; }
public decimal PriceIncl { get; set; }
public decimal VatPercentage { get; set; }
}
I want to query the Items and automatically get the ItemPrice collection.
I've created an OData V3 controller:
// GET: odata/Items
//[Queryable]
public IQueryable<Items> GetItems(ODataQueryOptions opts)
{
SelectExpandQueryOption expandOpts = new SelectExpandQueryOption(null, "SalesPrices", opts.Context);
Request.SetSelectExpandClause(expandOpts.SelectExpandClause);
return expandOpts.ApplyTo(db.Items.AsQueryable(), new ODataQuerySettings()) as IQueryable<Items>;
}
But I get the error:
"Cannot serialize null feed"
Yes, some Items have no ItemPrice list.
Can I get past this error, or can I do something different?
Kind regards
Jeroen
I found the underlying error is:
Unable to cast object of type
'System.Data.Entity.Infrastructure.DbQuery1[System.Web.Http.OData.Query.Expressions.SelectExpandBinder+SelectAllAndExpand1[.Models.Items]]'
to type '.Models.Items'.
I've solved it after I came across this post: http://www.jauernig-it.de/intercepting-and-post-processing-odata-queries-on-the-server/
This is my controller now:
SelectExpandQueryOption expandOpts = new SelectExpandQueryOption(null, "SalesPrices", opts.Context);
Request.SetSelectExpandClause(expandOpts.SelectExpandClause);
var result = expandOpts.ApplyTo(db.Items.AsQueryable(), new ODataQuerySettings());
var resultList = new List<Items>();
foreach (var item in result)
{
if (item is Items)
{
resultList.Add((Items)item);
}
else if (item.GetType().Name == "SelectAllAndExpand`1")
{
var entityProperty = item.GetType().GetProperty("Instance");
resultList.Add((Items)entityProperty.GetValue(item));
}
}
return resultList.AsQueryable();
Jeroen
GetItems([FromODataUri] ODataQueryOptions queryOptions)
expanding on Jeroen's post. Anytime a select or expand is involved, OData wraps the results in a SelectAll or SelectSome object; so, we need to unwrap the values rather than do an direct cast.
public static class ODataQueryOptionsExtensions
{
public static IEnumerable<T> ApplyODataOptions<T>(this IQueryable<T> query, ODataQueryOptions options) where T : class, new()
{
if (options == null)
{
return query;
}
var queryable = options.ApplyTo(query);
if (queryable is IQueryable<T> queriableEntity)
{
return queriableEntity.AsEnumerable();
}
return UnwrapAll<T>(queryable).ToList();
}
public static IEnumerable<T> UnwrapAll<T>(this IQueryable queryable) where T : class, new()
{
foreach (var item in queryable)
{
yield return Unwrap<T>(item);
}
}
public static T Unwrap<T>(object item) where T : class, new()
{
var instanceProp = item.GetType().GetProperty("Instance");
var value = (T)instanceProp.GetValue(item);
if (value != null)
{
return value;
}
value = new T();
var containerProp = item.GetType().GetProperty("Container");
var container = containerProp.GetValue(item);
if (container == null)
{
return (T)null;
}
var containerType = container.GetType();
var containerItem = container;
var allNull = true;
for (var i = 0; containerItem != null; i++)
{
var containerItemType = containerItem.GetType();
var containerItemValue = containerItemType.GetProperty("Value").GetValue(containerItem);
if (containerItemValue == null)
{
containerItem = containerType.GetProperty($"Next{i}")?.GetValue(container);
continue;
}
var containerItemName = containerItemType.GetProperty("Name").GetValue(containerItem) as string;
var expandedProp = typeof(T).GetProperty(containerItemName);
if (expandedProp.SetMethod == null)
{
containerItem = containerType.GetProperty($"Next{i}")?.GetValue(container);
continue;
}
if (containerItemValue.GetType() != typeof(string) && containerItemValue is IEnumerable containerValues)
{
var listType = typeof(List<>).MakeGenericType(expandedProp.PropertyType.GenericTypeArguments[0]);
var expandedList = (IList)Activator.CreateInstance(listType);
foreach (var expandedItem in containerValues)
{
var expandedInstanceProp = expandedItem.GetType().GetProperty("Instance");
var expandedValue = expandedInstanceProp.GetValue(expandedItem);
expandedList.Add(expandedValue);
}
expandedProp.SetValue(value, expandedList);
allNull = false;
}
else
{
var expandedInstanceProp = containerItemValue.GetType().GetProperty("Instance");
if (expandedInstanceProp == null)
{
expandedProp.SetValue(value, containerItemValue);
allNull = false;
}
else
{
var expandedValue = expandedInstanceProp.GetValue(containerItemValue);
if (expandedValue != null)
{
expandedProp.SetValue(value, expandedValue);
allNull = false;
}
else
{
var t = containerItemValue.GetType().GenericTypeArguments[0];
var wrapInfo = typeof(ODataQueryOptionsExtensions).GetMethod(nameof(Unwrap));
var wrapT = wrapInfo.MakeGenericMethod(t);
expandedValue = wrapT.Invoke(null, new[] { containerItemValue });
if (expandedValue != null)
{
expandedProp.SetValue(value, expandedValue);
allNull = false;
}
}
}
}
containerItem = containerType.GetProperty($"Next{i}")?.GetValue(container);
}
if (allNull)
{
return (T)null;
}
return value;
}
}

Take function doesn't work and cannot be sent to RavenDB for query

Query Code:
var query = session.IndexQuery<App_OrgSearch.IndexResult, App_OrgSearch>();
var organizationUnitResults = query.Statistics(out stats)
.Skip(0)
.Take(5)
.AsProjection<Org>().ToList();
public static IRavenQueryable<TResult> IndexQuery<TResult, TIndex>(this IDocumentSession session)
where TIndex : AbstractIndexCreationTask, new()
{
return session.Query<TResult, TIndex>();
}
App_OrgSearch is the index I defined as below:
public class App_OrgSearch : AbstractIndexCreationTask<Org, App_OrgSearch.IndexResult>
{
public class IndexResult
{
public string Id { get; set; }
public string BusinessName { get; set; }
public string ShortName { get; set; }
public IList<string> Names { get; set; }
public List<string> PhoneNumbers { get; set; }
public List<OrganizationUnitPhone> OrganizationUnitPhones { get; set; }
}
public App_OrganizationUnitSearch()
{
Map = docs => from doc in docs
select new
{
Id = doc.Id,
Names = new List<string>
{
doc.BusinessName,
doc.ShortName,
},
BusinessName = doc.BusinessName,
ShortName = doc.ShortName,
PhoneNumbers = doc.OrganizationUnitPhones.Where(x => x != null && x.Phone != null).Select(x => x.Phone.Number),
};
Indexes.Add(x => x.Names, FieldIndexing.Analyzed);
}
}
I have 27 records in database. I want to take 5, but after query, all 27 records are returned. Why does Take function not work?
Your sample code seems wrong.
var query = session.IndexQuery<App_OrgSearch.IndexResult, App_OrgSearch>();
var organizationUnitResults = organizationUnitsQuery.Statistics(out stats)
What is organizationUnitsQuery ? You have the query as query, but there is no IndexQuery method on the session

How to write unit test for mvc. In class a method return a list?

I need to write unit test for below code. Easy understanding of the solution. I novice for unit testing. I don't know how write. I have added unit test class successfully from solution explorer.
class 1:
public class ListModel
{
public int EmpID { get; set; }
public string EmpName {get;set;}
public string CusID {get; set;}
public string EmpType { get; set; }
public string EmpPDLC { get; set; }
public int? EmpMode { get; set; }
public int? Status { get; set; }
}
class 2:
public class EmployeeHelper : IEmployeeHelper
{
public List<ListModel> EmployeeList(int userId)
{
var lstModel = new List<ListModel>();
try
{
using (var dc = new EmployeeDataContext())
{
var lstGetTable = (from table in dc.UserTables
where table.UserID == userId && table.Status == 1
select table).ToList();
lstModel.AddRange(lstGetTable.Select(items => new ListModel
{
EmpID = items.EmpID,
EmpName = items.EmpName,
CustID = items.CustID,
EmpType = items.EmpType,
EmpPDLC = items.EmpPDLC,
EmpMode = items.EmpMode,
Status = items.Status
}));
}
}
catch (Exception)
{
return lstModel;
}
return lstModel;
}
}

RavenDB - How to merge related docs of same type into an index projection

Assuming the following class:
public class Thing
{
public string Id { get; set; }
public string Title{ get; set; }
public string KeyId { get; set; }
public CultureInfo Culture { get; set; }
}
and the following result class:
public class ProjectedThing
{
public string Id { get; set; }
public string Title{ get; set; }
public string KeyId { get; set; }
public CultureInfo Culture { get; set; }
public IEnumerable<Thing> Things { get; set; }
}
How can I build an index that holds the result class?
The closest I've come is with the following index definition:
public class ProjectedThings : AbstractIndexCreationTask<Thing,ProjectedThing>
{
public ProjectedThings()
{
Map = docs => from doc in docs
select new
{
Title = doc.Title,
KeyId = doc.KeyId,
Culture = doc.Culture,
Things = new[] {
new Thing{
Id = doc.Id,
Title = doc.Title,
KeyId = doc.KeyId,
Culture = doc.Culture,
TitlePluralized = doc.TitlePluralized
}
}
};
Reduce = results => from r in results
group r by r.KeyId into g
select new
{
Title = g.FirstOrDefault(x => x.Id == x.KeyId).Title,
KeyId = g.Key,
Culture = g.FirstOrDefault(x => x.Id == x.KeyId).Culture,
Things = from thing in g.SelectMany(x => x.Things).Where(x => x.Id != x.KeyId)
select new
{
Id = thing.Id,
Title = thing.Title,
KeyId = thing.Key,
Culture = thing.Culture
}
};
}
}
That's almost doing the trick, but I can't collect the Title, KeyId, and Culture in the reduction. Only the Things property is being populated.
Look at your code:
g.FirstOrDefault(x => x.Id == x.KeyId)
I don't understand this, but this is likely to be always false.
You probably want:
g.FirstOrDefault().Title,