Mongo C# IMongoCollection Design - c#

I am using an interface as a collection type when initializing an IMongoCollection. I am using an interface class as the collection so that it can be better for testing.
public IMongoCollection<IEmployee> Employees => Database.GetCollection<IEmployee>("employee");
public interface IEmployee
{
[BsonId]
ObjectId Id { get; set; }
string Name { get; set; }
}
[BsonDiscriminator(Required = true)]
[BsonKnownTypes(typeof(Employee))]
public class Employee : IEmployee
{
public ObjectId { get; set; }
public string Name { get; set; }
}
I have a database class of Employee which implements IEmployee. When storing to the database, I have to store a type of Employee because I can't declare a new instance of IEmployee.
var emp = new Employee
{
Id = ObjectId.GenerateNewId();
Name = "Wayne Rooney";
};
// Insert into the Employees collection
await Employees.InsertOneAsync(emp);
When I want to replace/update that document, I can't because I am querying from a lower layer class of IEmployee.
await Employees.FindOneAndReplaceAsync(f => f.Id.ToString() == context.Id, g);
context in this case is the parameter of type Employee that I am passing in. g is the update document of type Employee. f in this case is type IEmployee. When I do a replace, I get a [document].Id.ToString() is not supported error.
So the question is, I'm able to insert and retrieve them, but not able to update/replace/delete a document. Any suggestions?

First, a few heads ups.
When inserting to the database, if you have specified an ObjectId as your BsonId then there is no need to manually set it.
So set the BsonId-attribute on your Id.
public class Employee : IEmployee
{
[BsonId]
public ObjectId Id { get; set; }
public string Name { get; set; }
}
And then insert as such:
var emp = new Employee
{
Name = "Wayne Rooney";
};
// Insert into the Employees collection
await Employees.InsertOneAsync(emp);
A unique ObjectId will be set by itself upon inserting.
The problem itself most likely lies in the ToString()-part of your Linq-query. I'm not sure it translates well into a mongo-query. A quick work-around would be to just use something like:
var id = new ObjectId(context.Id);
await Employees.ReplaceOneAsync(f => f.Id == id, g);
This eliminates the ToString()-call and compares ObjectId's directly.
The support of Linq-queries are somewhat limited in the driver. I would advise against using them (there is also some overhead here).
The suggested, and most direct route is to use the built-in filters.
For instance:
var filter = Builders<IEmployee>.Filter
.Eq(nameof(Employee.Id), new ObjectId(context.Id));
await Employees.ReplaceOneAsync(filter, g);
nameof(Employee.Id) is C# 6, and will return "Id". If you do not have support for C# 6 then simply use "Id" instead.

Related

Define a LAMBDA query in a property so it can be reused

I'm trying to defined a lambda query in a property of my code first EF model as seen below as, GetLatestTransaction :
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual List<TransactionModel> Transactions { get; set; }
public TransactionModel GetLatestTransaction {
get {
return Transactions.OrderByDescending(x => x.Created).FirstOrDefault();
}
}
}
The reason for this is that I don't want to have to retype this query in many places and by having it in one place reduce the chances of a bug.
I want to use this in a query like this:
var user = _DB.Users
.Select(u => new UserDetailsView()
{
Id = u.Id,
FirstName= u.FirstName,
LastName= u.LastName,
Balance = u.GetLatestTransaction.ValueResult
}).FirstOrDefault(x => x.Id == userId);
This is however resulting in this error:
System.NotSupportedException: 'The specified type member 'GetLatestTransaction' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.'
Is there some way to achieve this without storing another relation to the latest transaction on the user and having to update it every time there is a new transaction?
Edit: I would also like to do it as above to avoid making another query to the database, I want it all in one go to improve performance.
Your ApplicationUser class represents the table in the database. It does not represent the usage of the data in the table.
Quite a lot of people think it is good practice to separate the database structure from the usage of the data. This separation is quite often done using the repository pattern. The repository is an abstraction from the internal datastructure of the database. It allows you to add functionality to your classes without demanding this functionality in the control classes that communicate with the database.
There are numerous articles about the repository. This one helped me to understand what functionality I should put in my entity framework classes and which in the repository.
So you'll need a class that represents the elements in your database table and one that represents the applicationUsers with only their LatestTransaction
The class that represents the database table:
class ApplicationUser : IdentityUser
{
public int Id {get; set;}
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual List<TransactionModel> Transactions { get; set; }
}
ApplicationUser with the latest transaction
class AppicationUserExt : <base class needed?>
{
public int Id {get; set;}
public string FirstName { get; set; }
public string LastName { get; set; }
public TransactionModel LatestTransaction { get; set; }
}
The function to get your extended ApplicationUser is an extension function of your ApplicationUser. Input: IQueryable<ApplicationUser output: IQueryable<ApplicationUserExt>
static class MyDbContextExtensions
{
// returns ne ApplicationUserExt for every ApplicationUser
public IQueryable<ApplicationUserExt> ToExtendedUsers(this IQueryable<ApplicationUser> applicationUsers)
{
return applicationUsers
.Select(user => new ApplicationUserExt()
{
Id = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
LatestTransaction = user.Trnasactions
.OrderByDescenting(transaction => transaction.CreationDate)
.FirstOrDefault(),
}
}
}
}
So whenever you have a query with the ApplicationUsers you want, you can use ToExtendedUsers() to get the extended suers
using (var dbContext = new MyDbContext(...))
{
// you wanted to have a query like:
var result dbContext.ApplicationUsers
.Where(user => user.FirstName = "John"
&& user.LastName = "Doe");
// you'll have to add ToExtendedUsers:
var result = dbContext.ApplicationUsers
.Where(user => user.FirstName = "John"
&& user.LastName = "Doe");
.ToExtendedUsers();
}
As the result is still an IQueryable, no query has been done yet. You can still add LINQ statements before the query is done:
var result2 = result
.Where(user.LatestTransaction.Year == 2018)
.GroupBy(user => user.LatestTransaction.Date)
.OrderBy(group => group.Key)
.Take(10)
.ToList();
You see, that you can still do all kinds of LINQ stuff as long as it is an ApplicationUser. As soon as you need the LatestTransaction you convert it to an ApplicationUserExt and continue concatenating your linq statements.

