Reliable Service Data Model Issue - azure-service-fabric

I'm new to Azure Service Fabric, and watched Ivan Gavryliuk's "Understanding the Programming Models of Azure Service Fabric" course on Pluralsight. I've been following along and the basic data model in the reliable service and API work as explained in the course.
However, if I increase the complexity of the data model used I hit an error.
Product.cs from the ECommerce.ProductCatelog.Model
namespace ECommerce.ProductCatalog.Model
{
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double Price { get; set; }
public int Availability { get; set; }
public Supplier Suppliers { get; set; }
}
public class Supplier
{
public Guid Id { get; set; }
public string Name { get; set; }
}
}
ApiProduct.cs from ECommerce.API.Model
public class ApiProduct
{
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("price")]
public double Price { get; set; }
[JsonProperty("isAvailable")]
public bool IsAvailable { get; set; }
[JsonProperty("suppliers")]
public ApiSupplier suppliers { get; set; }
}
public class ApiSupplier
{
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
ProductController.cs from Ecommerce.API.Controlers
[HttpGet]
public async Task<IEnumerable<ApiProduct>> GetAsync()
{
IEnumerable<Product> allProducts = await _service.GetAllProductsAsync();
return allProducts.Select(p => new ApiProduct
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
Price = p.Price,
IsAvailable = p.Availability > 0,
suppliers = p.Suppliers
});
}
The last line in the above block triggers an intellisense error:
"Cannot implicitly convert type 'ECommerce.ProductCatelog.Model.Supplier' to 'ECommerce.API.Model.Supplier'"
Any suggestions on how to work around this welcome :)
Cheers,
Adam

Your problem is not specific to Service Fabric but C# in general. You are trying to set a variable with a value of a different type.
In this line:
IEnumerable<Product> allProducts = await _service.GetAllProductsAsync();
You get a collection of items of type ECommerce.ProductCatalog.Model.Product. In this class, you added the property Suppliers (which should be Supplier since it's not a collection) of type ECommerce.ProductCatalog.Model.Supplier.
Now, with the following line:
return allProducts.Select(p => new ApiProduct
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
Price = p.Price,
IsAvailable = p.Availability > 0,
suppliers = p.Suppliers
});
you are converting this collection to a collection of a new type ECommerce.API.Model.Product, to which you added a new property Suppliers of type ECommerce.API.Model.Supplier, but you set this property to the original value, without converting it. So, convert the original Suppliers property to the correct type:
return allProducts.Select(p => new ApiProduct
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
Price = p.Price,
IsAvailable = p.Availability > 0,
suppliers = new ApiSupplier
{
Id = p.Suppliers.Id,
Name = p.Suppliers.Name
}
});
Update: make Suppliers a collection
Make your Suppliers property a collection both in your data model and in your Api model:
public Collection<ApiSupplier> suppliers { get; set; }
Then convert the collection accordingly:
return allProducts.Select(p => new ApiProduct
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
Price = p.Price,
IsAvailable = p.Availability > 0,
suppliers = p.Suppliers.Select(s => new ApiSupplier
{
Id = s.Id,
Name = s.Name
}
});

Related

