I have two classes Person and Animal
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace MongoTesting.Documents
{
public class Person
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid PersonId { get; set; } = Guid.NewGuid();
public Guid PetId { get; set; } = Guid.Empty;
public string Name { get; set; } = "Person";
}
}
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace MongoTesting.Documents
{
public class Animal
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid AnimalId { get; set; } = Guid.NewGuid();
public bool IsMammal { get; set; }
public string Description { get; set; } = "Animal";
}
}
Which are serialized into IMongoCollections
public IMongoCollection<Person> PersonCollection { get; set; }
public IMongoCollection<Animal> AnimalCollection { get; set; }
...
PersonCollection = Database.GetCollection<Person>("PersonCollection");
AnimalCollection = Database.GetCollection<Animal>("AnimalCollection");
Using the C# 2.7.0 MongoDB .Net driver and the MongoDB 3.4 mongod server daemon.
I am trying to write a specific type of query given this setup, specifically the one in the SSCCE below
using System;
using System.Linq;
using System.Collections.Generic;
using MongoTesting.Documents;
using MongoTesting.DatabaseInterface;
using MongoDB.Driver;
namespace MongoTesting
{
class Program
{
static void Main(string[] args)
{
MongoInterface _mongo = new MongoInterface();
_mongo.Open();
//CreateDocuments(_mongo);
Console.Out.WriteLine("Total Persons: " + _mongo.PersonCollection.CountDocuments(Builders<Person>.Filter.Empty));
Console.Out.WriteLine("Total Animals: " + _mongo.AnimalCollection.CountDocuments(Builders<Animal>.Filter.Empty));
var peopleWithMammalianPetsQuery =
from person in _mongo.PersonCollection.AsQueryable()
join animal in _mongo.AnimalCollection.AsQueryable() on person.PetId equals animal.AnimalId
where animal.IsMammal
select person;
var peopleWithMammalianPets = peopleWithMammalianPetsQuery.ToList();
peopleWithMammalianPets.ForEach(person => { Console.Out.WriteLine("Person: " + person.Name); });
Console.ReadLine();
}
public static void CreateDocuments(MongoInterface _mongo)
{
Animal dog = new Animal() { IsMammal = true, Description = "Dog" };
Animal cat = new Animal() { IsMammal = true, Description = "Cat" };
Animal bird = new Animal() { IsMammal = false, Description = "Bird" };
Animal snake = new Animal() { IsMammal = false, Description = "Snake" };
Person bob = new Person() { PetId = dog.AnimalId, Name = "Bob" };
Person sue = new Person() { PetId = cat.AnimalId, Name = "Sue" };
Person sam = new Person() { PetId = bird.AnimalId, Name = "Sam" };
Person eve = new Person() { PetId = snake.AnimalId, Name = "Eve" };
_mongo.PersonCollection.InsertMany(new List<Person>() { bob, sue, sam, eve });
_mongo.AnimalCollection.InsertMany(new List<Animal>() { dog, cat, bird, snake });
}
}
}
Where MongoInterface represents a class which can connect to the MongoDB server, get access to the IMongoDatabase, and manipulate PersonCollection and AnimalCollection.
My specific issue with the code above, is that, when ran the following exception is thrown during the execution of the peopleWithMammalianPetsQuery.
An unhandled exception of type 'System.NotSupportedException' occurred in
MongoDB.Driver.dll
Additional information: $project or $group does not support {document}.
I have searched around and I cannot find something the exactly duplicates this issue and solves it. There are many reports of the exception message though they all seem to differ in the usage which produced it (even some seem to have been fixed). I have also seen multiple posts showing very similar code (specifically a join) working without issue.
How can I perform the query described above?
Related
I'm developing a project with N-tier architecture. I got errors while generating a new object in the main function.I can not give the features of car1. The entire text is underlined in red and I get these errors "CS1922: Cannot initialize type 'Car' with a collection initializer because it does not implement 'System.Collections.IEnumerable'" and "CS0747: Invalid initializer member declator".
using Business.Concrete;
using System;
using Entities.Concrete;
using DataAccess.Concrete.EntityFramework;
static void Main(string[] args)
{
Car car1 = new Car
{
car1.Id = 4,
car1.BrandId = 1,
car1.ColourId = 2,
car1.ModelYear = 1990,
car1.Name = "King",
car1.DailyPrice = 150,
car1.Description = "Best"
};
CarManager carManager = new CarManager(new EfCarDal());
Console.ReadLine();
}
Entities\Concrete\Car.cs
using Core.Entities;
using System;
using System.Collections.Generic;
using System.Text;
namespace Entities.Concrete
{
public class Car : IEntity
{
public int Id { get; set; }
public int BrandId { get; set; }
public int ColourId { get; set; }
public string Name { get; set; }
public int ModelYear { get; set; }
public int DailyPrice { get; set; }
public string Description { get; set; }
}
}
You don't need the car1. access when initializing within {} like so:
Car car1 = new Car
{
Id = 4,
BrandId = 1,
ColourId = 2,
ModelYear = 1990,
Name = "King",
DailyPrice = 150,
Description = "Best"
};
My VS intellisense does not work when i do lambda queries, like Join, GroupJoin, etc. The properties of the second model never appear in the suggestions. I'm sorry for my english :)
See the images:
As #JeroenMostert said, this is a known bug. If you really want the intellisense, you can specify your types; with result2 you'll get intellisense.
You just have to decide if having to explicitly set your types is worth it, especially as it means you can't really return an anonymous object.
Personally, I don't think making your code more verbose is worth it as not having intellisense won't prevent you from setting up your lambda.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var people = new List<Person>();
var employees = new List<Employee>();
var result = employees.Join(people, x => x.Id, y => y.Id, (x, y) => new JoinedItem{ Id = x.Id, Name = y.Name });
var result2 = employees.Join<Employee, Person, int, JoinedItem>(people, x => x.Id, y => y.Id, (x, y) => new JoinedItem { Id = x.Id, Name = y.Name });
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class JoinedItem
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Apparently there is a workaround, by placing the second key selector and the result selector between brackets ().
class Person
{
public string Name { get; set; }
}
class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
public static void JoinEx1()
{
Person magnus = new Person { Name = "Hedlund, Magnus" };
Person terry = new Person { Name = "Adams, Terry" };
Person charlotte = new Person { Name = "Weiss, Charlotte" };
Pet barley = new Pet { Name = "Barley", Owner = terry };
Pet boots = new Pet { Name = "Boots", Owner = terry };
Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
Pet daisy = new Pet { Name = "Daisy", Owner = magnus };
List<Person> people = new List<Person> { magnus, terry, charlotte };
List<Pet> pets = new List<Pet> { barley, boots, whiskers, daisy };
var query =
people.Join(pets,
person => person,
(pet => pet.Owner), // intellisense here
((person, pet) => // intellisense here
new { OwnerName = person.Name, Pet = pet.Name }));
Afterwards the brackets can be removed, but intellisense helps a lot on complicated object structures.
Trying to find a simple way to combine strings from several model into a single string using linq to object expressions. Trying to put the result either all in first object where bob's name is, or all in People.names location. Maybe I need to add an another extension method like coalesce?
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
People people = new People
{
Persons =
{
new Person{
Name = "Bob",
Age = 15
},
new Person{
Name = "James",
Age = 17
},
new Person{
Name = "Mary",
Age = 15
}
},
};
people.names = people.Persons.Select(p => p.Name).ToList().ToString();
Console.WriteLine(people.names);
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public class People
{
public People() {
Persons = new List<Person>();
}
public string names { get; set; }
public IList<Person> Persons { get; set; }
}
}
Could do something like this:
class People
{
public List<Person> Persons { get; set; }
public string Names
{
get
{
if (Persons != null)
{
return String.Join(",", Persons.Select(p => p.Name));
}
else
{
return string.Empty;
}
}
}
}
class Person
{
public string Name { get; set; }
}
You can use string.Join:
Console.WriteLine(String.Join(" ",people.Persons.Select(p => p.Name)));
You can use string.Join to join several strings using a separator. To join the names use a simple select like:
string joinedNames = string.Join(",", people.Persons.Select(p => p.Name));
Dont't forget to add
using System.Linq;
Just for fun versions
people.Aggregate("", (a, b) => $"{a} {b.Name}").Trim()
string.Concat(people.Select(p => p.Name + " ")).Trim()
Crazy version:
string.Concat(people.Zip(
Enumerable.Range(0, people.Count).Select(x => " "),
(p, s) => p.Name + s)).Trim()
Ill keep the question short, as i suspect the answer may be a short no..
I would like to be able to pass a query into an entity framework function, to enable dynamic runtime querying through the ui to the database?
I imagined this to be like passing an IQueryable into a method, but exactly how i would go about this, i am a little unsure at the moment. Am i thinking about this the wrong way? Perhaps querying in the business layer, not semi-directly to the database?
Based on the comments, I'm providing the two options.
Based on a set of options given to the user.
Use a TreeExpression to build you expression dynamically, somthing like the example above (this example is indeed too simple, but you can have an idea).
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 = Expression.Lambda<Func<int, bool>>(numLessThanFive, new ParameterExpression[] { numParam });
This link will give you some more info on the subject: https://msdn.microsoft.com/en-us/library/bb882637.aspx
For letting the user type some expression and converting it to a query, search for Antlr http://www.antlr.org/, or some tool like this, padronize the expression syntax you want to implement and go for the 1ยบ solution to build the expression.
My example code is below. You'll need to install the following packages...
install-package entityframework
install-package newtonsoft.json
Be aware that this code is susceptible to injection by inserting valid VB.NET code to escape the query.
I compiled the code into 30742268.exe, which you can see is added as a reference for the IContext interface, etc.
using System;
using System.Linq;
using System.Text;
using Microsoft.VisualBasic;
using System.CodeDom.Compiler;
using Model;
using System.Collections.Generic;
using System.Data.Entity;
using Newtonsoft.Json;
namespace _30742268 {
class Program {
const String queryWrapperCode = #"
Imports System.Linq
Imports System.Data.Entity
Imports Model
Public Class DynamicQuery
Implements IDynamicQuery
Public Function Run(data As IContext) As IQueryable Implements IDynamicQuery.Run
Return {0}
End Function
End Class
";
static void Main(String[] args) {
using (var provider = new VBCodeProvider()) {
var parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.Core.dll");
parameters.ReferencedAssemblies.Add("EntityFramework.dll");
parameters.ReferencedAssemblies.Add("30742268.exe");
parameters.GenerateInMemory = true;
Console.WriteLine("Enter LINQ queries, 'demo' for an example, 'exit' to stop:");
for (;;) {
try {
var dynamicQueryString = Console.ReadLine();
if (dynamicQueryString == "exit")
return;
if (dynamicQueryString == "demo")
Console.WriteLine(dynamicQueryString = "from person in data.People where person.Name.Length = 4");
var results = provider.CompileAssemblyFromSource(parameters, String.Format(queryWrapperCode, dynamicQueryString));
if (results.Errors.HasErrors) {
var sb = new StringBuilder();
foreach (CompilerError error in results.Errors) {
sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
}
throw new InvalidOperationException(sb.ToString());
}
var assembly = results.CompiledAssembly;
var assemblyType = assembly.GetTypes().Single(x => typeof (IDynamicQuery).IsAssignableFrom(x));
var constructorInfo = assemblyType.GetConstructor(new Type[] {});
var dynamicQuery = (IDynamicQuery) constructorInfo.Invoke(null);
using (var context = new Context()) {
dynamic result = dynamicQuery.Run(context);
foreach (var person in result)
Console.WriteLine(person);
}
}
catch (Exception exception) {
Console.WriteLine(exception);
}
}
}
}
}
}
namespace Model {
public interface IDynamicQuery {
IQueryable Run(IContext context);
}
public abstract class Entity {
public override String ToString() {
return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
}
}
public class Person : Entity {
public Int64 Id { get; protected set; }
public String Name { get; set; }
public virtual Home Home { get; set; }
}
public class Home : Entity {
public Int64 Id { get; protected set; }
public String Address { get; set; }
public virtual ICollection<Person> Inhabitants { get; set; }
}
public interface IContext {
IQueryable<Person> People { get; set; }
IQueryable<Home> Homes { get; set; }
}
public class Context : DbContext, IContext {
public virtual DbSet<Person> People { get; set; }
public virtual DbSet<Home> Homes { get; set; }
IQueryable<Person> IContext.People {
get { return People; }
set { People = (DbSet<Person>)value; }
}
IQueryable<Home> IContext.Homes {
get { return Homes; }
set { Homes = (DbSet<Home>)value; }
}
public Context() {
Configuration.ProxyCreationEnabled = false;
Database.SetInitializer(new ContextInitializer());
}
}
class ContextInitializer : DropCreateDatabaseAlways<Context> {
protected override void Seed(Context context) {
var fakeSt = new Home {Address = "123 Fake St."};
var alabamaRd = new Home {Address = "1337 Alabama Rd."};
var hitchhikersLn = new Home {Address = "42 Hitchhiker's Ln."};
foreach (var home in new[] {fakeSt, alabamaRd, hitchhikersLn})
context.Homes.Add(home);
context.People.Add(new Person { Home = fakeSt , Name = "Nick" });
context.People.Add(new Person { Home = fakeSt , Name = "Paul" });
context.People.Add(new Person { Home = fakeSt , Name = "John" });
context.People.Add(new Person { Home = fakeSt , Name = "Henry" });
context.People.Add(new Person { Home = alabamaRd , Name = "Douglas" });
context.People.Add(new Person { Home = alabamaRd , Name = "Peter" });
context.People.Add(new Person { Home = alabamaRd , Name = "Joshua" });
context.People.Add(new Person { Home = hitchhikersLn, Name = "Anne" });
context.People.Add(new Person { Home = hitchhikersLn, Name = "Boris" });
context.People.Add(new Person { Home = hitchhikersLn, Name = "Nicholes" });
context.People.Add(new Person { Home = hitchhikersLn, Name = "Betty" });
context.SaveChanges();
}
}
}
I'm trying to update objects in Entity Framework and I'd like to know what the best way of updating Entities that contain a List of other Entities.
Consider the following example:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EntityFrameworkUpdateSketch
{
public class A
{
[Key]
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None)]
public int Akey {get;set;}
public string Aname { get; set; }
public virtual List<B> Blist {get;set;}
public A()
{
Blist = new List<B>();
}
}
public class B
{
public virtual A owner { get; set; }
[ForeignKey("owner")]
[Key, Column(Order = 0)]
public int Akey { get; set; }
[Key, Column(Order = 1)]
public int Order { get; set; }
public string Bname { get; set; }
}
public class Database: DbContext
{
public DbSet<A> As { get; set; }
public DbSet<B> Bs { get; set; }
}
public class Program
{
static void Main(string[] args)
{
// Make a new object
using (Database db = new Database())
{
A intodb = new A();
intodb.Akey = 1;
intodb.Aname = "Name of A object";
intodb.Blist.Add(new B() { Bname = "Name of B object one",Order=1 });
intodb.Blist.Add(new B() { Bname = "Name of B object two",Order=2 });
db.As.Add(intodb);
db.SaveChanges();
}
// Update the object in database
using (Database db = new Database())
{
// Imagine I got "update" from somewhere else
A update = new A();
update.Akey = 1;
update.Aname = "New Name of A object";
update.Blist.Add(new B() { Bname = "New Name of B object one" ,Akey=1, Order=1});
update.Blist.Add(new B() { Bname = "New Name of B object two", Akey = 1, Order = 2 });
update.Blist.Add(new B() { Bname = "A whole new object 3", Akey = 1, Order = 3 });
db.Entry<A>(update).State = EntityState.Modified;
db.SaveChanges();
}
}
}
}
I have an object "A" that contains a List as a property.
In the first half of my demonstration program I create a new object of type A and then store it in the database.
In the second half, I make a new object called A and I want to update the object in the database with new values- I want to change the B names and I want to add a new B object to the list.
This works for changing A , but the subcategories B don't work- they don't have new names.
The reason I'm trying to do this is because I'm trying to write a PUT method for a MVC WebAPI controller that stores its data in Entity Framework. I'm getting a new object passed in to my PUT action and I want to replace an existing object in the database.
I can't delete A and re add it (although this does work) because this breaks foreign keys- I have cascading deletes in my "real" solution that removes references elsewhere if I delete my A object.
You don't need to know anything about WebAPI or PUT to answer this question.
Here is a program that works, but could do with improving. All this program does now is delete all B objects in the database that belong to A, then re-add. This works but it seems bad. Any improvements?
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EntityFrameworkUpdateSketch
{
public class A
{
[Key]
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None)]
public int Akey {get;set;}
public string Aname { get; set; }
public virtual List<B> Blist {get;set;}
public A()
{
Blist = new List<B>();
}
}
public class B
{
public virtual A owner { get; set; }
[ForeignKey("owner")]
[Key, Column(Order = 0)]
public int Akey { get; set; }
[Key, Column(Order = 1)]
public int Order { get; set; }
public string Bname { get; set; }
}
public class Database: DbContext
{
public DbSet<A> As { get; set; }
public DbSet<B> Bs { get; set; }
}
public class Program
{
static void Main(string[] args)
{
// Make a new object
using (Database db = new Database())
{
A intodb = new A();
intodb.Akey = 1;
intodb.Aname = "Name of A object";
intodb.Blist.Add(new B() { Bname = "Name of B object one",Order=1 });
intodb.Blist.Add(new B() { Bname = "Name of B object two",Order=2 });
db.As.Add(intodb);
db.SaveChanges();
}
// Update the object in database
using (Database db = new Database())
{
// Imagine I got "update" from somewhere else
A update = new A();
update.Akey = 1;
update.Aname = "New Name of A object";
update.Blist.Add(new B() { Bname = "New Name of B object one" ,Akey=1, Order=1});
update.Blist.Add(new B() { Bname = "New Name of B object two", Akey = 1, Order = 2 });
update.Blist.Add(new B() { Bname = "A whole new object 3", Akey = 1, Order = 3 });
db.Entry<A>(update).State = EntityState.Modified;
// Clear all existing B objects attached to the updated object
var query = from c in db.Bs.AsEnumerable() where c.Akey==update.Akey select c;
foreach (var z in query)
{
db.Bs.Remove(z);
}
// Readd B objects
foreach (var i in update.Blist)
{
db.Bs.Add(i);
}
db.SaveChanges();
}
}
}
}