I'm trying to query on a LicenseId, which is unique and lives in a doubly nested object. I want to return the entire SoftwareOrderEntity object and filter out everything except the result with the matching Id. So far nothing I have tried works due to limitations with Cosmos...
Classes:
public class SoftwareOrderEntity : IUpdateAuditable, IInsertAuditable
{
public string? OrderId { get; set; }
public SoftwareOrderEntityExternalProperties? ExternalProperties { get; set; }
}
public class SoftwareOrderEntityExternalProperties
{
public List<ProductsOnOrder>? ProductsOnOrder { get; set; }
}
public class ProductsOnOrder
{
public string? ProductId { get; set; }
public List<ProductLicenseIds>? ProductLicenseIds { get; set; }
}
public class ProductLicenseIds
{
public string? LicenseId { get; set; }
public string? AssignedEntityId { get; set; }
}
I've tried many variations of LINQ, such as...
SoftwareOrders.Where(x => x.ExternalProperties.ProductsOnOrder.Where(y => y.ProductLicenseIds.Where(z => z.LicenseId.Contains(licenseId)).Count() > 0).Count() > 0);
And
var res = from c in domainContext.SoftwareOrders
from m in c.ExternalProperties.ProductsOnOrder
from x in m.ProductLicenseIds
where x.LicenseId == licenseId
select c;
EDIT: Any() is not supported by cosmos, which is part of the issue
Related
I'm learning EF Core and making ID on the below POCO Road's property rid
public class Road
{
public int rid { get; set; }
public string rname { get; set; }
public string zip { get; set; }
},
Currently my solution is two-step:
1: adding PK
2: using ValueGeneratedOnAdd() method
modelBuilder.Entity<Road>()
.HasKey(x => x.rid);
modelBuilder.Entity<Road>()
.Property(x =>x.rid)
.ValueGeneratedOnAdd();
I want a one-step solution, how to do it?
You can make an extension method:
public static class ModelBuilderExtensions
{
public static EntityTypeBuilder<T> HasKeyWithValueGeneratedOnAdd<T>(
this EntityTypeBuilder<T> b,
Expression<Func<T, object>> expression)
where T : class
{
b.HasKey(expression);
b.Property(expression).ValueGeneratedOnAdd();
return b;
}
}
Then use it as a one-liner:
modelBuilder.Entity<Road>().HasKeyWithValueGeneratedOnAdd(x => x.rid);
public class Road
{
[Key]
public int rid { get; set; }
public string rname { get; set; }
public string zip { get; set; }
},
This the table structure I have:
#region Tables
public class WorkoutProfile
{
public WorkoutProfile()
{
WorkoutExercises = new List<WorkoutExercise>();
}
[Key]
public int ProfileId { get; set; }
public string Name { get; set; }
public int Sets { get; set; }
public int RestAfterSetInSeconds { get; set; }
public virtual User User { get; set; }
public virtual ICollection<WorkoutExercise> WorkoutExercises { get; set; }
}
public class WorkoutExercise
{
[Key]
public int WorkoutId { get; set; }
public virtual Exercise Exercise { get; set; }
public int Order { get; set; }
public int WorkoutTimeInSeconds { get; set; }
public int RestAfterInSeconds { get; set; }
}
public class Exercise
{
[Key]
public long ExerciseId { get; set; }
public string Title { get; set; }
public string Visualisation { get; set; }
public bool IsDefault { get; set; } // Is exersice should be included when user first registers
}
public class User
{
[Key]
public long UserId { get; set; }
public string Email { get; set; }
public DateTime Registered { get; set; }
}
#endregion Tables
In the repository class I run the following linq query:
return context
.WorkoutProfiles.Include(w => w.WorkoutExercises)
.Where(q => q.User.UserId == userId && q.ProfileId == profileId)
.FirstOrDefault();
and I receive the good and old "Object reference not set to an instance of an object". When examining the result, see that Exercises property in WorkoutExercises is null.
This is how the database is created using code first approach:
So, the question is: why Exercises not included in WorkoutExercises object? Do I need to include it somehow? I am using .NET Core 2
The simple answer would be no lazy loading in EFCore. Not Released yet but if you want to dabble with alpha code, its in the repository. Based on your classes there are no collections for exercises in WorkoutExcercise.
Then you need to ThenInclude(w => w.Exercises) following your Include clause since EFCore doesn't do lazy loading.
I found a solution following this post
Altered my code as following:
var top = context
.Set<WorkoutProfile>()
.Where(q => q.ProfileId == profileId && q.User.UserId == userId)
.Include(q => q.WorkoutExercises)
.SingleOrDefault();
context
.Entry(top)
.Collection(e => e.WorkoutExercises)
.Query()
.OfType<WorkoutExercise>()
.Include(e => e.Exercise)
.Load();
And it worked
I have this model:
public class RepairRequest
{
[Key]
public int Id { get; set; }
public List<RepairAction> RepairActions { get; set; }
public decimal TotalPrice => RepairActions.Sum(r => r.ActionPrice);
public string LastOperation => RepairActions.LastOrDefault().RepairOperation.Description;
}
public class RepairAction
{
[Key]
public int Id { get; set; }
public int RepairRequestId { get; set; }
public RepairRequest RepairRequest { get; set; }
public int RepairOperationId { get; set; }
public RepairOperation RepairOperation { get; set; }
public decimal ActionPrice { get; set; }
}
public class RepairOperation
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
}
I'm trying to query RepairRequests and get TotalPrice and also LastOperation in a List but doesn't work for both properties. This is what I have tried till now:
using (var context = new ServiceManagerContext(new DbContextOptions<ServiceManagerContext>())) {
var data = context.RepairRequests
.Include(r => r.RepairActions).ThenInclude(r => r.RepairOperation); // Only LastAction works
//.Include("RepairActions").Include("RepairActions.RepairOperation"); // Only LastAction works
//.Include(r => r.RepairActions); // Only TotalPrice works
//.Include("RepairActions"); // Only TotalPrice works
var repairRequest = data.FirstOrDefault(r => r.Id == 5);
Assert.NotNull(repairRequest);
Assert.Equal(60.0m, repairRequest.RepairPrice);
Assert.Equal("Παραδόθηκε", repairRequest.LastAction);
}
Thank you.
I'd consider avoiding attempting to resolve calculated properties in your domain entities and instead look to resolve those when querying the data to populate view models.
If your view model needs the TotalPrice and LastOperation, then provided a Repository or such returning IQueryable you can expand the query to return what is needed using deferred execution rather than attempting to rely on eager loading the entire tree:
I.e.
IQueryable<RepairRequest> requests = context.RepairRequests.Where(x => x.Id == 5); // Or pull from a Repository returning the IQueryable
var viewModelData = requests.Select(x => new {x.Id, TotalPrice = x.RepairActions.Sum(), LastOperation = x.RepairActions.LastOrDefault()?.RepairOperation?.Description }).SingleOrDefault();
This should execute a more optimized query and return you an anonymous type with just the data you need to populate whatever view model you want to display. The iffy bit is around situations where there are no repair actions, or a repair action without an operation.. EF should avoid the null ref and just return null. the ?. syntax may not be necessary or supported, so it may just need to be ".". Using a method where you eager or lazy load those related entities and execute Linq off the entity instances, be careful around .SingleOrDefault() and drilling down into child fields.
Firstaball you have to declare Foreign Keys, and flag virtual properties like :
public class RepairRequest
{
[Key]
public int Id { get; set; }
public virtual ICollection<RepairAction> RepairActions { get; set; }
public decimal TotalPrice => RepairActions.Sum(r => r.ActionPrice);
public string LastOperation => RepairActions.LastOrDefault().RepairOperation.Description;
}
public class RepairAction
{
[Key]
public int Id { get; set; }
public decimal ActionPrice { get; set; }
public int RepairRequestId { get; set; }
[ForeignKey("RepairRequestId ")]
public virtual RepairRequest RepairRequest { get; set; }
public int RepairOperationId { get; set; }
[ForeignKey("RepairOperationId")]
public RepairOperation RepairOperation { get; set; }
}
Then you could call this, which load all children values :
var data = context.RepairRequests.Include("RepairActions.RepairOperation");
I really think I am missing something here that's probably really simple that's not jumping out at me.
I have these objects and I am trying to join a parent object to a child collection but not necessarily using the parent's primary key. In sql I can do this pretty easily, but it's bugging me why this cannot happen using code first. I am trying to join CompetitorMatchInformation to BrandSkuPricing by the ErpSkuId.
public class CompetitorMatchInformation {
[Key(), Column("MatchId")]
public long MatchId { get; set; }
[Column("ErpSkuId")]
public int? ErpSkuId { get; set; }
[Column("CompetitorId")]
public int CompetitorId { get; set; }
[ForeignKey("CompetitorId")]
public virtual Competitors Competitor { get; set; }
[ForeignKey("CompetitorItemToErpSkuMatchId")]
//[ForeignKey("ErpSkuId")]
public virtual List<BrandSkuPricing> BrandSkuPricing { get; set; }
}
public class Competitors
{
[Key(), Column("CompetitorId")]
public int CompetitorId { get; set; }
[Column("CompetitorName")]
public string CompetitorName { get; set; }
}
public class BrandSkuPricing
{
[Key(), Column("BrandSkuId")]
public int BrandSkuId { get; set; }
[Column("CompetitorItemToErpSkuMatchId")]
public long CompetitorItemToErpSkuMatchId { get; set; }
[Column("ErpSkuId")]
public int? ErpSkuId { get; set; }
[Column("Price")]
public decimal? Price { get; set; }
[Column("BrandId")]
public int? BrandId { get; set; }
[Column("BrandSourceSytemId")]
public string BrandSourceSytemId { get; set; }
[Column("BrandName")]
public string BrandName { get; set; }
[Column("BrandSkuNumber")]
public string BrandSkuNumber { get; set; }
}
The Competitor comes over correctly, but the child collection not so much. This isn't a normal scenario I know, but the underlying view for BrandSkuPricing has a relationship that's not entirely normal.
The query I am using is
public List<CompetitorMatchInformation> GetCompetitorMatchInfoByCompetitorItemId(long competitorItemId, int? brandId = null)
{
var query = this.Entity.Include(x => x.CurrentChallenges).Include(x => x.BrandSkuPricing);
var list = query.Where(x => x.CompetitorItemId == competitorItemId &&
((x.CurrentChallenges.Count > 0 && x.CurrentChallenges.Any(w => !w.IsResolved)) ||
x.CurrentChallenges.Count == 0))
.ToList();
list.ForEach(l =>
{
if (brandId.HasValue)
{
l.BrandSkuPricing = l.BrandSkuPricing.Where(x => x.BrandId == brandId).ToList();
}
});
return list;
}
And in the model builder, I have nothing. I have tried but cannot get it to work even in the builder. Anyway I can get the child collection to join on ErpSkuId? I have changed the underlying view to pull in the CompetitorItemToErpSkuMatchId so it working that way, but this scenario of joining on something that isn't a key will come up for me a lot soon.
Thanks!
I have two tables which have primary and foriegn key concept. I want to get the combined data on behalf of those keys. i don't know how to bind both the table into single model and display it into view.
Model
public class TVSerialModel
{
public Int32 Serial_ID { get; set; } // primary key
public string Serial_Name { get; set; }
public int? Release_Year { get; set; }
}
public class TVSerialEpisodeModel
{
public Int64 Video_ID { get; set; }
public Int32 Serial_ID { get; set; }// foriegn key
public string Episode_Name { get; set; }
public string Description { get; set; }
public DateTime Uploaded_Time { get; set; }
}
public class TVSerial_Episode_VM
{
public IEnumerable<TVSerialEpisodeModel> tvserialEpisode { get; set; }
public IEnumerable<TVSerialModel> Tvserial { get; set; }
}
Controller
public ActionResult NewEpisodeReleased()
{
cDBContext tvContext = new cDBContext();
TVSerial_Episode_VM tves=new TVSerial_Episode_VM();
tves= tvContext.dbTvSerialEpisodes.
Join(tvContext.dbTvSerials, p => p.Serial_ID, r => r.Serial_ID,(p, r) => new { p, r }).
Select(o => new TVSerial_Episode_VM
{ ****what should i write here to get all columns from both table**** }).
Take(9).ToList();
return View(tves);
}
Expected Result
If TVSerialEpisode has a property TVSerial, you can just dot through your foreign keys.
cDBContext.dbTvSerialEpisode
.Select(t =>
new {
t.TVSerial.Serial_ID,
t.TVSerial.Serial_Name,
t.Episode_Name
})
.Take(9)
.ToList();
You need to improve little bit the models you used with EF. You must include the reference object in model.
Like this
public virtual TVSerialModel TVSerialModel { get; set; }
in main table. This way you can select referred table too.
EF Include
public ActionResult NewEpisodeReleased()
{
cDBContext tvContext = new cDBContext();
TVSerial_Episode_VM tves=new TVSerial_Episode_VM();
tves= tvContext.dbTvSerialEpisodes.Include("TVSerialEpisodeModel")
.Include("TVSerialModel").ToList();
return View(tves);
}