EF Core 3.0 Select Projection with index overload (aka .Select((entity, index) => new {}) fails

I have current setup, with a Select indexer-projection (entity, index) (see SubRubrics). If i leave the indexer out, the problem is solved... However if I leave out the SubRubricItems then I can use the indexer. Is it only on the last select projection I can use it, or..?
Below linq projection, error message and more info.
await _db
.Exams
.AsNoTracking()
.Include(exam => exam.Stations)
.ThenInclude(station => station.Rubrics)
.ThenInclude(rubric => rubric.SubRubrics)
.ThenInclude(subRubric => subRubric.Items)
.Select(exam => new Result.ExamViewModel
{
Id = exam.Id,
Name = exam.Name,
Stations = exam.Stations.Select(station => new Result.StationViewModel
{
Id = station.Id,
Description = station.Description,
Rubrics = station.Rubrics.Select(rubric => new Result.RubricViewModel
{
Id = rubric.Id,
Name = rubric.Name,
Info = rubric.Info,
SubRubrics = rubric.SubRubrics.Select((subRubric, index) => new Result.SubRubricViewModel
{
Id = subRubric.Id,
Order = index,
Name = subRubric.Name,
Info = subRubric.Info,
Type = subRubric.Type.ToString(),
Items = subRubric.Items.Select(item => new Result.SubRubricItemViewModel
{
Id = item.Id,
Name = item.Name
})
})
})
})
})
.ToListAsync()
This provides this error which I don't understand :/
InvalidOperationException: Processing of the LINQ expression '(MaterializeCollectionNavigation(
navigation: Navigation: Rubric.SubRubrics,
subquery: (NavigationExpansionExpression
Source: DbSet<SubRubric>
.Where(s0 => !(s0.IsDeleted))
.Where(s0 => EF.Property<Nullable<long>>(r, "Id") != null && EF.Property<Nullable<long>>(r, "Id") == EF.Property<Nullable<long>>(s0, "RubricId"))
PendingSelector: s0 => (NavigationTreeExpression
Value: (EntityReference: SubRubric | IncludePaths: Items)
Expression: s0)
)
.Where(i => EF.Property<Nullable<long>>((NavigationTreeExpression
Value: (EntityReference: Rubric | IncludePaths: Version SubRubrics->...)
Expression: r), "Id") != null && EF.Property<Nullable<long>>((NavigationTreeExpression
Value: (EntityReference: Rubric | IncludePaths: Version SubRubrics->...)
Expression: r), "Id") == EF.Property<Nullable<long>>(i, "RubricId")))
.AsQueryable()
.Select((subRubric, index) => new SubRubricViewModel{
Id = subRubric.Id,
Order = index,
Name = subRubric.Name,
Info = subRubric.Info,
Type = subRubric.Type.ToString(),
Items = subRubric.Items
.AsQueryable()
.Select(item => new SubRubricItemViewModel{
Id = item.Id,
Name = item.Name
}
)
}
)' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
This used to work, until I added the extra SubRubricItems select for the Items model, aka
Items = subRubric.Items.Select(item => new Result.SubRubricItemViewModel
{
Id = item.Id,
Name = item.Name
})
For reference sake, this is the viewmodel that's being projected into:
public sealed class Result
{
public IEnumerable<ExamViewModel> Exams { get; set; }
public sealed class ExamViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public IEnumerable<StationViewModel> Stations { get; set; }
}
public sealed class StationViewModel
{
public long Id { get; set; }
public string Description { get; set; }
public IEnumerable<RubricViewModel> Rubrics { get; set; }
}
public sealed class RubricViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public string Info { get; set; }
public IEnumerable<SubRubricViewModel> SubRubrics { get; set; }
}
public sealed class SubRubricViewModel
{
public long Id { get; set; }
public int Order { get; set; }
public string Name { get; set; }
public string Info { get; set; }
public string Type { get; set; }
public IEnumerable<SubRubricItemViewModel> Items { get; set; }
}
public sealed class SubRubricItemViewModel
{
public long Id { get; set; }
public int Order { get; set; }
public string Name { get; set; }
public string Info { get; set; }
public string Type { get; set; }
}
}
That can't be translated to SQL. So either run the SQL query before the .Select(),
.ThenInclude(subRubric => subRubric.Items)
.AsEnumerable()
.Select(exam => new Result.ExamViewModel
or remove the Includes (they don't do anything when you have a custom projection, and thereby change the query)
SubRubrics = rubric.SubRubrics.Select((subRubric) => new Result.SubRubricViewModel
{
Id = subRubric.Id,
Order = 0, . . .
and fill in the Order property on the view models afterwards.

How to bring name from another context by using foreign key?

I'm tying to write an EntityFramework query to bring hospital name by hospital ID from Hospitals Context to Departments context.I tried couple of things like join tables etc. but I couldn't complete to write that correct query.Here my models and context below
Models
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
}
Context
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public DbSet<Hospital> Hospitals { get; set; }
public DbSet<Department> Departments { get; set; }
}
Above you can see that model Department has HospitalId to connect Hospital table.After join I want to get that Hospital Name where department belongs to.Result should be department ID,department Name and its Hospital Name .
My Final Try
public async Task<IEnumerable<Department>> GetDepartment(string input)
{
var departmentWithHospital = _context.Departments
.Where(d => d.Hospital.Id == d.HospitalId)
.Include(d => d.Hospital)
.Select(d => new {
departmentId = d.Id,
departmentName = d.Name,
hospitalName = d.Hospital.Name
});
return await departmentWithHospital;
// Compiler Error:doesnt contain a definition for GetAwaiter and no
//accesible extension for GetAwaiter....
}
Three points to note:
1.The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. like below:
var hospital =await _context.Hospitals.ToListAsync();
return hospital;
2.The relationships between Hospital and Department is one-to-many , you could refer to Relationships to design your model as follows:
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
public Hospital Hospital { get; set; }
}
3.You want to return a new object list which contains department ID,department Name and its Hospital Name, but your return type of the method is IEnumerable<Department> .So you could directly return a Department collection or define a ViewModel with the properties you want
Return type :IEnumerable<Department>
var departmentWithHospital =await _context.Departments
.Include(d => d.Hospital)
.Where(d => d.HospitalId == hospitalId).ToListAsync();
return departmentWithHospital;
DepartmentWithHospital ViewModel
public class DepartmentWithHospital
{
public int departmentId { get; set; }
public string departmentName { get; set; }
public string hospitalName { get; set; }
}
public async Task<IEnumerable<DepartmentWithHospital>> GetDepartment(int hospitalId)
{
var departmentWithHospital =await _context.Departments
.Include(d => d.Hospital)
.Where(d => d.HospitalId == hospitalId)
.Select(d => new DepartmentWithHospital
{
departmentId = d.Id,
departmentName = d.Name,
hospitalName = d.Hospital.Name
}).ToListAsync();
return departmentWithHospital;
}
You need a Hospital in your Departments class, and a collection of Departments in your Hospital class.
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public virtual ICollection<Department> Departments { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
public Hospital Hospital { get; set; }
}
For the query, try this (Been awhile since I messed with EF, and this is for EF6). I can't remember if you need the include or not, but this should get you an anonymous object with the properties you requested.
This code is not tested.
var departmentWithHospital = context.Departments
.Where(d => d.Hospital.Id == hospitalId)
.Include(d => d.Hospital)
.Select(d => new {
departmentId = d.Id,
departmentName = d.DepartmentName,
hospitalName = d.Hospital.HospitalName
})
.ToList();
If I understood your question correctly, you are looking for this:
var departmentId = "123";
var result = from department in _context.Departments
join hospital in _context.Hospitals
on hospital.Id equals department.HospitalId
where department.Id == departmentId
select new
{
DepartmentID = departmentId,
DepartmentName = department.Name,
HospitalName = hospital.Name
};

Entity framework 6 use parentid in where clause with lambda expression

I'm new to EF and want to get an entry from my database (SQLite) in the following way:
Classes:
public class Customer
{
public Guid Id { get; set; }
public List<Month> Months { get; } = new List<Month>();
}
public class Month
{
public int Id { get; set; }
public string CacheId { get; set; }
public string Data { get; set; }
[Required]
public Customer Customer { get; set; }
}
DBContext:
public DbSet<Customer> Customers { get; set; }
public DbSet<Month> Months { get; set; }
Usage:
using (var context = new CustomerContext())
{
var customer = context.Customers.First();
context.Database.Log = Console.WriteLine;
var shouldContainOneEntry = context.Months.Where(x => x.Customer.Id == customer.Id).ToList();
}
shouldContainOneEntry is emtpy, but a test with a delegate and a static variable instead of the lambda expression worked:
private static Guid staticGuid;
public static bool DelegateTest(Month x)
{
return staticGuid == x.Customer.Id;
}
...
staticGuid = customer.Id;
var workingVersion = context.Months.Where(DelegateTest).ToList();
The generated SQL looks correct:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[CacheId] AS [CacheId],
[Extent1].[Data] AS [Data],
[Extent1].[Customer_Id] AS [Customer_Id]
FROM [Months] AS [Extent1]
WHERE [Extent1].[Customer_Id] = #p__linq__0
-- p__linq__0: '5cfde6e0-5b3f-437b-84c8-2845b077462d' (Type = AnsiStringFixedLength, IsNullable = false)
Why is the version with the lambda expression not working?
The solution was found by following the hints of IvanStoev.
SQLite stores the Guid by default in a binary format.
Therefor a query like
SELECT * FROM Months WHERE CustomerId = '5cfde6e0-5b3f-437b-84c8-2845b077462d'
delivers an empty result.
Using SQLite Adminstrator the Guid is shown with the binary format.
Using the server explorer in VS the Guid is shown with a string format which lead me to the wrong conclusion that this should work.
After setting the option BinaryGUID of the connection string to false the code works fine.
Based on your declarations
public class Customer
{
public Guid Id { get; set; }
public List<Month> Months { get; } = new List<Month>();
}
public class Month
{
public int Id { get; set; }
public Guid CustomerId{get;set;} // you need to add the foreign Key of the customer ( i will suppose that your foreign key in db called CustomerId
public string CacheId { get; set; }
public string Data { get; set; }
[Required]
[ForeignKey("CustomerId")] // and you need to tell EF which column is the foreign key
public Customer Customer { get; set; }
}
how to use it now
using (var context = new CustomerContext())
{
var customer = context.Customers.First();
context.Database.Log = Console.WriteLine;
var shouldContainOneEntry = context.Months.Where(x => x.CustomerId == customer.Id).ToList();
}
hope it will help you

Join multiple one to many related tables in EF and select as view model

Database Models of my Application are:
public class Restaurant
{
public int Id { get; set; }
.........
}
public class Review
{
public int Id { get; set; }
public string ReviewTitle { get; set; }
public string ReviewContent { get; set; }
public int UserId { get; set; }
public int RestaurantId { get; set; }
}
public ReviewHelpful
{
public int Id { get; set; }
public int ReviewId { get; set; }
public bool IsHelpfull { get; set; }
}
public ReviewImage
{
public int Id { get; set; }
public string ImageLink { get; set; }
public int ReviewId { get; set; }
}
There is no navigation property in any table. In ReviewHelpful table, If user finds helpfull of this review than value is true otherwise false.
Now I want to create a view-model Like this:
public class ReviewViewModel
{
public int ReviewId { get; set; }
public int RestaurantId { get; set; }
public string ReviewTitle { get; set; }
public string ReviewContent { get; set; }
public int UserId { get; set; }
public int NumberOfHelpfull { get; set; }
public int NumberOfNotHelpfull { get; set; }
public List<string> ImagesLinks { get; set; }
}
For that reason, I want to write this kind of query :
var reviews = (from review in _foodTrackerContext.RestaurantReviews
join helpful in _foodTrackerContext.Helpfuls on review.Id equals helpful.ReviewId
join reviewPicture in _foodTrackerContext.ReviewPictures on review.Id equals reviewPicture.ReviewId
where review.ResturantId == 2
select new ReviewViewModel()
{
Id = review.Id,
RestaurantId = 2,
ReviewTitle = review.ReviewTitle,
ReviewContent = review.ReviewContent,
NumberOfHelpfull = .. ??,
NumberOfNotHelpfull = ... ??,
ImagesLinks = ... ???
}
I can not retrieve HelpfulYes, HelpfulNo, ImagesLinks with this query. What would be query for finding these variables?.
This query produces multiple rows for single review with each ReviewImage and each ReviewHelpful.
The query that ypu need to do is this one:
var model =
from review in ctx.Reviews
where review.RestaurantId == 2
join helpful in ctx.ReviewHelpfuls
on review.Id equals helpful.ReviewId into helpfuls
join image in ctx.ReviewImages
on review.Id equals image.ReviewId into images
select new RestaurantReviewViewModel
{
Id = review.Id,
RestaurantId = 2,
ReviewTitle = review.ReviewTitle,
ReviewContent = review.ReviewContent,
NumberOfHelpfull = helpfuls.Count(h => h.IsHelpfull),
NumberOfNotHelpfull = helpfuls.Count(h => !h.IsHelpfull),
ImagesLinks = (from image in images select image.ImageLink).ToList()
};
Please, note that when you do a one to manyh join you need to include an into to give a nameto the joined entities to be able to work on them.
I've used the dot syntax for selecting the count, but you could use the query syntax if you wanted. Over time, I've found dot synatx more natural.
NOTE: if you used navigation properties this would become much easier. Why are you not using them? With navigation properties you don't need to make the joins explicitly, as they are already available.
List<ReviewViewModel> listModel = new List<ReviewViewModel>();
context.dbRestaurant
.include("Review")
.include("Review.ReviewHelpful")
.include("Review.ReviewImage").ToList().ForEach((item) =>
{
ReviewViewModel model = new ReviewViewModel();
model.ID = item.ID
listModel.Add(model);
});

Code First Relationships

I'm having some unexpected results when using code first and I know it has to do with how EF is creating relationships but I can't figure it out. Here is what I have.
Product class, KitComponent class (hold a product, and a qty, etc)
A Product has many KitComponents
A KitComponent has many Products
End result is to show a product, and this product has some other products included as a kit. It may contain 1 of a product or 2 pieces, thats why I need a KitComponent class / table, to hold the other info.
For the longest time I struggled with the ef relationships. The database would put the ParentProductId and Parent Product into the IncludedProductID and IncludedProduct. NOW i fixed that but don't know how but the IncludedProduct field is not being populated. The IncludedProductID is now correct in database. What Am I missing so that when I pull a product and cycle through the kitCOmponents that the IncludedProduct is not null.
AND on top of that, have I correctly described the relationships in the config?
Product:
public class Product
{
public Product()
{
this.KitComponents = new HashSet<KitComponent>();
}
public int Id { get; set; }
public string PartNumber { get; set; }
public virtual ICollection<KitComponent> KitComponents { get; set; }
}
KitComponent:
public class KitComponent
{
public int Id { get; set; }
public string UOM { get; set; }
public int QTY { get; set; }
public int ParentProductId { get; set; }
public Product ParentProduct { get; set; }
public int IncludedProductId { get; set; }
public Product IncludedProduct { get; set; }
}
DBContext With fluent API code:
public class ProductContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<KitComponent> KitComponents { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().HasMany(p => p.KitComponents)
.WithRequired(k => k.ParentProduct)
.HasForeignKey(p => p.ParentProductId);
modelBuilder.Entity<KitComponent>().HasRequired(k => k.IncludedProduct)
.WithMany()
.HasForeignKey(k => k.IncludedProductId)
.WillCascadeOnDelete(false);
}
}
Simple Seed Method to Demo Data:
public class ProductContextInitializer : DropCreateDatabaseAlways<ProductContext>
{
private ProductContext db = new ProductContext();
protected override void Seed(ProductContext context)
{
Product prod1 = new Product { PartNumber = "P1" };
Product prod2 = new Product { PartNumber = "P2" };
Product prod3 = new Product { PartNumber = "P3" };
Product prod4 = new Product { PartNumber = "P4" };
Product prod5 = new Product { PartNumber = "P5" };
Product prod6 = new Product { PartNumber = "P6" };
Product prod7 = new Product { PartNumber = "P7" };
db.Products.Add(prod1);
db.Products.Add(prod2);
db.Products.Add(prod3);
db.Products.Add(prod4);
db.Products.Add(prod5);
//db.Products.Add(prod6);
//db.Products.Add(prod7);
db.SaveChanges();
var kitComp = new KitComponent() { IncludedProduct = prod2, QTY = 1, UOM = "EA" };
prod1.KitComponents.Add(kitComp);
db.SaveChanges();
}
}
In order for the property to load, you have to make the navigation property virtual:
public virtual Product IncludedProduct { get; set; }
All the code first conventions can be found here: https://msdn.microsoft.com/en-us/data/jj679962.aspx