Creating two linked objects before linked field has been generated by database - Entity Framework

I would like to know if there is a way to add two linked objects to a database through entity framework before the linked field has been generated by the database.
I am still learning EF and I'm not exactly sure how to ask this question clearly so here is an example of what I am trying to achieve:
I have two classes:
class Sale
{
public int Id { get; set; } // generated by SQL Server
public string Foo { get; set; }
public string Bar { get; set; }
public virtual ICollection<SalesComment> Comments { get; set; }
}
class SalesComment
{
public int Id { get; set; }
public int SaleId { get; set; }
public string Comment {get; set; }
}
Using fluent api in my 'dbcontext' class I link the two objects like so:
modelBuilder.Entity<Sale>().HasMany(s => s.Comments).WithRequired().HasForeignKey(c => c.SaleId);
This works great, I can retrieve a Sale object from the database and I can loop through the comments linked to it by SaleId.
What I want to do is when creating a new Sale, add a Comment as I am creating it, with EF realising this is happening and adding the SaleId to the Comments table once it is generated by the database, something like:
using (MyDatabase db = new MyDatabase())
{
var sale = db.Set<Sale>();
sale.Add(new Sale
{
Foo = "Test",
Bar = "Test",
Comment.Add(new SalesComment .... //this is the line i'm not sure about
});
db.SaveChanges();
}
Is something like this possible? Or would I have to first save the Sale object to the database so that a SaleId is generated, then retrieve that object again so I can read the SaleId to use for a new SalesComment.
Try this.
using (MyDatabase db = new MyDatabase())
{
var sale = db.Set<Sale>();
var saleComment = new SalesComment{Comment="Hello"};
var saleToAdd = new Sale
{
Foo = "Test",
Bar = "Test",
Comments = new List<SalesComment> {saleComment}
});
sale.Add(saleToAdd);
db.SaveChanges();
// After saving changes, these 2 values will be populated and are equal
var saleID = saleToAdd.Id;
var saleCommentSaleId = saleComment.saleId;
}
You don't have to retrieve the object again if you cache it properly before adding it to the DbSet.

Ef many to many linq query that gets objects that have all list members

I am using entity framework. I have two domain models.
public class Animal
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Feature> Features { get; set; }
}
public class Feature
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Animal> Animals { get; set; }
}
Animal and feature have many to many relationship so I have these tables in DB:
Animal
Feature
AnimalFeature
I get a list
var featureList = new List<Feature> { new Feature { Name = "f1" }, new Feature { Name = "f2" } };
How do i get a list of animals which contains all features in featureList
Any help will be appreciated. I'm stuck on this one really bad.
You can try something like this:
var features = { "f1","f2" };
var query = from a in dc.Animals
where ( from f in a.Features
where features.Contains(f.Name)
select f).Count() == features.Length
select a;
I suggest you use a primitive type collection instead a List of Features because EF only works with primitive types and enum types when you need to check if several elements are contained in a specific set. You can't compare two custom objects in your where condition. Remember this query is going to be translated to sql, and EF doesn't know how to translate the comparison between two objects. So, as I said before, you should use primitive types or enum types in this kind of query. You can select the names or Ids of the features you need to have the animals and compare with that collection as I did above.
Anyway, I think you can also do this another query using the Any extension method:
var featureList = new List<Feature> { new Feature { Name = "f1" }, new Feature { Name = "f2" } };
var query = from a in dc.Animals
where ( from f in a.Features
where featureList.Any(e=>e.Name==f.Name)
select f).Count() == featureList.Count
select a;
But I particularly prefer the first variant.

