2016-07-05 37 views
0

假设我在Mongo有三个系列:flavor,colorcupcake。每个集合都有自己_id(明显),并且在cupcake集合引用_id S IN flavorcupcake像这样:如何在C#中使用类型安全的Mongo对象ID?

{ 
    "_id": ObjectId("123"), 
    "flavorId": ObjectId("234"), 
    "colorId": ObjectId("345"), 
    "moreData": {} 
} 

这是一个玩具的例子,当然,并没有在这些集合更多的东西。这对这个问题并不重要,只不过它是我查询时真正需要的moreData

我希望能够查找cupcake对象flavorIdcolorId(并且它们适合于这种查找索引)。然而,这两个字段都是ObjectId,我想避免有人不小心寻找colorIdflavorId。我如何设计对象和存储库类,以使colorIdflavorId为不同类型,以便编译器不允许交换它们,但仍将这两个ID存储为ObjectId?

我的第一个想法是扩展ObjectId并传递扩展对象,但是ObjectId是struct,无法扩展。

+0

这似乎有点多余?*,我想避免有人不小心寻找带有flavorId *的colorId。你不能允许有人引入错误 – Liam

+0

这比引入这个特定的错误更容易。假设你有一个仓库对象,并且你认为,“我将通过'flavorId'获得所有'cupcake'对象。”所以你调用cupcakeRepository.Find(ObjectId flavorId),因为IntelliSense非常有帮助地提示它,而你没有读取参数名称。嘿,它编译!即使你的单元测试也通过了,因为你根据你认为的方法嘲笑了方法。 – meustrus

+0

或'cupcakeRepository.Find(ObjectId colorId)'。我实际上忘记了在键入这两个句子之间我正在尝试使用哪一个。看,这不仅仅是关于其他开发者,这是为了保护我免受自己的伤害。 – meustrus

回答

0

所以我结束了咬关于构建Mongo特定垃圾的子弹,以便为此定制类。因此,这里是我的下拉更换为的ObjectId:

public struct DocumentId<T> : IEquatable<DocumentId<T>> 
{ 
    static DocumentId() 
    { 
     BsonSerializer.RegisterSerializer(typeof(DocumentId<T>), DocumentIdSerializer<T>.Instance); 
     BsonSerializer.RegisterIdGenerator(typeof(DocumentId<T>), DocumentIdGenerator<T>.Instance); 
    } 

    public static readonly DocumentId<T> Empty = new DocumentId<T>(ObjectId.Empty); 
    public readonly ObjectId Value; 

    public DocumentId(ObjectId value) 
    { 
     Value = value; 
    } 

    public static DocumentId<T> GenerateNewId() 
    { 
     return new DocumentId<T>(ObjectId.GenerateNewId()); 
    } 

    public static DocumentId<T> Parse(string value) 
    { 
     return new DocumentId<T>(ObjectId.Parse(value)); 
    } 

    public bool Equals(DocumentId<T> other) 
    { 
     return Value.Equals(other.Value); 
    } 

    public override bool Equals(object obj) 
    { 
     if (ReferenceEquals(null, obj)) return false; 
     return obj is DocumentId<T> && Equals((DocumentId<T>)obj); 
    } 

    public static bool operator ==(DocumentId<T> left, DocumentId<T> right) 
    { 
     return left.Value == right.Value; 
    } 

    public static bool operator !=(DocumentId<T> left, DocumentId<T> right) 
    { 
     return left.Value != right.Value; 
    } 

    public override int GetHashCode() 
    { 
     return Value.GetHashCode(); 
    } 

    public override string ToString() 
    { 
     return Value.ToString(); 
    } 
} 

public class DocumentIdSerializer<T> : StructSerializerBase<DocumentId<T>> 
{ 
    public static readonly DocumentIdSerializer<T> Instance = new DocumentIdSerializer<T>(); 

    public override DocumentId<T> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) 
    { 
     return new DocumentId<T>(context.Reader.ReadObjectId()); 
    } 

    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DocumentId<T> value) 
    { 
     context.Writer.WriteObjectId(value.Value); 
    } 
} 

public class DocumentIdGenerator<T> : IIdGenerator 
{ 
    public static readonly DocumentIdGenerator<T> Instance = new DocumentIdGenerator<T>(); 

    public object GenerateId(object container, object document) 
    { 
     return DocumentId<T>.GenerateNewId(); 
    } 

    public bool IsEmpty(object id) 
    { 
     var docId = id as DocumentId<T>? ?? DocumentId<T>.Empty; 
     return docId.Equals(DocumentId<T>.Empty); 
    } 
} 

类型参数T可以是任何东西;它从未被使用过。它应该是你的对象的类型,像这样:

public class Cupcake { 
    [BsonId] 
    public DocumentId<Cupcake> Id { get; set; } 
    // ... 
} 

这样,你Flavor类有DocumentId<Flavor>类型的ID和Color类有DocumentId<Color>类型的ID,决不应在两个互换。现在我可以创建具有以下明确的方法,以及一个CupcakeRepository

public interface ICupcakeRepository { 
    IEnumerable<Cupcake> Find(DocumentId<Flavor> flavorId); 
    IEnumerable<Cupcake> Find(DocumentId<Color> colorId); 
} 

这应该与现有的数据以及安全的,因为序列化表示形式是完全一样的,只是一个ObjectId("1234567890abcef123456789")

1

您将无法防止这些错误,但您可以使用数字间隔来使“人员”更容易找到问题。

如果我没有弄错,你可以设置ID,所以你可以为每种类型使用“前缀”。

颜色可能与1000开始,与2000年的口味等等...

+0

不幸的是,这不能解决问题。它可以确保进行错误的调用总是不会返回任何结果,但是现在程序员需要知道甚至更多的疑难问题,而不需要考虑它们的ObjectId是'colorId'还是'flavorId'。 – meustrus

+0

他们不需要知道它。知道这是可选的,但如果他这样做,这会让他更容易。只要他开始玩弄这些ID,他很可能会在没有任何文档的情况下找到该模式。 – Woozar

0

嗯,它是一种软的问题,因为在大多数仓库ID是常见(如整数)的东西。因此,考虑到这一点,我们可以强制传递一个额外的参数,而不是改变基本对象,这样的防弹解决方案

cupcakeRepository.Find(ObjectId flavorId, ÒbjectType ÒbjectType.Flavor) 

或只是扩展库更详细

cupcakeRepository.FindByColor(ObjectId id) 

cupcakeRepository.FindByFlavor(ObjectId id)