Combine select clauses in linq to entities with anonymous types

How can i combine select clause in linq to entities in order to project into an anonymous type?
Assume that I have these entities:
public class Address
{
public string City { get; set; }
public int ZipCode { get; set; }
//other properties
}
public class Person
{
public string Name { get; set; }
public Address Address { get; set; }
//a LOT of other properties
}
//extend person class with employee specific properties
public class Employee : Person
{
public double Salary { get; set; }
public Person Manager { get; set; }
}
Sometimes I only need to request few properties of my Person class:
Context.Persons.Where(...).Select(p => new
{
p.Name,
PersonCity = p.Address.City,
//other needed properties
});
And I also need to request the same properties of my Employee class plus the specific properties:
Context.Employees.OfType<Employee>().Where(...).Select(e => new
{
e.Salary,
ManagerName = e.Manager.Name,
e.Name,
PersonCity = e.City.Name,
//other needed properties identical as the previous select with Person entity
});
Is it possible with expression-tree manipulations (or another solution) to combine two select clauses in order to not duplicate all the select clause from my Person entity?
Something like that:
var personSelect = p => new {
p.Name,
PersonCity = p.Address.City,
//other needed properties
};
var employeeSelect = personSelect.Combine(e => new {
e.Salary,
ManagerName = e.Manager.Name
});
context.Employees.OfType<Employee>().Where(...).Select(employeeSelect).FirstOrDefault();
// returns an anonymous object
// {
// Name = "Joachim",
// PersonCity = "Lyon",
// <other Person properties>
// Salary = 65432.10,
// ManagerName = "Samuel"
// }
No, there is no way to do exactly what you're asking for. The problem is that each anonymous type has to be created at compile time, but expression trees work at runtime.
I can see two ways to work around that:
Your anonymous type for Employee would have a property called something like PersonData, which would contain the anonymous type with the information from Person.
You would create normal types like PersonData and EmployeeData (which inherits from PersonData). Each type would be able to give you an expression to create it and EmployeeData's expression would be computed based on PersonData's expression.
In both cases you would need some expression trees plumbing, but it shouldn't be hard to do that.

Entity Framework 4: How to code projection to a class type?

If I have a class like the following:
public class Customer {
public int id {get;set;}
public string name {get;set;}
public string line1 {get;set;}
public string line2 {get;set;}
public string line3 {get;set;}
public string line4 {get;set;}
}
And I only want to select the ID and Name values, leaving the rest null.
var myCustomerList = DC.Customer.Select(
p => new Customer { id = p.id, name = p.name });
I get the following error:
The entity or complex type 'MyModel.Customer' cannot
be constructed in a LINQ to Entities query.
How else would you do it? Am I required to specify all the Class's fields?
Try this:
var myCustomerList = from c in DC.Customer
select new { id = p.id, name = p.name };
The above will create an Anonymous Type.
Practical Application:
"var myCustomerList" <-- Anonymous Type.
An anonymous type with two properties "id" and "name". Also, "var" lets you create an Implicitly typed local variable. This means:
a) You didn't have to declare/write a class structure to hold a type with only those two properties;
b) You don't have to maintain that either - you can change the structure of the above query, and "it just works".
Another option is to create a CustomerInfo type:
public class CustomerInfo
{
public int Id { get; set;}
public string Name { get; set; }
}
You can't map two types directly to the same table in EF but you can easily create a view for your info type and then map to that:
CREATE VIEW vwCustomerInfo AS SELECT Id, Name FROM Customer
You then map your CustomerInfo type to your view:
public class CustomerInfoMap : EntityConfiguration<CustomerInfo>
{
public CustomerInfoMap()
{
.ToTable("vwCustomerInfo");
}
}
A side-effect of this is that EF will only retrieve the columns in the view when querying your database. When retrieving a CustomerInfo by id you'll get SQL like this:
SELECT Id, Name FROM vwCustomers WHERE id = 1
In addition, as long as your view is updatable you can update your CustomerInfo type from EF and the underlying table will be updated.

Categories